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