diff options
Diffstat (limited to 'usr.sbin/bluetooth/bthidd/btuinput.c')
| -rw-r--r-- | usr.sbin/bluetooth/bthidd/btuinput.c | 616 | 
1 files changed, 616 insertions, 0 deletions
| diff --git a/usr.sbin/bluetooth/bthidd/btuinput.c b/usr.sbin/bluetooth/bthidd/btuinput.c new file mode 100644 index 000000000000..497a5527e3aa --- /dev/null +++ b/usr.sbin/bluetooth/bthidd/btuinput.c @@ -0,0 +1,616 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2015-2017 Vladimir Kondratyev <wulf@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/kbio.h> +#include <sys/sysctl.h> + +#include <dev/evdev/input.h> +#include <dev/evdev/uinput.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <assert.h> +#define L2CAP_SOCKET_CHECKED +#include <bluetooth.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <usbhid.h> + +#include "bthid_config.h" +#include "bthidd.h" +#include "btuinput.h" + +static int16_t const mbuttons[8] = { +	BTN_LEFT, +	BTN_MIDDLE, +	BTN_RIGHT, +	BTN_SIDE, +	BTN_EXTRA, +	BTN_FORWARD, +	BTN_BACK, +	BTN_TASK +}; + +static uint16_t const led_codes[3] = { +	LED_CAPSL,	/* CLKED */ +	LED_NUML,	/* NLKED */ +	LED_SCROLLL,	/* SLKED */ +}; + +#define	NONE	KEY_RESERVED + +static uint16_t const keymap[0x100] = { +	/* 0x00 - 0x27 */ +	NONE,	NONE,	NONE,	NONE,	KEY_A,	KEY_B,	KEY_C,	KEY_D, +	KEY_E,	KEY_F,	KEY_G,	KEY_H,	KEY_I,	KEY_J,	KEY_K,	KEY_L, +	KEY_M,	KEY_N,	KEY_O,	KEY_P,	KEY_Q,	KEY_R,	KEY_S,	KEY_T, +	KEY_U,	KEY_V,	KEY_W,	KEY_X,	KEY_Y,	KEY_Z,	KEY_1,	KEY_2, +	KEY_3,	KEY_4,	KEY_5,	KEY_6,	KEY_7,	KEY_8,	KEY_9,	KEY_0, +	/* 0x28 - 0x3f */ +	KEY_ENTER,	KEY_ESC,	KEY_BACKSPACE,	KEY_TAB, +	KEY_SPACE,	KEY_MINUS,	KEY_EQUAL,	KEY_LEFTBRACE, +	KEY_RIGHTBRACE,	KEY_BACKSLASH,	KEY_BACKSLASH,	KEY_SEMICOLON, +	KEY_APOSTROPHE,	KEY_GRAVE,	KEY_COMMA,	KEY_DOT, +	KEY_SLASH,	KEY_CAPSLOCK,	KEY_F1,		KEY_F2, +	KEY_F3,		KEY_F4,		KEY_F5,		KEY_F6, +	/* 0x40 - 0x5f */ +	KEY_F7,		KEY_F8,		KEY_F9,		KEY_F10, +	KEY_F11,	KEY_F12,	KEY_SYSRQ,	KEY_SCROLLLOCK, +	KEY_PAUSE,	KEY_INSERT,	KEY_HOME,	KEY_PAGEUP, +	KEY_DELETE,	KEY_END,	KEY_PAGEDOWN,	KEY_RIGHT, +	KEY_LEFT,	KEY_DOWN,	KEY_UP,		KEY_NUMLOCK, +	KEY_KPSLASH,	KEY_KPASTERISK,	KEY_KPMINUS,	KEY_KPPLUS, +	KEY_KPENTER,	KEY_KP1,	KEY_KP2,	KEY_KP3, +	KEY_KP4,	KEY_KP5,	KEY_KP6,	KEY_KP7, +	/* 0x60 - 0x7f */ +	KEY_KP8,	KEY_KP9,	KEY_KP0,	KEY_KPDOT, +	KEY_102ND,	KEY_COMPOSE,	KEY_POWER,	KEY_KPEQUAL, +	KEY_F13,	KEY_F14,	KEY_F15,	KEY_F16, +	KEY_F17,	KEY_F18,	KEY_F19,	KEY_F20, +	KEY_F21,	KEY_F22,	KEY_F23,	KEY_F24, +	KEY_OPEN,	KEY_HELP,	KEY_PROPS,	KEY_FRONT, +	KEY_STOP,	KEY_AGAIN,	KEY_UNDO,	KEY_CUT, +	KEY_COPY,	KEY_PASTE,	KEY_FIND,	KEY_MUTE, +	/* 0x80 - 0x9f */ +	KEY_VOLUMEUP,	KEY_VOLUMEDOWN,	NONE,		NONE, +	NONE,		KEY_KPCOMMA,	NONE,		KEY_RO, +	KEY_KATAKANAHIRAGANA,	KEY_YEN,KEY_HENKAN,	KEY_MUHENKAN, +	KEY_KPJPCOMMA,	NONE,		NONE,		NONE, +	KEY_HANGEUL,	KEY_HANJA,	KEY_KATAKANA,	KEY_HIRAGANA, +	KEY_ZENKAKUHANKAKU,	NONE,	NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	/* 0xa0 - 0xbf */ +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	NONE,		NONE,		NONE,		NONE, +	/* 0xc0 - 0xdf */ +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	NONE,		NONE,           NONE,		NONE, +	/* 0xe0 - 0xff */ +	KEY_LEFTCTRL,	KEY_LEFTSHIFT,	KEY_LEFTALT,	KEY_LEFTMETA, +	KEY_RIGHTCTRL,	KEY_RIGHTSHIFT,	KEY_RIGHTALT,	KEY_RIGHTMETA, +	KEY_PLAYPAUSE,	KEY_STOPCD,	KEY_PREVIOUSSONG,KEY_NEXTSONG, +	KEY_EJECTCD,	KEY_VOLUMEUP,	KEY_VOLUMEDOWN, KEY_MUTE, +	KEY_WWW,	KEY_BACK,	KEY_FORWARD,	KEY_STOP, +	KEY_FIND,	KEY_SCROLLUP,	KEY_SCROLLDOWN,	KEY_EDIT, +	KEY_SLEEP,	KEY_COFFEE,	KEY_REFRESH,	KEY_CALC, +	NONE,		NONE,		NONE,		NONE, +}; + +/* Consumer page usage mapping */ +static uint16_t const consmap[0x300] = { +	[0x030] = KEY_POWER, +	[0x031] = KEY_RESTART, +	[0x032] = KEY_SLEEP, +	[0x034] = KEY_SLEEP, +	[0x035] = KEY_KBDILLUMTOGGLE, +	[0x036] = BTN_MISC, +	[0x040] = KEY_MENU, +	[0x041] = KEY_SELECT, +	[0x042] = KEY_UP, +	[0x043] = KEY_DOWN, +	[0x044] = KEY_LEFT, +	[0x045] = KEY_RIGHT, +	[0x046] = KEY_ESC, +	[0x047] = KEY_KPPLUS, +	[0x048] = KEY_KPMINUS, +	[0x060] = KEY_INFO, +	[0x061] = KEY_SUBTITLE, +	[0x063] = KEY_VCR, +	[0x065] = KEY_CAMERA, +	[0x069] = KEY_RED, +	[0x06a] = KEY_GREEN, +	[0x06b] = KEY_BLUE, +	[0x06c] = KEY_YELLOW, +	[0x06d] = KEY_ZOOM, +	[0x06f] = KEY_BRIGHTNESSUP, +	[0x070] = KEY_BRIGHTNESSDOWN, +	[0x072] = KEY_BRIGHTNESS_TOGGLE, +	[0x073] = KEY_BRIGHTNESS_MIN, +	[0x074] = KEY_BRIGHTNESS_MAX, +	[0x075] = KEY_BRIGHTNESS_AUTO, +	[0x082] = KEY_VIDEO_NEXT, +	[0x083] = KEY_LAST, +	[0x084] = KEY_ENTER, +	[0x088] = KEY_PC, +	[0x089] = KEY_TV, +	[0x08a] = KEY_WWW, +	[0x08b] = KEY_DVD, +	[0x08c] = KEY_PHONE, +	[0x08d] = KEY_PROGRAM, +	[0x08e] = KEY_VIDEOPHONE, +	[0x08f] = KEY_GAMES, +	[0x090] = KEY_MEMO, +	[0x091] = KEY_CD, +	[0x092] = KEY_VCR, +	[0x093] = KEY_TUNER, +	[0x094] = KEY_EXIT, +	[0x095] = KEY_HELP, +	[0x096] = KEY_TAPE, +	[0x097] = KEY_TV2, +	[0x098] = KEY_SAT, +	[0x09a] = KEY_PVR, +	[0x09c] = KEY_CHANNELUP, +	[0x09d] = KEY_CHANNELDOWN, +	[0x0a0] = KEY_VCR2, +	[0x0b0] = KEY_PLAY, +	[0x0b1] = KEY_PAUSE, +	[0x0b2] = KEY_RECORD, +	[0x0b3] = KEY_FASTFORWARD, +	[0x0b4] = KEY_REWIND, +	[0x0b5] = KEY_NEXTSONG, +	[0x0b6] = KEY_PREVIOUSSONG, +	[0x0b7] = KEY_STOPCD, +	[0x0b8] = KEY_EJECTCD, +	[0x0bc] = KEY_MEDIA_REPEAT, +	[0x0b9] = KEY_SHUFFLE, +	[0x0bf] = KEY_SLOW, +	[0x0cd] = KEY_PLAYPAUSE, +	[0x0cf] = KEY_VOICECOMMAND, +	[0x0e2] = KEY_MUTE, +	[0x0e5] = KEY_BASSBOOST, +	[0x0e9] = KEY_VOLUMEUP, +	[0x0ea] = KEY_VOLUMEDOWN, +	[0x0f5] = KEY_SLOW, +	[0x181] = KEY_BUTTONCONFIG, +	[0x182] = KEY_BOOKMARKS, +	[0x183] = KEY_CONFIG, +	[0x184] = KEY_WORDPROCESSOR, +	[0x185] = KEY_EDITOR, +	[0x186] = KEY_SPREADSHEET, +	[0x187] = KEY_GRAPHICSEDITOR, +	[0x188] = KEY_PRESENTATION, +	[0x189] = KEY_DATABASE, +	[0x18a] = KEY_MAIL, +	[0x18b] = KEY_NEWS, +	[0x18c] = KEY_VOICEMAIL, +	[0x18d] = KEY_ADDRESSBOOK, +	[0x18e] = KEY_CALENDAR, +	[0x18f] = KEY_TASKMANAGER, +	[0x190] = KEY_JOURNAL, +	[0x191] = KEY_FINANCE, +	[0x192] = KEY_CALC, +	[0x193] = KEY_PLAYER, +	[0x194] = KEY_FILE, +	[0x196] = KEY_WWW, +	[0x199] = KEY_CHAT, +	[0x19c] = KEY_LOGOFF, +	[0x19e] = KEY_COFFEE, +	[0x19f] = KEY_CONTROLPANEL, +	[0x1a2] = KEY_APPSELECT, +	[0x1a3] = KEY_NEXT, +	[0x1a4] = KEY_PREVIOUS, +	[0x1a6] = KEY_HELP, +	[0x1a7] = KEY_DOCUMENTS, +	[0x1ab] = KEY_SPELLCHECK, +	[0x1ae] = KEY_KEYBOARD, +	[0x1b1] = KEY_SCREENSAVER, +	[0x1b4] = KEY_FILE, +	[0x1b6] = KEY_IMAGES, +	[0x1b7] = KEY_AUDIO, +	[0x1b8] = KEY_VIDEO, +	[0x1bc] = KEY_MESSENGER, +	[0x1bd] = KEY_INFO, +	[0x201] = KEY_NEW, +	[0x202] = KEY_OPEN, +	[0x203] = KEY_CLOSE, +	[0x204] = KEY_EXIT, +	[0x207] = KEY_SAVE, +	[0x208] = KEY_PRINT, +	[0x209] = KEY_PROPS, +	[0x21a] = KEY_UNDO, +	[0x21b] = KEY_COPY, +	[0x21c] = KEY_CUT, +	[0x21d] = KEY_PASTE, +	[0x21f] = KEY_FIND, +	[0x221] = KEY_SEARCH, +	[0x222] = KEY_GOTO, +	[0x223] = KEY_HOMEPAGE, +	[0x224] = KEY_BACK, +	[0x225] = KEY_FORWARD, +	[0x226] = KEY_STOP, +	[0x227] = KEY_REFRESH, +	[0x22a] = KEY_BOOKMARKS, +	[0x22d] = KEY_ZOOMIN, +	[0x22e] = KEY_ZOOMOUT, +	[0x22f] = KEY_ZOOMRESET, +	[0x233] = KEY_SCROLLUP, +	[0x234] = KEY_SCROLLDOWN, +	[0x23d] = KEY_EDIT, +	[0x25f] = KEY_CANCEL, +	[0x269] = KEY_INSERT, +	[0x26a] = KEY_DELETE, +	[0x279] = KEY_REDO, +	[0x289] = KEY_REPLY, +	[0x28b] = KEY_FORWARDMAIL, +	[0x28c] = KEY_SEND, +	[0x2c7] = KEY_KBDINPUTASSIST_PREV, +	[0x2c8] = KEY_KBDINPUTASSIST_NEXT, +	[0x2c9] = KEY_KBDINPUTASSIST_PREVGROUP, +	[0x2ca] = KEY_KBDINPUTASSIST_NEXTGROUP, +	[0x2cb] = KEY_KBDINPUTASSIST_ACCEPT, +	[0x2cc] = KEY_KBDINPUTASSIST_CANCEL, +}; + +static int32_t +uinput_open_common(hid_device_p const p, bdaddr_p local, const uint8_t *name) +{ +	struct uinput_setup	uisetup; +	uint8_t			phys[UINPUT_MAX_NAME_SIZE]; +	uint8_t			uniq[UINPUT_MAX_NAME_SIZE]; +	int32_t			fd; + +	/* Take local and remote bdaddr */ +	bt_ntoa(local, phys); +	bt_ntoa(&p->bdaddr, uniq); + +	/* Take device name from bthidd.conf. Fallback to generic name. */ +	if (p->name != NULL) +		name = p->name; + +	/* Set device name and bus/vendor information */ +	memset(&uisetup, 0, sizeof(uisetup)); +	snprintf(uisetup.name, UINPUT_MAX_NAME_SIZE, +	    "%s, bdaddr %s", name, uniq); +	uisetup.id.bustype = BUS_BLUETOOTH; +	uisetup.id.vendor  = p->vendor_id; +	uisetup.id.product = p->product_id; +	uisetup.id.version = p->version; + +	fd = open("/dev/uinput", O_RDWR | O_NONBLOCK); + +	if (ioctl(fd, UI_SET_PHYS, phys) < 0 || +	    ioctl(fd, UI_SET_BSDUNIQ, uniq) < 0 || +	    ioctl(fd, UI_DEV_SETUP, &uisetup) < 0) +		return (-1); + +	return (fd); +} + +/* + * Setup uinput device as 8button mouse with wheel(s) + * TODO: bring in more feature detection code from ums + */ +int32_t +uinput_open_mouse(hid_device_p const p, bdaddr_p local) +{ +	size_t	i; +	int32_t	fd; + +	assert(p != NULL); + +	if ((fd = uinput_open_common(p, local, "Bluetooth Mouse")) < 0) +		goto bail_out; + +	/* Advertise events and axes */ +	if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 || +	    ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || +	    ioctl(fd, UI_SET_EVBIT, EV_REL) < 0 || +	    ioctl(fd, UI_SET_RELBIT, REL_X) < 0 || +	    ioctl(fd, UI_SET_RELBIT, REL_Y) < 0 || +	    (p->has_wheel && ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0) || +	    (p->has_hwheel && ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0) || +	    ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_POINTER) < 0) +		goto bail_out; + +	/* Advertise mouse buttons */ +	for (i = 0; i < nitems(mbuttons); i++) +		if (ioctl(fd, UI_SET_KEYBIT, mbuttons[i]) < 0) +			goto bail_out; + +	if (ioctl(fd, UI_DEV_CREATE) >= 0) +		return (fd); /* SUCCESS */ + +bail_out: +	if (fd >= 0) +		close(fd); +	return (-1); +} + +/* + * Setup uinput keyboard + */ +int32_t +uinput_open_keyboard(hid_device_p const p, bdaddr_p local) +{ +	size_t	i; +	int32_t	fd; + +	assert(p != NULL); + +	if ((fd = uinput_open_common(p, local, "Bluetooth Keyboard")) < 0) +		goto bail_out; + +	/* Advertise key events and LEDs */ +	if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || +	    ioctl(fd, UI_SET_EVBIT, EV_LED) < 0 || +	    ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0 || +	    ioctl(fd, UI_SET_EVBIT, EV_REP) < 0 || +	    ioctl(fd, UI_SET_LEDBIT, LED_CAPSL) < 0 || +	    ioctl(fd, UI_SET_LEDBIT, LED_NUML) < 0 || +	    ioctl(fd, UI_SET_LEDBIT, LED_SCROLLL)) +		goto bail_out; + +	/* Advertise keycodes */ +	for (i = 0; i < nitems(keymap); i++) +		if (keymap[i] != NONE && +		    ioctl(fd, UI_SET_KEYBIT, keymap[i]) < 0) +			goto bail_out; + +	/* Advertise consumer page keys if any */ +	if (p->has_cons) { +		for (i = 0; i < nitems(consmap); i++) { +			if (consmap[i] != NONE && +			    ioctl(fd, UI_SET_KEYBIT, consmap[i]) < 0) +				goto bail_out; +		} +	} + +	if (ioctl(fd, UI_DEV_CREATE) >= 0) +		return (fd); /* SUCCESS */ + +bail_out: +	if (fd >= 0) +		close(fd); +	return (-1); +} + +/* from sys/dev/evdev/evdev.h */ +#define	EVDEV_RCPT_HW_MOUSE	(1<<2) +#define	EVDEV_RCPT_HW_KBD	(1<<3) + +#define	MASK_POLL_INTERVAL	5 /* seconds */ +#define	MASK_SYSCTL		"kern.evdev.rcpt_mask" + +static int32_t +uinput_get_rcpt_mask(void) +{ +	static struct timespec last = { 0, 0 }; +	struct timespec now; +	static int32_t mask = 0; +	size_t len; +	time_t elapsed; + +	if (clock_gettime(CLOCK_MONOTONIC_FAST, &now) == -1) +		return mask; + +	elapsed = now.tv_sec - last.tv_sec; +	if (now.tv_nsec < last.tv_nsec) +		elapsed--; + +	if (elapsed >= MASK_POLL_INTERVAL) { +		len = sizeof(mask); +		if (sysctlbyname(MASK_SYSCTL, &mask, &len, NULL, 0) < 0) { +			if (errno == ENOENT) +				/* kernel is compiled w/o EVDEV_SUPPORT */ +				mask = EVDEV_RCPT_HW_MOUSE | EVDEV_RCPT_HW_KBD; +			else +				mask = 0; +		} +		last = now; +	} +	return mask; +} + +static int32_t +uinput_write_event(int32_t fd, uint16_t type, uint16_t code, int32_t value) +{ +	struct input_event ie; + +	assert(fd >= 0); + +	memset(&ie, 0, sizeof(ie)); +	ie.type = type; +	ie.code = code; +	ie.value = value; +	return (write(fd, &ie, sizeof(ie))); +} + +int32_t +uinput_rep_mouse(int32_t fd, int32_t x, int32_t y, int32_t z, int32_t t, +    int32_t buttons, int32_t obuttons) +{ +	size_t i; +	int32_t rcpt_mask, mask; + +	assert(fd >= 0); + +	rcpt_mask = uinput_get_rcpt_mask(); +	if (!(rcpt_mask & EVDEV_RCPT_HW_MOUSE)) +		return (0); + +	if ((x != 0 && uinput_write_event(fd, EV_REL, REL_X, x) < 0) || +	    (y != 0 && uinput_write_event(fd, EV_REL, REL_Y, y) < 0) || +	    (z != 0 && uinput_write_event(fd, EV_REL, REL_WHEEL, -z) < 0) || +	    (t != 0 && uinput_write_event(fd, EV_REL, REL_HWHEEL, t) < 0)) +		return (-1); + +	for (i = 0; i < nitems(mbuttons); i++) { +		mask = 1 << i; +		if ((buttons & mask) == (obuttons & mask)) +			continue; +		if (uinput_write_event(fd, EV_KEY, mbuttons[i], +		    (buttons & mask) != 0) < 0) +			return (-1); +	} + +	if (uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) < 0) +		return (-1); + +	return (0); +} + +/* + * Translate and report keyboard page key events + */ +int32_t +uinput_rep_key(int32_t fd, int32_t key, int32_t make) +{ +	int32_t rcpt_mask; + +	assert(fd >= 0); + +	rcpt_mask = uinput_get_rcpt_mask(); +	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) +		return (0); + +	if (key >= 0 && key < (int32_t)nitems(keymap) && +	    keymap[key] != NONE) { +		if (uinput_write_event(fd, EV_KEY, keymap[key], make) > 0 && +		    uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0) +			return (0); +	} +	return (-1); +} + +/* + * Translate and report consumer page key events + */ +int32_t +uinput_rep_cons(int32_t fd, int32_t key, int32_t make) +{ +	int32_t rcpt_mask; + +	assert(fd >= 0); + +	rcpt_mask = uinput_get_rcpt_mask(); +	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) +		return (0); + +	if (key >= 0 && key < (int32_t)nitems(consmap) && +	    consmap[key] != NONE) { +		if (uinput_write_event(fd, EV_KEY, consmap[key], make) > 0 && +		    uinput_write_event(fd, EV_SYN, SYN_REPORT, 0) > 0) +			return (0); +	} +	return (-1); +} + +/* + * Translate and report LED events + */ +int32_t +uinput_rep_leds(int32_t fd, int state, int mask) +{ +	size_t i; +	int32_t rcpt_mask; + +	assert(fd >= 0); + +	rcpt_mask = uinput_get_rcpt_mask(); +	if (!(rcpt_mask & EVDEV_RCPT_HW_KBD)) +		return (0); + +	for (i = 0; i < nitems(led_codes); i++) { +		if (mask & (1 << i) && +		    uinput_write_event(fd, EV_LED, led_codes[i], +					state & (1 << i) ? 1 : 0) < 0) +			return (-1); +	} + +	return (0); +} + +/* + * Process status change from evdev + */ +int32_t +uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len) +{ +	struct input_event ie; +	int32_t leds, oleds; +	size_t i; + +	assert(s != NULL); +	assert(s->vkbd >= 0); +	assert(len == sizeof(struct input_event)); + +	memcpy(&ie, data, sizeof(ie)); +	switch (ie.type) { +	case EV_LED: +		ioctl(s->vkbd, KDGETLED, &oleds); +		leds = oleds; +		for (i = 0; i < nitems(led_codes); i++) { +			if (led_codes[i] == ie.code) { +				if (ie.value) +					leds |= 1 << i; +				else +					leds &= ~(1 << i); +				if (leds != oleds) +					ioctl(s->vkbd, KDSETLED, leds); +				break; +			} +		} +		break; +	case EV_REP: +		/* FALLTHROUGH. Repeats are handled by evdev subsystem */ +	default: +		break; +	} + +	return (0); +} | 
