xref: /freebsd/usr.sbin/bluetooth/bthidd/btuinput.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2015-2017 Vladimir Kondratyev <wulf@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/ioctl.h>
31 #include <sys/kbio.h>
32 #include <sys/sysctl.h>
33 
34 #include <dev/evdev/input.h>
35 #include <dev/evdev/uinput.h>
36 #include <dev/usb/usb.h>
37 #include <dev/usb/usbhid.h>
38 
39 #include <assert.h>
40 #define L2CAP_SOCKET_CHECKED
41 #include <bluetooth.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <time.h>
48 #include <unistd.h>
49 #include <usbhid.h>
50 
51 #include "bthid_config.h"
52 #include "bthidd.h"
53 #include "btuinput.h"
54 
55 static int16_t const mbuttons[8] = {
56 	BTN_LEFT,
57 	BTN_MIDDLE,
58 	BTN_RIGHT,
59 	BTN_SIDE,
60 	BTN_EXTRA,
61 	BTN_FORWARD,
62 	BTN_BACK,
63 	BTN_TASK
64 };
65 
66 static uint16_t const led_codes[3] = {
67 	LED_CAPSL,	/* CLKED */
68 	LED_NUML,	/* NLKED */
69 	LED_SCROLLL,	/* SLKED */
70 };
71 
72 #define	NONE	KEY_RESERVED
73 
74 static uint16_t const keymap[0x100] = {
75 	/* 0x00 - 0x27 */
76 	NONE,	NONE,	NONE,	NONE,	KEY_A,	KEY_B,	KEY_C,	KEY_D,
77 	KEY_E,	KEY_F,	KEY_G,	KEY_H,	KEY_I,	KEY_J,	KEY_K,	KEY_L,
78 	KEY_M,	KEY_N,	KEY_O,	KEY_P,	KEY_Q,	KEY_R,	KEY_S,	KEY_T,
79 	KEY_U,	KEY_V,	KEY_W,	KEY_X,	KEY_Y,	KEY_Z,	KEY_1,	KEY_2,
80 	KEY_3,	KEY_4,	KEY_5,	KEY_6,	KEY_7,	KEY_8,	KEY_9,	KEY_0,
81 	/* 0x28 - 0x3f */
82 	KEY_ENTER,	KEY_ESC,	KEY_BACKSPACE,	KEY_TAB,
83 	KEY_SPACE,	KEY_MINUS,	KEY_EQUAL,	KEY_LEFTBRACE,
84 	KEY_RIGHTBRACE,	KEY_BACKSLASH,	KEY_BACKSLASH,	KEY_SEMICOLON,
85 	KEY_APOSTROPHE,	KEY_GRAVE,	KEY_COMMA,	KEY_DOT,
86 	KEY_SLASH,	KEY_CAPSLOCK,	KEY_F1,		KEY_F2,
87 	KEY_F3,		KEY_F4,		KEY_F5,		KEY_F6,
88 	/* 0x40 - 0x5f */
89 	KEY_F7,		KEY_F8,		KEY_F9,		KEY_F10,
90 	KEY_F11,	KEY_F12,	KEY_SYSRQ,	KEY_SCROLLLOCK,
91 	KEY_PAUSE,	KEY_INSERT,	KEY_HOME,	KEY_PAGEUP,
92 	KEY_DELETE,	KEY_END,	KEY_PAGEDOWN,	KEY_RIGHT,
93 	KEY_LEFT,	KEY_DOWN,	KEY_UP,		KEY_NUMLOCK,
94 	KEY_KPSLASH,	KEY_KPASTERISK,	KEY_KPMINUS,	KEY_KPPLUS,
95 	KEY_KPENTER,	KEY_KP1,	KEY_KP2,	KEY_KP3,
96 	KEY_KP4,	KEY_KP5,	KEY_KP6,	KEY_KP7,
97 	/* 0x60 - 0x7f */
98 	KEY_KP8,	KEY_KP9,	KEY_KP0,	KEY_KPDOT,
99 	KEY_102ND,	KEY_COMPOSE,	KEY_POWER,	KEY_KPEQUAL,
100 	KEY_F13,	KEY_F14,	KEY_F15,	KEY_F16,
101 	KEY_F17,	KEY_F18,	KEY_F19,	KEY_F20,
102 	KEY_F21,	KEY_F22,	KEY_F23,	KEY_F24,
103 	KEY_OPEN,	KEY_HELP,	KEY_PROPS,	KEY_FRONT,
104 	KEY_STOP,	KEY_AGAIN,	KEY_UNDO,	KEY_CUT,
105 	KEY_COPY,	KEY_PASTE,	KEY_FIND,	KEY_MUTE,
106 	/* 0x80 - 0x9f */
107 	KEY_VOLUMEUP,	KEY_VOLUMEDOWN,	NONE,		NONE,
108 	NONE,		KEY_KPCOMMA,	NONE,		KEY_RO,
109 	KEY_KATAKANAHIRAGANA,	KEY_YEN,KEY_HENKAN,	KEY_MUHENKAN,
110 	KEY_KPJPCOMMA,	NONE,		NONE,		NONE,
111 	KEY_HANGEUL,	KEY_HANJA,	KEY_KATAKANA,	KEY_HIRAGANA,
112 	KEY_ZENKAKUHANKAKU,	NONE,	NONE,		NONE,
113 	NONE,		NONE,		NONE,		NONE,
114 	NONE,		NONE,		NONE,		NONE,
115 	/* 0xa0 - 0xbf */
116 	NONE,		NONE,		NONE,		NONE,
117 	NONE,		NONE,		NONE,		NONE,
118 	NONE,		NONE,		NONE,		NONE,
119 	NONE,		NONE,		NONE,		NONE,
120 	NONE,		NONE,		NONE,		NONE,
121 	NONE,		NONE,		NONE,		NONE,
122 	NONE,		NONE,		NONE,		NONE,
123 	NONE,		NONE,		NONE,		NONE,
124 	/* 0xc0 - 0xdf */
125 	NONE,		NONE,           NONE,		NONE,
126 	NONE,		NONE,           NONE,		NONE,
127 	NONE,		NONE,           NONE,		NONE,
128 	NONE,		NONE,           NONE,		NONE,
129 	NONE,		NONE,           NONE,		NONE,
130 	NONE,		NONE,           NONE,		NONE,
131 	NONE,		NONE,           NONE,		NONE,
132 	NONE,		NONE,           NONE,		NONE,
133 	/* 0xe0 - 0xff */
134 	KEY_LEFTCTRL,	KEY_LEFTSHIFT,	KEY_LEFTALT,	KEY_LEFTMETA,
135 	KEY_RIGHTCTRL,	KEY_RIGHTSHIFT,	KEY_RIGHTALT,	KEY_RIGHTMETA,
136 	KEY_PLAYPAUSE,	KEY_STOPCD,	KEY_PREVIOUSSONG,KEY_NEXTSONG,
137 	KEY_EJECTCD,	KEY_VOLUMEUP,	KEY_VOLUMEDOWN, KEY_MUTE,
138 	KEY_WWW,	KEY_BACK,	KEY_FORWARD,	KEY_STOP,
139 	KEY_FIND,	KEY_SCROLLUP,	KEY_SCROLLDOWN,	KEY_EDIT,
140 	KEY_SLEEP,	KEY_COFFEE,	KEY_REFRESH,	KEY_CALC,
141 	NONE,		NONE,		NONE,		NONE,
142 };
143 
144 /* Consumer page usage mapping */
145 static uint16_t const consmap[0x300] = {
146 	[0x030] = KEY_POWER,
147 	[0x031] = KEY_RESTART,
148 	[0x032] = KEY_SLEEP,
149 	[0x034] = KEY_SLEEP,
150 	[0x035] = KEY_KBDILLUMTOGGLE,
151 	[0x036] = BTN_MISC,
152 	[0x040] = KEY_MENU,
153 	[0x041] = KEY_SELECT,
154 	[0x042] = KEY_UP,
155 	[0x043] = KEY_DOWN,
156 	[0x044] = KEY_LEFT,
157 	[0x045] = KEY_RIGHT,
158 	[0x046] = KEY_ESC,
159 	[0x047] = KEY_KPPLUS,
160 	[0x048] = KEY_KPMINUS,
161 	[0x060] = KEY_INFO,
162 	[0x061] = KEY_SUBTITLE,
163 	[0x063] = KEY_VCR,
164 	[0x065] = KEY_CAMERA,
165 	[0x069] = KEY_RED,
166 	[0x06a] = KEY_GREEN,
167 	[0x06b] = KEY_BLUE,
168 	[0x06c] = KEY_YELLOW,
169 	[0x06d] = KEY_ZOOM,
170 	[0x06f] = KEY_BRIGHTNESSUP,
171 	[0x070] = KEY_BRIGHTNESSDOWN,
172 	[0x072] = KEY_BRIGHTNESS_TOGGLE,
173 	[0x073] = KEY_BRIGHTNESS_MIN,
174 	[0x074] = KEY_BRIGHTNESS_MAX,
175 	[0x075] = KEY_BRIGHTNESS_AUTO,
176 	[0x082] = KEY_VIDEO_NEXT,
177 	[0x083] = KEY_LAST,
178 	[0x084] = KEY_ENTER,
179 	[0x088] = KEY_PC,
180 	[0x089] = KEY_TV,
181 	[0x08a] = KEY_WWW,
182 	[0x08b] = KEY_DVD,
183 	[0x08c] = KEY_PHONE,
184 	[0x08d] = KEY_PROGRAM,
185 	[0x08e] = KEY_VIDEOPHONE,
186 	[0x08f] = KEY_GAMES,
187 	[0x090] = KEY_MEMO,
188 	[0x091] = KEY_CD,
189 	[0x092] = KEY_VCR,
190 	[0x093] = KEY_TUNER,
191 	[0x094] = KEY_EXIT,
192 	[0x095] = KEY_HELP,
193 	[0x096] = KEY_TAPE,
194 	[0x097] = KEY_TV2,
195 	[0x098] = KEY_SAT,
196 	[0x09a] = KEY_PVR,
197 	[0x09c] = KEY_CHANNELUP,
198 	[0x09d] = KEY_CHANNELDOWN,
199 	[0x0a0] = KEY_VCR2,
200 	[0x0b0] = KEY_PLAY,
201 	[0x0b1] = KEY_PAUSE,
202 	[0x0b2] = KEY_RECORD,
203 	[0x0b3] = KEY_FASTFORWARD,
204 	[0x0b4] = KEY_REWIND,
205 	[0x0b5] = KEY_NEXTSONG,
206 	[0x0b6] = KEY_PREVIOUSSONG,
207 	[0x0b7] = KEY_STOPCD,
208 	[0x0b8] = KEY_EJECTCD,
209 	[0x0bc] = KEY_MEDIA_REPEAT,
210 	[0x0b9] = KEY_SHUFFLE,
211 	[0x0bf] = KEY_SLOW,
212 	[0x0cd] = KEY_PLAYPAUSE,
213 	[0x0cf] = KEY_VOICECOMMAND,
214 	[0x0e2] = KEY_MUTE,
215 	[0x0e5] = KEY_BASSBOOST,
216 	[0x0e9] = KEY_VOLUMEUP,
217 	[0x0ea] = KEY_VOLUMEDOWN,
218 	[0x0f5] = KEY_SLOW,
219 	[0x181] = KEY_BUTTONCONFIG,
220 	[0x182] = KEY_BOOKMARKS,
221 	[0x183] = KEY_CONFIG,
222 	[0x184] = KEY_WORDPROCESSOR,
223 	[0x185] = KEY_EDITOR,
224 	[0x186] = KEY_SPREADSHEET,
225 	[0x187] = KEY_GRAPHICSEDITOR,
226 	[0x188] = KEY_PRESENTATION,
227 	[0x189] = KEY_DATABASE,
228 	[0x18a] = KEY_MAIL,
229 	[0x18b] = KEY_NEWS,
230 	[0x18c] = KEY_VOICEMAIL,
231 	[0x18d] = KEY_ADDRESSBOOK,
232 	[0x18e] = KEY_CALENDAR,
233 	[0x18f] = KEY_TASKMANAGER,
234 	[0x190] = KEY_JOURNAL,
235 	[0x191] = KEY_FINANCE,
236 	[0x192] = KEY_CALC,
237 	[0x193] = KEY_PLAYER,
238 	[0x194] = KEY_FILE,
239 	[0x196] = KEY_WWW,
240 	[0x199] = KEY_CHAT,
241 	[0x19c] = KEY_LOGOFF,
242 	[0x19e] = KEY_COFFEE,
243 	[0x19f] = KEY_CONTROLPANEL,
244 	[0x1a2] = KEY_APPSELECT,
245 	[0x1a3] = KEY_NEXT,
246 	[0x1a4] = KEY_PREVIOUS,
247 	[0x1a6] = KEY_HELP,
248 	[0x1a7] = KEY_DOCUMENTS,
249 	[0x1ab] = KEY_SPELLCHECK,
250 	[0x1ae] = KEY_KEYBOARD,
251 	[0x1b1] = KEY_SCREENSAVER,
252 	[0x1b4] = KEY_FILE,
253 	[0x1b6] = KEY_IMAGES,
254 	[0x1b7] = KEY_AUDIO,
255 	[0x1b8] = KEY_VIDEO,
256 	[0x1bc] = KEY_MESSENGER,
257 	[0x1bd] = KEY_INFO,
258 	[0x201] = KEY_NEW,
259 	[0x202] = KEY_OPEN,
260 	[0x203] = KEY_CLOSE,
261 	[0x204] = KEY_EXIT,
262 	[0x207] = KEY_SAVE,
263 	[0x208] = KEY_PRINT,
264 	[0x209] = KEY_PROPS,
265 	[0x21a] = KEY_UNDO,
266 	[0x21b] = KEY_COPY,
267 	[0x21c] = KEY_CUT,
268 	[0x21d] = KEY_PASTE,
269 	[0x21f] = KEY_FIND,
270 	[0x221] = KEY_SEARCH,
271 	[0x222] = KEY_GOTO,
272 	[0x223] = KEY_HOMEPAGE,
273 	[0x224] = KEY_BACK,
274 	[0x225] = KEY_FORWARD,
275 	[0x226] = KEY_STOP,
276 	[0x227] = KEY_REFRESH,
277 	[0x22a] = KEY_BOOKMARKS,
278 	[0x22d] = KEY_ZOOMIN,
279 	[0x22e] = KEY_ZOOMOUT,
280 	[0x22f] = KEY_ZOOMRESET,
281 	[0x233] = KEY_SCROLLUP,
282 	[0x234] = KEY_SCROLLDOWN,
283 	[0x23d] = KEY_EDIT,
284 	[0x25f] = KEY_CANCEL,
285 	[0x269] = KEY_INSERT,
286 	[0x26a] = KEY_DELETE,
287 	[0x279] = KEY_REDO,
288 	[0x289] = KEY_REPLY,
289 	[0x28b] = KEY_FORWARDMAIL,
290 	[0x28c] = KEY_SEND,
291 	[0x2c7] = KEY_KBDINPUTASSIST_PREV,
292 	[0x2c8] = KEY_KBDINPUTASSIST_NEXT,
293 	[0x2c9] = KEY_KBDINPUTASSIST_PREVGROUP,
294 	[0x2ca] = KEY_KBDINPUTASSIST_NEXTGROUP,
295 	[0x2cb] = KEY_KBDINPUTASSIST_ACCEPT,
296 	[0x2cc] = KEY_KBDINPUTASSIST_CANCEL,
297 };
298 
299 static int32_t
300 uinput_open_common(hid_device_p const p, bdaddr_p local, const uint8_t *name)
301 {
302 	struct uinput_setup	uisetup;
303 	uint8_t			phys[UINPUT_MAX_NAME_SIZE];
304 	uint8_t			uniq[UINPUT_MAX_NAME_SIZE];
305 	int32_t			fd;
306 
307 	/* Take local and remote bdaddr */
308 	bt_ntoa(local, phys);
309 	bt_ntoa(&p->bdaddr, uniq);
310 
311 	/* Take device name from bthidd.conf. Fallback to generic name. */
312 	if (p->name != NULL)
313 		name = p->name;
314 
315 	/* Set device name and bus/vendor information */
316 	memset(&uisetup, 0, sizeof(uisetup));
317 	snprintf(uisetup.name, UINPUT_MAX_NAME_SIZE,
318 	    "%s, bdaddr %s", name, uniq);
319 	uisetup.id.bustype = BUS_BLUETOOTH;
320 	uisetup.id.vendor  = p->vendor_id;
321 	uisetup.id.product = p->product_id;
322 	uisetup.id.version = p->version;
323 
324 	fd = open("/dev/uinput", O_RDWR | O_NONBLOCK);
325 
326 	if (ioctl(fd, UI_SET_PHYS, phys) < 0 ||
327 	    ioctl(fd, UI_SET_BSDUNIQ, uniq) < 0 ||
328 	    ioctl(fd, UI_DEV_SETUP, &uisetup) < 0)
329 		return (-1);
330 
331 	return (fd);
332 }
333 
334 /*
335  * Setup uinput device as 8button mouse with wheel(s)
336  * TODO: bring in more feature detection code from ums
337  */
338 int32_t
339 uinput_open_mouse(hid_device_p const p, bdaddr_p local)
340 {
341 	size_t	i;
342 	int32_t	fd;
343 
344 	assert(p != NULL);
345 
346 	if ((fd = uinput_open_common(p, local, "Bluetooth Mouse")) < 0)
347 		goto bail_out;
348 
349 	/* Advertise events and axes */
350 	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 ||
351 	    ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 ||
352 	    ioctl(fd, UI_SET_EVBIT, EV_REL) < 0 ||
353 	    ioctl(fd, UI_SET_RELBIT, REL_X) < 0 ||
354 	    ioctl(fd, UI_SET_RELBIT, REL_Y) < 0 ||
355 	    (p->has_wheel && ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0) ||
356 	    (p->has_hwheel && ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0) ||
357 	    ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER) < 0)
358 		goto bail_out;
359 
360 	/* Advertise mouse buttons */
361 	for (i = 0; i < nitems(mbuttons); i++)
362 		if (ioctl(fd, UI_SET_KEYBIT, mbuttons[i]) < 0)
363 			goto bail_out;
364 
365 	if (ioctl(fd, UI_DEV_CREATE) >= 0)
366 		return (fd); /* SUCCESS */
367 
368 bail_out:
369 	if (fd >= 0)
370 		close(fd);
371 	return (-1);
372 }
373 
374 /*
375  * Setup uinput keyboard
376  */
377 int32_t
378 uinput_open_keyboard(hid_device_p const p, bdaddr_p local)
379 {
380 	size_t	i;
381 	int32_t	fd;
382 
383 	assert(p != NULL);
384 
385 	if ((fd = uinput_open_common(p, local, "Bluetooth Keyboard")) < 0)
386 		goto bail_out;
387 
388 	/* Advertise key events and LEDs */
389 	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 ||
390 	    ioctl(fd, UI_SET_EVBIT, EV_LED) < 0 ||
391 	    ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 ||
392 	    ioctl(fd, UI_SET_EVBIT, EV_REP) < 0 ||
393 	    ioctl(fd, UI_SET_LEDBIT, LED_CAPSL) < 0 ||
394 	    ioctl(fd, UI_SET_LEDBIT, LED_NUML) < 0 ||
395 	    ioctl(fd, UI_SET_LEDBIT, LED_SCROLLL))
396 		goto bail_out;
397 
398 	/* Advertise keycodes */
399 	for (i = 0; i < nitems(keymap); i++)
400 		if (keymap[i] != NONE &&
401 		    ioctl(fd, UI_SET_KEYBIT, keymap[i]) < 0)
402 			goto bail_out;
403 
404 	/* Advertise consumer page keys if any */
405 	if (p->has_cons) {
406 		for (i = 0; i < nitems(consmap); i++) {
407 			if (consmap[i] != NONE &&
408 			    ioctl(fd, UI_SET_KEYBIT, consmap[i]) < 0)
409 				goto bail_out;
410 		}
411 	}
412 
413 	if (ioctl(fd, UI_DEV_CREATE) >= 0)
414 		return (fd); /* SUCCESS */
415 
416 bail_out:
417 	if (fd >= 0)
418 		close(fd);
419 	return (-1);
420 }
421 
422 /* from sys/dev/evdev/evdev.h */
423 #define	EVDEV_RCPT_HW_MOUSE	(1<<2)
424 #define	EVDEV_RCPT_HW_KBD	(1<<3)
425 
426 #define	MASK_POLL_INTERVAL	5 /* seconds */
427 #define	MASK_SYSCTL		"kern.evdev.rcpt_mask"
428 
429 static int32_t
430 uinput_get_rcpt_mask(void)
431 {
432 	static struct timespec last = { 0, 0 };
433 	struct timespec now;
434 	static int32_t mask = 0;
435 	size_t len;
436 	time_t elapsed;
437 
438 	if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == -1)
439 		return mask;
440 
441 	elapsed = now.tv_sec - last.tv_sec;
442 	if (now.tv_nsec < last.tv_nsec)
443 		elapsed--;
444 
445 	if (elapsed >= MASK_POLL_INTERVAL) {
446 		len = sizeof(mask);
447 		if (sysctlbyname(MASK_SYSCTL, &mask, &len, NULL, 0) < 0) {
448 			if (errno == ENOENT)
449 				/* kernel is compiled w/o EVDEV_SUPPORT */
450 				mask = EVDEV_RCPT_HW_MOUSE | EVDEV_RCPT_HW_KBD;
451 			else
452 				mask = 0;
453 		}
454 		last = now;
455 	}
456 	return mask;
457 }
458 
459 static int32_t
460 uinput_write_event(int32_t fd, uint16_t type, uint16_t code, int32_t value)
461 {
462 	struct input_event ie;
463 
464 	assert(fd >= 0);
465 
466 	memset(&ie, 0, sizeof(ie));
467 	ie.type = type;
468 	ie.code = code;
469 	ie.value = value;
470 	return (write(fd, &ie, sizeof(ie)));
471 }
472 
473 int32_t
474 uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z, int32_t t,
475     int32_t buttons, int32_t obuttons)
476 {
477 	size_t i;
478 	int32_t rcpt_mask, mask;
479 
480 	assert(fd >= 0);
481 
482 	rcpt_mask = uinput_get_rcpt_mask();
483 	if (!(rcpt_mask & EVDEV_RCPT_HW_MOUSE))
484 		return (0);
485 
486 	if ((x != 0 && uinput_write_event(fd, EV_REL, REL_X, x) < 0) ||
487 	    (y != 0 && uinput_write_event(fd, EV_REL, REL_Y, y) < 0) ||
488 	    (z != 0 && uinput_write_event(fd, EV_REL, REL_WHEEL, -z) < 0) ||
489 	    (t != 0 && uinput_write_event(fd, EV_REL, REL_HWHEEL, t) < 0))
490 		return (-1);
491 
492 	for (i = 0; i < nitems(mbuttons); i++) {
493 		mask = 1 << i;
494 		if ((buttons & mask) == (obuttons & mask))
495 			continue;
496 		if (uinput_write_event(fd, EV_KEY, mbuttons[i],
497 		    (buttons & mask) != 0) < 0)
498 			return (-1);
499 	}
500 
501 	if (uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) < 0)
502 		return (-1);
503 
504 	return (0);
505 }
506 
507 /*
508  * Translate and report keyboard page key events
509  */
510 int32_t
511 uinput_rep_key(int32_t fd, int32_t key, int32_t make)
512 {
513 	int32_t rcpt_mask;
514 
515 	assert(fd >= 0);
516 
517 	rcpt_mask = uinput_get_rcpt_mask();
518 	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
519 		return (0);
520 
521 	if (key >= 0 && key < (int32_t)nitems(keymap) &&
522 	    keymap[key] != NONE) {
523 		if (uinput_write_event(fd, EV_KEY, keymap[key], make) > 0 &&
524 		    uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0)
525 			return (0);
526 	}
527 	return (-1);
528 }
529 
530 /*
531  * Translate and report consumer page key events
532  */
533 int32_t
534 uinput_rep_cons(int32_t fd, int32_t key, int32_t make)
535 {
536 	int32_t rcpt_mask;
537 
538 	assert(fd >= 0);
539 
540 	rcpt_mask = uinput_get_rcpt_mask();
541 	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
542 		return (0);
543 
544 	if (key >= 0 && key < (int32_t)nitems(consmap) &&
545 	    consmap[key] != NONE) {
546 		if (uinput_write_event(fd, EV_KEY, consmap[key], make) > 0 &&
547 		    uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0)
548 			return (0);
549 	}
550 	return (-1);
551 }
552 
553 /*
554  * Translate and report LED events
555  */
556 int32_t
557 uinput_rep_leds(int32_t fd, int state, int mask)
558 {
559 	size_t i;
560 	int32_t rcpt_mask;
561 
562 	assert(fd >= 0);
563 
564 	rcpt_mask = uinput_get_rcpt_mask();
565 	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD))
566 		return (0);
567 
568 	for (i = 0; i < nitems(led_codes); i++) {
569 		if (mask & (1 << i) &&
570 		    uinput_write_event(fd, EV_LED, led_codes[i],
571 					state & (1 << i) ? 1 : 0) < 0)
572 			return (-1);
573 	}
574 
575 	return (0);
576 }
577 
578 /*
579  * Process status change from evdev
580  */
581 int32_t
582 uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len)
583 {
584 	struct input_event ie;
585 	int32_t leds, oleds;
586 	size_t i;
587 
588 	assert(s != NULL);
589 	assert(s->vkbd >= 0);
590 	assert(len == sizeof(struct input_event));
591 
592 	memcpy(&ie, data, sizeof(ie));
593 	switch (ie.type) {
594 	case EV_LED:
595 		ioctl(s->vkbd, KDGETLED, &oleds);
596 		leds = oleds;
597 		for (i = 0; i < nitems(led_codes); i++) {
598 			if (led_codes[i] == ie.code) {
599 				if (ie.value)
600 					leds |= 1 << i;
601 				else
602 					leds &= ~(1 << i);
603 				if (leds != oleds)
604 					ioctl(s->vkbd, KDSETLED, leds);
605 				break;
606 			}
607 		}
608 		break;
609 	case EV_REP:
610 		/* FALLTHROUGH. Repeats are handled by evdev subsystem */
611 	default:
612 		break;
613 	}
614 
615 	return (0);
616 }
617