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 <string.h> 45 #include <syslog.h> 46 #include <unistd.h> 47 #include <usbhid.h> 48 #include "bthid_config.h" 49 #include "bthidd.h" 50 #include "kbd.h" 51 52 /* 53 * Process data from control channel 54 */ 55 56 int32_t 57 hid_control(bthid_session_p s, uint8_t *data, int32_t len) 58 { 59 assert(s != NULL); 60 assert(data != NULL); 61 assert(len > 0); 62 63 switch (data[0] >> 4) { 64 case 0: /* Handshake (response to command) */ 65 if (data[0] & 0xf) 66 syslog(LOG_ERR, "Got handshake message with error " \ 67 "response 0x%x from %s", 68 data[0], bt_ntoa(&s->bdaddr, NULL)); 69 break; 70 71 case 1: /* HID Control */ 72 switch (data[0] & 0xf) { 73 case 0: /* NOP */ 74 break; 75 76 case 1: /* Hard reset */ 77 case 2: /* Soft reset */ 78 syslog(LOG_WARNING, "Device %s requested %s reset", 79 bt_ntoa(&s->bdaddr, NULL), 80 ((data[0] & 0xf) == 1)? "hard" : "soft"); 81 break; 82 83 case 3: /* Suspend */ 84 syslog(LOG_NOTICE, "Device %s requested Suspend", 85 bt_ntoa(&s->bdaddr, NULL)); 86 break; 87 88 case 4: /* Exit suspend */ 89 syslog(LOG_NOTICE, "Device %s requested Exit Suspend", 90 bt_ntoa(&s->bdaddr, NULL)); 91 break; 92 93 case 5: /* Virtual cable unplug */ 94 syslog(LOG_NOTICE, "Device %s unplugged virtual cable", 95 bt_ntoa(&s->bdaddr, NULL)); 96 session_close(s); 97 break; 98 99 default: 100 syslog(LOG_WARNING, "Device %s sent unknown " \ 101 "HID_Control message 0x%x", 102 bt_ntoa(&s->bdaddr, NULL), data[0]); 103 break; 104 } 105 break; 106 107 default: 108 syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \ 109 "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL)); 110 break; 111 } 112 113 return (0); 114 } 115 116 /* 117 * Process data from the interrupt channel 118 */ 119 120 int32_t 121 hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len) 122 { 123 hid_device_p hid_device; 124 hid_data_t d; 125 hid_item_t h; 126 int32_t report_id, usage, page, val, 127 mouse_x, mouse_y, mouse_z, mouse_butt, 128 mevents, kevents, i; 129 130 assert(s != NULL); 131 assert(s->srv != NULL); 132 assert(data != NULL); 133 134 if (len < 3) { 135 syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \ 136 "channel from %s", len, bt_ntoa(&s->bdaddr, NULL)); 137 return (-1); 138 } 139 140 if (data[0] != 0xa1) { 141 syslog(LOG_ERR, "Got unexpected message 0x%x on " \ 142 "Interrupt channel from %s", 143 data[0], bt_ntoa(&s->bdaddr, NULL)); 144 return (-1); 145 } 146 147 report_id = data[1]; 148 data ++; 149 len --; 150 151 hid_device = get_hid_device(&s->bdaddr); 152 assert(hid_device != NULL); 153 154 mouse_x = mouse_y = mouse_z = mouse_butt = mevents = kevents = 0; 155 156 for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1); 157 hid_get_item(d, &h) > 0; ) { 158 if ((h.flags & HIO_CONST) || (h.report_ID != report_id) || 159 (h.kind != hid_input)) 160 continue; 161 162 page = HID_PAGE(h.usage); 163 val = hid_get_data(data, &h); 164 165 /* 166 * When the input field is an array and the usage is specified 167 * with a range instead of an ID, we have to derive the actual 168 * usage by using the item value as an index in the usage range 169 * list. 170 */ 171 if ((h.flags & HIO_VARIABLE)) { 172 usage = HID_USAGE(h.usage); 173 } else { 174 const uint32_t usage_offset = val - h.logical_minimum; 175 usage = HID_USAGE(h.usage_minimum + usage_offset); 176 } 177 178 switch (page) { 179 case HUP_GENERIC_DESKTOP: 180 switch (usage) { 181 case HUG_X: 182 mouse_x = val; 183 mevents ++; 184 break; 185 186 case HUG_Y: 187 mouse_y = val; 188 mevents ++; 189 break; 190 191 case HUG_WHEEL: 192 mouse_z = -val; 193 mevents ++; 194 break; 195 196 case HUG_SYSTEM_SLEEP: 197 if (val) 198 syslog(LOG_NOTICE, "Sleep button pressed"); 199 break; 200 } 201 break; 202 203 case HUP_KEYBOARD: 204 kevents ++; 205 206 if (h.flags & HIO_VARIABLE) { 207 if (val && usage < kbd_maxkey()) 208 bit_set(s->keys1, usage); 209 } else { 210 if (val && val < kbd_maxkey()) 211 bit_set(s->keys1, val); 212 213 for (i = 1; i < h.report_count; i++) { 214 h.pos += h.report_size; 215 val = hid_get_data(data, &h); 216 if (val && val < kbd_maxkey()) 217 bit_set(s->keys1, val); 218 } 219 } 220 break; 221 222 case HUP_BUTTON: 223 if (usage != 0) { 224 if (usage == 2) 225 usage = 3; 226 else if (usage == 3) 227 usage = 2; 228 229 mouse_butt |= (val << (usage - 1)); 230 mevents ++; 231 } 232 break; 233 234 case HUP_CONSUMER: 235 if (!val) 236 break; 237 238 switch (usage) { 239 case HUC_AC_PAN: 240 /* Horizontal scroll */ 241 if (val < 0) 242 mouse_butt |= (1 << 5); 243 else 244 mouse_butt |= (1 << 6); 245 246 mevents ++; 247 val = 0; 248 break; 249 250 case 0xb5: /* Scan Next Track */ 251 val = 0x19; 252 break; 253 254 case 0xb6: /* Scan Previous Track */ 255 val = 0x10; 256 break; 257 258 case 0xb7: /* Stop */ 259 val = 0x24; 260 break; 261 262 case 0xcd: /* Play/Pause */ 263 val = 0x22; 264 break; 265 266 case 0xe2: /* Mute */ 267 val = 0x20; 268 break; 269 270 case 0xe9: /* Volume Up */ 271 val = 0x30; 272 break; 273 274 case 0xea: /* Volume Down */ 275 val = 0x2E; 276 break; 277 278 case 0x183: /* Media Select */ 279 val = 0x6D; 280 break; 281 282 case 0x018a: /* Mail */ 283 val = 0x6C; 284 break; 285 286 case 0x192: /* Calculator */ 287 val = 0x21; 288 break; 289 290 case 0x194: /* My Computer */ 291 val = 0x6B; 292 break; 293 294 case 0x221: /* WWW Search */ 295 val = 0x65; 296 break; 297 298 case 0x223: /* WWW Home */ 299 val = 0x32; 300 break; 301 302 case 0x224: /* WWW Back */ 303 val = 0x6A; 304 break; 305 306 case 0x225: /* WWW Forward */ 307 val = 0x69; 308 break; 309 310 case 0x226: /* WWW Stop */ 311 val = 0x68; 312 break; 313 314 case 0x227: /* WWW Refresh */ 315 val = 0x67; 316 break; 317 318 case 0x22a: /* WWW Favorites */ 319 val = 0x66; 320 break; 321 322 default: 323 val = 0; 324 break; 325 } 326 327 /* XXX FIXME - UGLY HACK */ 328 if (val != 0) { 329 if (hid_device->keyboard) { 330 int32_t buf[4] = { 0xe0, val, 331 0xe0, val|0x80 }; 332 333 assert(s->vkbd != -1); 334 write(s->vkbd, buf, sizeof(buf)); 335 } else 336 syslog(LOG_ERR, "Keyboard events " \ 337 "received from non-keyboard " \ 338 "device %s. Please report", 339 bt_ntoa(&s->bdaddr, NULL)); 340 } 341 break; 342 343 case HUP_MICROSOFT: 344 switch (usage) { 345 case 0xfe01: 346 if (!hid_device->battery_power) 347 break; 348 349 switch (val) { 350 case 1: 351 syslog(LOG_INFO, "Battery is OK on %s", 352 bt_ntoa(&s->bdaddr, NULL)); 353 break; 354 355 case 2: 356 syslog(LOG_NOTICE, "Low battery on %s", 357 bt_ntoa(&s->bdaddr, NULL)); 358 break; 359 360 case 3: 361 syslog(LOG_WARNING, "Very low battery "\ 362 "on %s", 363 bt_ntoa(&s->bdaddr, NULL)); 364 break; 365 } 366 break; 367 } 368 break; 369 } 370 } 371 hid_end_parse(d); 372 373 /* 374 * XXX FIXME Feed keyboard events into kernel. 375 * The code below works, bit host also needs to track 376 * and handle repeat. 377 * 378 * Key repeat currently works in X, but not in console. 379 */ 380 381 if (kevents > 0) { 382 if (hid_device->keyboard) { 383 assert(s->vkbd != -1); 384 kbd_process_keys(s); 385 } else 386 syslog(LOG_ERR, "Keyboard events received from " \ 387 "non-keyboard device %s. Please report", 388 bt_ntoa(&s->bdaddr, NULL)); 389 } 390 391 /* 392 * XXX FIXME Feed mouse events into kernel. 393 * The code block below works, but it is not good enough. 394 * Need to track double-clicks etc. 395 * 396 * Double click currently works in X, but not in console. 397 */ 398 399 if (mevents > 0) { 400 struct mouse_info mi; 401 402 mi.operation = MOUSE_ACTION; 403 mi.u.data.x = mouse_x; 404 mi.u.data.y = mouse_y; 405 mi.u.data.z = mouse_z; 406 mi.u.data.buttons = mouse_butt; 407 408 if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0) 409 syslog(LOG_ERR, "Could not process mouse events from " \ 410 "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), 411 strerror(errno), errno); 412 } 413 414 return (0); 415 } 416