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