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 "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 (!val) 284 break; 285 286 switch (usage) { 287 case HUC_AC_PAN: 288 /* Horizontal scroll */ 289 mouse_t = val; 290 mevents ++; 291 val = 0; 292 break; 293 294 case 0xb5: /* Scan Next Track */ 295 val = 0x19; 296 break; 297 298 case 0xb6: /* Scan Previous Track */ 299 val = 0x10; 300 break; 301 302 case 0xb7: /* Stop */ 303 val = 0x24; 304 break; 305 306 case 0xcd: /* Play/Pause */ 307 val = 0x22; 308 break; 309 310 case 0xe2: /* Mute */ 311 val = 0x20; 312 break; 313 314 case 0xe9: /* Volume Up */ 315 val = 0x30; 316 break; 317 318 case 0xea: /* Volume Down */ 319 val = 0x2E; 320 break; 321 322 case 0x183: /* Media Select */ 323 val = 0x6D; 324 break; 325 326 case 0x018a: /* Mail */ 327 val = 0x6C; 328 break; 329 330 case 0x192: /* Calculator */ 331 val = 0x21; 332 break; 333 334 case 0x194: /* My Computer */ 335 val = 0x6B; 336 break; 337 338 case 0x221: /* WWW Search */ 339 val = 0x65; 340 break; 341 342 case 0x223: /* WWW Home */ 343 val = 0x32; 344 break; 345 346 case 0x224: /* WWW Back */ 347 val = 0x6A; 348 break; 349 350 case 0x225: /* WWW Forward */ 351 val = 0x69; 352 break; 353 354 case 0x226: /* WWW Stop */ 355 val = 0x68; 356 break; 357 358 case 0x227: /* WWW Refresh */ 359 val = 0x67; 360 break; 361 362 case 0x22a: /* WWW Favorites */ 363 val = 0x66; 364 break; 365 366 default: 367 val = 0; 368 break; 369 } 370 371 /* XXX FIXME - UGLY HACK */ 372 if (val != 0) { 373 if (hid_device->keyboard) { 374 int32_t buf[4] = { 0xe0, val, 375 0xe0, val|0x80 }; 376 377 assert(s->vkbd != -1); 378 write(s->vkbd, buf, sizeof(buf)); 379 } else 380 syslog(LOG_ERR, "Keyboard events " \ 381 "received from non-keyboard " \ 382 "device %s. Please report", 383 bt_ntoa(&s->bdaddr, NULL)); 384 } 385 break; 386 387 case HUP_MICROSOFT: 388 switch (usage) { 389 case 0xfe01: 390 if (!hid_device->battery_power) 391 break; 392 393 switch (val) { 394 case 1: 395 syslog(LOG_INFO, "Battery is OK on %s", 396 bt_ntoa(&s->bdaddr, NULL)); 397 break; 398 399 case 2: 400 syslog(LOG_NOTICE, "Low battery on %s", 401 bt_ntoa(&s->bdaddr, NULL)); 402 break; 403 404 case 3: 405 syslog(LOG_WARNING, "Very low battery "\ 406 "on %s", 407 bt_ntoa(&s->bdaddr, NULL)); 408 break; 409 } 410 break; 411 } 412 break; 413 } 414 } 415 hid_end_parse(d); 416 417 /* 418 * Apple adheres to no standards and sends reports it does 419 * not introduce in its hid descriptor for its magic mouse. 420 * Handle those reports here. 421 */ 422 if (MAGIC_MOUSE(hid_device) && s->ctx) { 423 struct apple_state *c = (struct apple_state *)s->ctx; 424 int firm = 0, middle = 0; 425 int16_t v; 426 427 data++, len--; /* Chomp report_id */ 428 429 if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len)) 430 goto check_middle_button; 431 432 /* 433 * The basics. When touches are detected, no normal mouse 434 * reports are sent. Collect clicks and dx/dy 435 */ 436 if (data[2] & 1) 437 mouse_butt |= 0x1; 438 if (data[2] & 2) 439 mouse_butt |= 0x4; 440 441 if ((v = data[0] + ((data[2] & 0x0C) << 6))) 442 mouse_x += ((int16_t)(v << 6)) >> 6, mevents++; 443 if ((v = data[1] + ((data[2] & 0x30) << 4))) 444 mouse_y += ((int16_t)(v << 6)) >> 6, mevents++; 445 446 /* 447 * The hard part: accumulate touch events and emulate middle 448 */ 449 for (data += AMM_BASIC_BLOCK, len -= AMM_BASIC_BLOCK; 450 len >= AMM_FINGER_BLOCK; 451 data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) { 452 int x, y, z, force, id; 453 454 v = data[0] | ((data[1] & 0xf) << 8); 455 x = ((int16_t)(v << 4)) >> 4; 456 457 v = (data[1] >> 4) | (data[2] << 4); 458 y = -(((int16_t)(v << 4)) >> 4); 459 460 force = data[5] & 0x3f; 461 id = 0xf & ((data[5] >> 6) | (data[6] << 2)); 462 z = (y - c->y[id]) / AMM_WHEEL_SPEED; 463 464 switch ((data[7] >> 4) & 0x7) { /* Phase */ 465 case 3: /* First touch */ 466 c->y[id] = y; 467 break; 468 case 4: /* Touch dragged */ 469 if (z) { 470 mouse_z += z; 471 c->y[id] += z * AMM_WHEEL_SPEED; 472 mevents++; 473 } 474 break; 475 default: 476 break; 477 } 478 /* Count firm touches vs. firm+middle touches */ 479 if (force >= 8 && ++firm && x > -350 && x < 350) 480 ++middle; 481 } 482 483 /* 484 * If a new click is registered by mouse and there are firm 485 * touches which are all in center, make it a middle click 486 */ 487 if (mouse_butt && !c->button_state && firm && middle == firm) 488 mouse_butt = 0x2; 489 490 /* 491 * If we're still clicking and have converted the click 492 * to a middle click, keep it middle clicking 493 */ 494 check_middle_button: 495 if (mouse_butt && c->button_state == 0x2) 496 mouse_butt = 0x2; 497 498 if (mouse_butt != c->button_state) 499 c->button_state = mouse_butt, mevents++; 500 } 501 502 /* 503 * XXX FIXME Feed keyboard events into kernel. 504 * The code below works, bit host also needs to track 505 * and handle repeat. 506 * 507 * Key repeat currently works in X, but not in console. 508 */ 509 510 if (kevents > 0) { 511 if (hid_device->keyboard) { 512 assert(s->vkbd != -1); 513 kbd_process_keys(s); 514 } else 515 syslog(LOG_ERR, "Keyboard events received from " \ 516 "non-keyboard device %s. Please report", 517 bt_ntoa(&s->bdaddr, NULL)); 518 } 519 520 /* 521 * XXX FIXME Feed mouse events into kernel. 522 * The code block below works, but it is not good enough. 523 * Need to track double-clicks etc. 524 * 525 * Double click currently works in X, but not in console. 526 */ 527 528 if (mevents > 0) { 529 struct mouse_info mi; 530 531 memset(&mi, 0, sizeof(mi)); 532 mi.operation = MOUSE_ACTION; 533 mi.u.data.buttons = mouse_butt; 534 535 /* translate T-axis into button presses */ 536 if (mouse_t != 0) { 537 mi.u.data.buttons |= 1 << (mouse_t > 0 ? 6 : 5); 538 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 539 syslog(LOG_ERR, "Could not process mouse " \ 540 "events from %s. %s (%d)", 541 bt_ntoa(&s->bdaddr, NULL), 542 strerror(errno), errno); 543 } 544 545 mi.u.data.x = mouse_x; 546 mi.u.data.y = mouse_y; 547 mi.u.data.z = mouse_z; 548 mi.u.data.buttons = mouse_butt; 549 550 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 551 syslog(LOG_ERR, "Could not process mouse events from " \ 552 "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), 553 strerror(errno), errno); 554 } 555 556 return (0); 557 } 558