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_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_butt = mevents = kevents = 0; 200 201 for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1); 202 hid_get_item(d, &h) > 0; ) { 203 if ((h.flags & HIO_CONST) || (h.report_ID != report_id) || 204 (h.kind != hid_input)) 205 continue; 206 207 page = HID_PAGE(h.usage); 208 val = hid_get_data(data, &h); 209 210 /* 211 * When the input field is an array and the usage is specified 212 * with a range instead of an ID, we have to derive the actual 213 * usage by using the item value as an index in the usage range 214 * list. 215 */ 216 if ((h.flags & HIO_VARIABLE)) { 217 usage = HID_USAGE(h.usage); 218 } else { 219 const uint32_t usage_offset = val - h.logical_minimum; 220 usage = HID_USAGE(h.usage_minimum + usage_offset); 221 } 222 223 switch (page) { 224 case HUP_GENERIC_DESKTOP: 225 switch (usage) { 226 case HUG_X: 227 mouse_x = val; 228 mevents ++; 229 break; 230 231 case HUG_Y: 232 mouse_y = val; 233 mevents ++; 234 break; 235 236 case HUG_WHEEL: 237 mouse_z = -val; 238 mevents ++; 239 break; 240 241 case HUG_SYSTEM_SLEEP: 242 if (val) 243 syslog(LOG_NOTICE, "Sleep button pressed"); 244 break; 245 } 246 break; 247 248 case HUP_KEYBOARD: 249 kevents ++; 250 251 if (h.flags & HIO_VARIABLE) { 252 if (val && usage < kbd_maxkey()) 253 bit_set(s->keys1, usage); 254 } else { 255 if (val && val < kbd_maxkey()) 256 bit_set(s->keys1, val); 257 258 for (i = 1; i < h.report_count; i++) { 259 h.pos += h.report_size; 260 val = hid_get_data(data, &h); 261 if (val && val < kbd_maxkey()) 262 bit_set(s->keys1, val); 263 } 264 } 265 break; 266 267 case HUP_BUTTON: 268 if (usage != 0) { 269 if (usage == 2) 270 usage = 3; 271 else if (usage == 3) 272 usage = 2; 273 274 mouse_butt |= (val << (usage - 1)); 275 mevents ++; 276 } 277 break; 278 279 case HUP_CONSUMER: 280 if (!val) 281 break; 282 283 switch (usage) { 284 case HUC_AC_PAN: 285 /* Horizontal scroll */ 286 if (val < 0) 287 mouse_butt |= (1 << 5); 288 else 289 mouse_butt |= (1 << 6); 290 291 mevents ++; 292 val = 0; 293 break; 294 295 case 0xb5: /* Scan Next Track */ 296 val = 0x19; 297 break; 298 299 case 0xb6: /* Scan Previous Track */ 300 val = 0x10; 301 break; 302 303 case 0xb7: /* Stop */ 304 val = 0x24; 305 break; 306 307 case 0xcd: /* Play/Pause */ 308 val = 0x22; 309 break; 310 311 case 0xe2: /* Mute */ 312 val = 0x20; 313 break; 314 315 case 0xe9: /* Volume Up */ 316 val = 0x30; 317 break; 318 319 case 0xea: /* Volume Down */ 320 val = 0x2E; 321 break; 322 323 case 0x183: /* Media Select */ 324 val = 0x6D; 325 break; 326 327 case 0x018a: /* Mail */ 328 val = 0x6C; 329 break; 330 331 case 0x192: /* Calculator */ 332 val = 0x21; 333 break; 334 335 case 0x194: /* My Computer */ 336 val = 0x6B; 337 break; 338 339 case 0x221: /* WWW Search */ 340 val = 0x65; 341 break; 342 343 case 0x223: /* WWW Home */ 344 val = 0x32; 345 break; 346 347 case 0x224: /* WWW Back */ 348 val = 0x6A; 349 break; 350 351 case 0x225: /* WWW Forward */ 352 val = 0x69; 353 break; 354 355 case 0x226: /* WWW Stop */ 356 val = 0x68; 357 break; 358 359 case 0x227: /* WWW Refresh */ 360 val = 0x67; 361 break; 362 363 case 0x22a: /* WWW Favorites */ 364 val = 0x66; 365 break; 366 367 default: 368 val = 0; 369 break; 370 } 371 372 /* XXX FIXME - UGLY HACK */ 373 if (val != 0) { 374 if (hid_device->keyboard) { 375 int32_t buf[4] = { 0xe0, val, 376 0xe0, val|0x80 }; 377 378 assert(s->vkbd != -1); 379 write(s->vkbd, buf, sizeof(buf)); 380 } else 381 syslog(LOG_ERR, "Keyboard events " \ 382 "received from non-keyboard " \ 383 "device %s. Please report", 384 bt_ntoa(&s->bdaddr, NULL)); 385 } 386 break; 387 388 case HUP_MICROSOFT: 389 switch (usage) { 390 case 0xfe01: 391 if (!hid_device->battery_power) 392 break; 393 394 switch (val) { 395 case 1: 396 syslog(LOG_INFO, "Battery is OK on %s", 397 bt_ntoa(&s->bdaddr, NULL)); 398 break; 399 400 case 2: 401 syslog(LOG_NOTICE, "Low battery on %s", 402 bt_ntoa(&s->bdaddr, NULL)); 403 break; 404 405 case 3: 406 syslog(LOG_WARNING, "Very low battery "\ 407 "on %s", 408 bt_ntoa(&s->bdaddr, NULL)); 409 break; 410 } 411 break; 412 } 413 break; 414 } 415 } 416 hid_end_parse(d); 417 418 /* 419 * Apple adheres to no standards and sends reports it does 420 * not introduce in its hid descriptor for its magic mouse. 421 * Handle those reports here. 422 */ 423 if (MAGIC_MOUSE(hid_device) && s->ctx) { 424 struct apple_state *c = (struct apple_state *)s->ctx; 425 int firm = 0, middle = 0; 426 int16_t v; 427 428 data++, len--; /* Chomp report_id */ 429 430 if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len)) 431 goto check_middle_button; 432 433 /* 434 * The basics. When touches are detected, no normal mouse 435 * reports are sent. Collect clicks and dx/dy 436 */ 437 if (data[2] & 1) 438 mouse_butt |= 0x1; 439 if (data[2] & 2) 440 mouse_butt |= 0x4; 441 442 if ((v = data[0] + ((data[2] & 0x0C) << 6))) 443 mouse_x += ((int16_t)(v << 6)) >> 6, mevents++; 444 if ((v = data[1] + ((data[2] & 0x30) << 4))) 445 mouse_y += ((int16_t)(v << 6)) >> 6, mevents++; 446 447 /* 448 * The hard part: accumulate touch events and emulate middle 449 */ 450 for (data += AMM_BASIC_BLOCK, len -= AMM_BASIC_BLOCK; 451 len >= AMM_FINGER_BLOCK; 452 data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) { 453 int x, y, z, force, id; 454 455 v = data[0] | ((data[1] & 0xf) << 8); 456 x = ((int16_t)(v << 4)) >> 4; 457 458 v = (data[1] >> 4) | (data[2] << 4); 459 y = -(((int16_t)(v << 4)) >> 4); 460 461 force = data[5] & 0x3f; 462 id = 0xf & ((data[5] >> 6) | (data[6] << 2)); 463 z = (y - c->y[id]) / AMM_WHEEL_SPEED; 464 465 switch ((data[7] >> 4) & 0x7) { /* Phase */ 466 case 3: /* First touch */ 467 c->y[id] = y; 468 break; 469 case 4: /* Touch dragged */ 470 if (z) { 471 mouse_z += z; 472 c->y[id] += z * AMM_WHEEL_SPEED; 473 mevents++; 474 } 475 break; 476 default: 477 break; 478 } 479 /* Count firm touches vs. firm+middle touches */ 480 if (force >= 8 && ++firm && x > -350 && x < 350) 481 ++middle; 482 } 483 484 /* 485 * If a new click is registered by mouse and there are firm 486 * touches which are all in center, make it a middle click 487 */ 488 if (mouse_butt && !c->button_state && firm && middle == firm) 489 mouse_butt = 0x2; 490 491 /* 492 * If we're still clicking and have converted the click 493 * to a middle click, keep it middle clicking 494 */ 495 check_middle_button: 496 if (mouse_butt && c->button_state == 0x2) 497 mouse_butt = 0x2; 498 499 if (mouse_butt != c->button_state) 500 c->button_state = mouse_butt, mevents++; 501 } 502 503 /* 504 * XXX FIXME Feed keyboard events into kernel. 505 * The code below works, bit host also needs to track 506 * and handle repeat. 507 * 508 * Key repeat currently works in X, but not in console. 509 */ 510 511 if (kevents > 0) { 512 if (hid_device->keyboard) { 513 assert(s->vkbd != -1); 514 kbd_process_keys(s); 515 } else 516 syslog(LOG_ERR, "Keyboard events received from " \ 517 "non-keyboard device %s. Please report", 518 bt_ntoa(&s->bdaddr, NULL)); 519 } 520 521 /* 522 * XXX FIXME Feed mouse events into kernel. 523 * The code block below works, but it is not good enough. 524 * Need to track double-clicks etc. 525 * 526 * Double click currently works in X, but not in console. 527 */ 528 529 if (mevents > 0) { 530 struct mouse_info mi; 531 532 mi.operation = MOUSE_ACTION; 533 mi.u.data.x = mouse_x; 534 mi.u.data.y = mouse_y; 535 mi.u.data.z = mouse_z; 536 mi.u.data.buttons = mouse_butt; 537 538 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 539 syslog(LOG_ERR, "Could not process mouse events from " \ 540 "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), 541 strerror(errno), errno); 542 } 543 544 return (0); 545 } 546