1 /* 2 * hid.c 3 */ 4 5 /*- 6 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 7 * 8 * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com> 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $ 33 * $FreeBSD$ 34 */ 35 36 #include <sys/consio.h> 37 #include <sys/mouse.h> 38 #include <sys/queue.h> 39 #include <assert.h> 40 #define L2CAP_SOCKET_CHECKED 41 #include <bluetooth.h> 42 #include <dev/usb/usb.h> 43 #include <dev/usb/usbhid.h> 44 #include <errno.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <syslog.h> 49 #include <unistd.h> 50 #include <usbhid.h> 51 #include "bthid_config.h" 52 #include "bthidd.h" 53 #include "btuinput.h" 54 #include "kbd.h" 55 56 /* 57 * Inoffical and unannounced report ids for Apple Mice and trackpad 58 */ 59 #define TRACKPAD_REPORT_ID 0x28 60 #define AMM_REPORT_ID 0x29 61 #define BATT_STAT_REPORT_ID 0x30 62 #define BATT_STRENGTH_REPORT_ID 0x47 63 #define SURFACE_REPORT_ID 0x61 64 65 /* 66 * Apple magic mouse (AMM) specific device state 67 */ 68 #define AMM_MAX_BUTTONS 16 69 struct apple_state { 70 int y [AMM_MAX_BUTTONS]; 71 int button_state; 72 }; 73 74 #define MAGIC_MOUSE(D) (((D)->vendor_id == 0x5ac) && ((D)->product_id == 0x30d)) 75 #define AMM_BASIC_BLOCK 5 76 #define AMM_FINGER_BLOCK 8 77 #define AMM_VALID_REPORT(L) (((L) >= AMM_BASIC_BLOCK) && \ 78 ((L) <= 16*AMM_FINGER_BLOCK + AMM_BASIC_BLOCK) && \ 79 ((L) % AMM_FINGER_BLOCK) == AMM_BASIC_BLOCK) 80 #define AMM_WHEEL_SPEED 100 81 82 /* 83 * Probe for per-device initialisation 84 */ 85 void 86 hid_initialise(bthid_session_p s) 87 { 88 hid_device_p hid_device = get_hid_device(&s->bdaddr); 89 90 if (hid_device && MAGIC_MOUSE(hid_device)) { 91 /* Magic report to enable trackpad on Apple's Magic Mouse */ 92 static uint8_t rep[] = {0x53, 0xd7, 0x01}; 93 94 if ((s->ctx = calloc(1, sizeof(struct apple_state))) == NULL) 95 return; 96 write(s->ctrl, rep, 3); 97 } 98 } 99 100 /* 101 * Process data from control channel 102 */ 103 104 int32_t 105 hid_control(bthid_session_p s, uint8_t *data, int32_t len) 106 { 107 assert(s != NULL); 108 assert(data != NULL); 109 assert(len > 0); 110 111 switch (data[0] >> 4) { 112 case 0: /* Handshake (response to command) */ 113 if (data[0] & 0xf) 114 syslog(LOG_ERR, "Got handshake message with error " \ 115 "response 0x%x from %s", 116 data[0], bt_ntoa(&s->bdaddr, NULL)); 117 break; 118 119 case 1: /* HID Control */ 120 switch (data[0] & 0xf) { 121 case 0: /* NOP */ 122 break; 123 124 case 1: /* Hard reset */ 125 case 2: /* Soft reset */ 126 syslog(LOG_WARNING, "Device %s requested %s reset", 127 bt_ntoa(&s->bdaddr, NULL), 128 ((data[0] & 0xf) == 1)? "hard" : "soft"); 129 break; 130 131 case 3: /* Suspend */ 132 syslog(LOG_NOTICE, "Device %s requested Suspend", 133 bt_ntoa(&s->bdaddr, NULL)); 134 break; 135 136 case 4: /* Exit suspend */ 137 syslog(LOG_NOTICE, "Device %s requested Exit Suspend", 138 bt_ntoa(&s->bdaddr, NULL)); 139 break; 140 141 case 5: /* Virtual cable unplug */ 142 syslog(LOG_NOTICE, "Device %s unplugged virtual cable", 143 bt_ntoa(&s->bdaddr, NULL)); 144 session_close(s); 145 break; 146 147 default: 148 syslog(LOG_WARNING, "Device %s sent unknown " \ 149 "HID_Control message 0x%x", 150 bt_ntoa(&s->bdaddr, NULL), data[0]); 151 break; 152 } 153 break; 154 155 default: 156 syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \ 157 "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL)); 158 break; 159 } 160 161 return (0); 162 } 163 164 /* 165 * Process data from the interrupt channel 166 */ 167 168 int32_t 169 hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len) 170 { 171 hid_device_p hid_device; 172 hid_data_t d; 173 hid_item_t h; 174 int32_t report_id, usage, page, val, 175 mouse_x, mouse_y, mouse_z, mouse_t, mouse_butt, 176 mevents, kevents, i; 177 178 assert(s != NULL); 179 assert(s->srv != NULL); 180 assert(data != NULL); 181 182 if (len < 3) { 183 syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \ 184 "channel from %s", len, bt_ntoa(&s->bdaddr, NULL)); 185 return (-1); 186 } 187 188 if (data[0] != 0xa1) { 189 syslog(LOG_ERR, "Got unexpected message 0x%x on " \ 190 "Interrupt channel from %s", 191 data[0], bt_ntoa(&s->bdaddr, NULL)); 192 return (-1); 193 } 194 195 report_id = data[1]; 196 data ++; 197 len --; 198 199 hid_device = get_hid_device(&s->bdaddr); 200 assert(hid_device != NULL); 201 202 mouse_x = mouse_y = mouse_z = mouse_t = mouse_butt = 0; 203 mevents = kevents = 0; 204 205 for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1); 206 hid_get_item(d, &h) > 0; ) { 207 if ((h.flags & HIO_CONST) || (h.report_ID != report_id) || 208 (h.kind != hid_input)) 209 continue; 210 211 page = HID_PAGE(h.usage); 212 val = hid_get_data(data, &h); 213 214 /* 215 * When the input field is an array and the usage is specified 216 * with a range instead of an ID, we have to derive the actual 217 * usage by using the item value as an index in the usage range 218 * list. 219 */ 220 if ((h.flags & HIO_VARIABLE)) { 221 usage = HID_USAGE(h.usage); 222 } else { 223 const uint32_t usage_offset = val - h.logical_minimum; 224 usage = HID_USAGE(h.usage_minimum + usage_offset); 225 } 226 227 switch (page) { 228 case HUP_GENERIC_DESKTOP: 229 switch (usage) { 230 case HUG_X: 231 mouse_x = val; 232 mevents ++; 233 break; 234 235 case HUG_Y: 236 mouse_y = val; 237 mevents ++; 238 break; 239 240 case HUG_WHEEL: 241 mouse_z = -val; 242 mevents ++; 243 break; 244 245 case HUG_SYSTEM_SLEEP: 246 if (val) 247 syslog(LOG_NOTICE, "Sleep button pressed"); 248 break; 249 } 250 break; 251 252 case HUP_KEYBOARD: 253 kevents ++; 254 255 if (h.flags & HIO_VARIABLE) { 256 if (val && usage < kbd_maxkey()) 257 bit_set(s->keys1, usage); 258 } else { 259 if (val && val < kbd_maxkey()) 260 bit_set(s->keys1, val); 261 262 for (i = 1; i < h.report_count; i++) { 263 h.pos += h.report_size; 264 val = hid_get_data(data, &h); 265 if (val && val < kbd_maxkey()) 266 bit_set(s->keys1, val); 267 } 268 } 269 break; 270 271 case HUP_BUTTON: 272 if (usage != 0) { 273 if (usage == 2) 274 usage = 3; 275 else if (usage == 3) 276 usage = 2; 277 278 mouse_butt |= (val << (usage - 1)); 279 mevents ++; 280 } 281 break; 282 283 case HUP_CONSUMER: 284 if (hid_device->keyboard && s->srv->uinput) { 285 if (h.flags & HIO_VARIABLE) { 286 uinput_rep_cons(s->ukbd, usage, !!val); 287 } else { 288 if (s->consk > 0) 289 uinput_rep_cons(s->ukbd, 290 s->consk, 0); 291 if (uinput_rep_cons(s->ukbd, val, 1) 292 == 0) 293 s->consk = val; 294 } 295 } 296 297 if (!val) 298 break; 299 300 switch (usage) { 301 case HUC_AC_PAN: 302 /* Horizontal scroll */ 303 mouse_t = val; 304 mevents ++; 305 val = 0; 306 break; 307 308 case 0xb5: /* Scan Next Track */ 309 val = 0x19; 310 break; 311 312 case 0xb6: /* Scan Previous Track */ 313 val = 0x10; 314 break; 315 316 case 0xb7: /* Stop */ 317 val = 0x24; 318 break; 319 320 case 0xcd: /* Play/Pause */ 321 val = 0x22; 322 break; 323 324 case 0xe2: /* Mute */ 325 val = 0x20; 326 break; 327 328 case 0xe9: /* Volume Up */ 329 val = 0x30; 330 break; 331 332 case 0xea: /* Volume Down */ 333 val = 0x2E; 334 break; 335 336 case 0x183: /* Media Select */ 337 val = 0x6D; 338 break; 339 340 case 0x018a: /* Mail */ 341 val = 0x6C; 342 break; 343 344 case 0x192: /* Calculator */ 345 val = 0x21; 346 break; 347 348 case 0x194: /* My Computer */ 349 val = 0x6B; 350 break; 351 352 case 0x221: /* WWW Search */ 353 val = 0x65; 354 break; 355 356 case 0x223: /* WWW Home */ 357 val = 0x32; 358 break; 359 360 case 0x224: /* WWW Back */ 361 val = 0x6A; 362 break; 363 364 case 0x225: /* WWW Forward */ 365 val = 0x69; 366 break; 367 368 case 0x226: /* WWW Stop */ 369 val = 0x68; 370 break; 371 372 case 0x227: /* WWW Refresh */ 373 val = 0x67; 374 break; 375 376 case 0x22a: /* WWW Favorites */ 377 val = 0x66; 378 break; 379 380 default: 381 val = 0; 382 break; 383 } 384 385 /* XXX FIXME - UGLY HACK */ 386 if (val != 0) { 387 if (hid_device->keyboard) { 388 int32_t buf[4] = { 0xe0, val, 389 0xe0, val|0x80 }; 390 391 assert(s->vkbd != -1); 392 write(s->vkbd, buf, sizeof(buf)); 393 } else 394 syslog(LOG_ERR, "Keyboard events " \ 395 "received from non-keyboard " \ 396 "device %s. Please report", 397 bt_ntoa(&s->bdaddr, NULL)); 398 } 399 break; 400 401 case HUP_MICROSOFT: 402 switch (usage) { 403 case 0xfe01: 404 if (!hid_device->battery_power) 405 break; 406 407 switch (val) { 408 case 1: 409 syslog(LOG_INFO, "Battery is OK on %s", 410 bt_ntoa(&s->bdaddr, NULL)); 411 break; 412 413 case 2: 414 syslog(LOG_NOTICE, "Low battery on %s", 415 bt_ntoa(&s->bdaddr, NULL)); 416 break; 417 418 case 3: 419 syslog(LOG_WARNING, "Very low battery "\ 420 "on %s", 421 bt_ntoa(&s->bdaddr, NULL)); 422 break; 423 } 424 break; 425 } 426 break; 427 } 428 } 429 hid_end_parse(d); 430 431 /* 432 * Apple adheres to no standards and sends reports it does 433 * not introduce in its hid descriptor for its magic mouse. 434 * Handle those reports here. 435 */ 436 if (MAGIC_MOUSE(hid_device) && s->ctx) { 437 struct apple_state *c = (struct apple_state *)s->ctx; 438 int firm = 0, middle = 0; 439 int16_t v; 440 441 data++, len--; /* Chomp report_id */ 442 443 if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len)) 444 goto check_middle_button; 445 446 /* 447 * The basics. When touches are detected, no normal mouse 448 * reports are sent. Collect clicks and dx/dy 449 */ 450 if (data[2] & 1) 451 mouse_butt |= 0x1; 452 if (data[2] & 2) 453 mouse_butt |= 0x4; 454 455 if ((v = data[0] + ((data[2] & 0x0C) << 6))) 456 mouse_x += ((int16_t)(v << 6)) >> 6, mevents++; 457 if ((v = data[1] + ((data[2] & 0x30) << 4))) 458 mouse_y += ((int16_t)(v << 6)) >> 6, mevents++; 459 460 /* 461 * The hard part: accumulate touch events and emulate middle 462 */ 463 for (data += AMM_BASIC_BLOCK, len -= AMM_BASIC_BLOCK; 464 len >= AMM_FINGER_BLOCK; 465 data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) { 466 int x, y, z, force, id; 467 468 v = data[0] | ((data[1] & 0xf) << 8); 469 x = ((int16_t)(v << 4)) >> 4; 470 471 v = (data[1] >> 4) | (data[2] << 4); 472 y = -(((int16_t)(v << 4)) >> 4); 473 474 force = data[5] & 0x3f; 475 id = 0xf & ((data[5] >> 6) | (data[6] << 2)); 476 z = (y - c->y[id]) / AMM_WHEEL_SPEED; 477 478 switch ((data[7] >> 4) & 0x7) { /* Phase */ 479 case 3: /* First touch */ 480 c->y[id] = y; 481 break; 482 case 4: /* Touch dragged */ 483 if (z) { 484 mouse_z += z; 485 c->y[id] += z * AMM_WHEEL_SPEED; 486 mevents++; 487 } 488 break; 489 default: 490 break; 491 } 492 /* Count firm touches vs. firm+middle touches */ 493 if (force >= 8 && ++firm && x > -350 && x < 350) 494 ++middle; 495 } 496 497 /* 498 * If a new click is registered by mouse and there are firm 499 * touches which are all in center, make it a middle click 500 */ 501 if (mouse_butt && !c->button_state && firm && middle == firm) 502 mouse_butt = 0x2; 503 504 /* 505 * If we're still clicking and have converted the click 506 * to a middle click, keep it middle clicking 507 */ 508 check_middle_button: 509 if (mouse_butt && c->button_state == 0x2) 510 mouse_butt = 0x2; 511 512 if (mouse_butt != c->button_state) 513 c->button_state = mouse_butt, mevents++; 514 } 515 516 /* 517 * XXX FIXME Feed keyboard events into kernel. 518 * The code below works, bit host also needs to track 519 * and handle repeat. 520 * 521 * Key repeat currently works in X, but not in console. 522 */ 523 524 if (kevents > 0) { 525 if (hid_device->keyboard) { 526 assert(s->vkbd != -1); 527 kbd_process_keys(s); 528 } else 529 syslog(LOG_ERR, "Keyboard events received from " \ 530 "non-keyboard device %s. Please report", 531 bt_ntoa(&s->bdaddr, NULL)); 532 } 533 534 /* 535 * XXX FIXME Feed mouse events into kernel. 536 * The code block below works, but it is not good enough. 537 * Need to track double-clicks etc. 538 * 539 * Double click currently works in X, but not in console. 540 */ 541 542 if (mevents > 0) { 543 struct mouse_info mi; 544 545 memset(&mi, 0, sizeof(mi)); 546 mi.operation = MOUSE_ACTION; 547 mi.u.data.buttons = mouse_butt; 548 549 /* translate T-axis into button presses */ 550 if (mouse_t != 0) { 551 mi.u.data.buttons |= 1 << (mouse_t > 0 ? 6 : 5); 552 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 553 syslog(LOG_ERR, "Could not process mouse " \ 554 "events from %s. %s (%d)", 555 bt_ntoa(&s->bdaddr, NULL), 556 strerror(errno), errno); 557 } 558 559 mi.u.data.x = mouse_x; 560 mi.u.data.y = mouse_y; 561 mi.u.data.z = mouse_z; 562 mi.u.data.buttons = mouse_butt; 563 564 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 565 syslog(LOG_ERR, "Could not process mouse events from " \ 566 "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), 567 strerror(errno), errno); 568 569 if (hid_device->mouse && s->srv->uinput && 570 uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z, 571 mouse_t, mouse_butt, s->obutt) < 0) 572 syslog(LOG_ERR, "Could not process mouse events from " \ 573 "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), 574 strerror(errno), errno); 575 s->obutt = mouse_butt; 576 } 577 578 return (0); 579 } 580