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