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