Index: etc/defaults/rc.conf =================================================================== --- etc/defaults/rc.conf +++ etc/defaults/rc.conf @@ -433,6 +433,7 @@ bthidd_enable="NO" # Enable bthidd(8) (or NO) bthidd_config="/etc/bluetooth/bthidd.conf" # bthidd(8) configuration file bthidd_hids="/var/db/bthidd.hids" # bthidd(8) known HID devices file +bthidd_evdev_support="AUTO" # AUTO depends on EVDEV_SUPPORT kernel option rfcomm_pppd_server_enable="NO" # Enable rfcomm_pppd(8) in server mode (or NO) rfcomm_pppd_server_profile="one two" # Profile to use from /etc/ppp/ppp.conf Index: etc/rc.d/bthidd =================================================================== --- etc/rc.d/bthidd +++ etc/rc.d/bthidd @@ -17,8 +17,25 @@ pidfile="/var/run/${name}.pid" start_precmd="bthidd_prestart" +evdev_enabled() +{ + case ${bthidd_evdev_support} in + [Aa][Uu][Tt][Oo]) + check_kern_features evdev_support + return $? + ;; + *) + checkyesno bthidd_evdev_support + return $? + ;; + esac +} + bthidd_prestart() { + if evdev_enabled; then + load_kld -m uinput uinput + fi load_kld -m kbdmux kbdmux load_kld -m vkbd vkbd load_kld -m ng_btsocket ng_btsocket @@ -29,6 +46,9 @@ config="${bthidd_config:-/etc/bluetooth/${name}.conf}" hids="${bthidd_hids:-/var/db/${name}.hids}" command_args="-c ${config} -H ${hids} -p ${pidfile}" +if evdev_enabled; then + command_args="$command_args -u" +fi required_files="${config}" run_rc_command "$1" Index: lib/libbluetooth/bluetooth.h =================================================================== --- lib/libbluetooth/bluetooth.h +++ lib/libbluetooth/bluetooth.h @@ -182,9 +182,19 @@ int bt_devfilter_evt_tst(struct bt_devfilter const *filter, uint8_t event); int bt_devinquiry(char const *devname, time_t length, int num_rsp, struct bt_devinquiry **ii); +char * bt_devremote_name(char const *devname, const bdaddr_t *remote, + time_t to, uint16_t clk_off, + uint8_t ps_rep_mode, uint8_t ps_mode); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t cb, void *arg); +static __inline char * +bt_devremote_name_gen(char const *devname, const bdaddr_t *remote) +{ + return (bt_devremote_name(devname, remote, 0, 0x0000, + NG_HCI_SCAN_REP_MODE0, NG_HCI_MANDATORY_PAGE_SCAN_MODE)); +} + /* * bdaddr utility functions (from NetBSD) */ Index: lib/libbluetooth/bluetooth.3 =================================================================== --- lib/libbluetooth/bluetooth.3 +++ lib/libbluetooth/bluetooth.3 @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd April 9, 2009 +.Dd January 28, 2018 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -58,6 +58,8 @@ .Nm bt_devfilter_evt_clr , .Nm bt_devfilter_evt_tst , .Nm bt_devinquiry , +.Nm bt_devremote_name , +.Nm bt_devremote_name_gen , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -126,6 +128,11 @@ .Fn bt_devfilter_evt_tst "struct bt_devfilter const *filter" "uint8_t event" .Ft int .Fn bt_devinquiry "char const *devname" "time_t length" "int num_rsp" "struct bt_devinquiry **ii" +.Ft char * +.Fn bt_devremote_name "char const *devname" "const bdaddr_t *remote" \ +"time_t to" "uint16_t clk_off" "uint8_t ps_rep_mode" "uint8_t ps_mode" +.Ft char * +.Fn bt_devremote_name_gen "char const *devname" "const bdaddr_t *remote" .Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int @@ -589,8 +596,54 @@ .Ed .Pp The +.Fn bt_devremote_name +function performs Bluetooth Remote Name Request procedure to obtain the +user-friendly name of another Bluetooth unit. +The +.Fa devname +parameter specifies which local Bluetooth device should perform the request. +If not specified +.Dv ( NULL ) , +the first available device is used. +The +.Fa remote +parameter specifies the Bluetooth BD_ADDR of the remote device to query. +The +.Fa to +parameter specifies response timeout in seconds. +If not specified (0), the default value is taken from the +net.bluetooth.hci.command_timeout +.Xr sysctl 8 +value. +The +.Fa clk_off , +.Fa ps_rep_mode , +and +.Fa ps_mode +parameters specify Clock_Offset, Page_Scan_Repetition_Mode, and Page_Scan_Mode +fields of HCI_Remote_Name_Request respectively. +On success, the function returns a pointer to dynamically allocated +NUL-terminated string or +.Dv NULL +if an error occurred. +It is up to the caller to release returned string using +.Xr free 3 . +.Pp +The +.Fn bt_devremote_name_gen +function is a shortcut to +.Fn bt_devremote_name +that passes generic defaults for +.Fa to , +.Fa clk_off , +.Fa ps_rep_mode , +and +.Fa ps_mode +parameters. +.Pp +The .Fn bdaddr_same , -.Fn bdaddr_any +.Fn bdaddr_any , and .Fn bdaddr_copy are handy shorthand Bluetooth address utility functions. Index: lib/libbluetooth/hci.c =================================================================== --- lib/libbluetooth/hci.c +++ lib/libbluetooth/hci.c @@ -32,6 +32,9 @@ * $FreeBSD$ */ +#include +#include + #include #define L2CAP_SOCKET_CHECKED #include @@ -39,6 +42,7 @@ #include #include #include +#include #include #undef MIN @@ -46,6 +50,7 @@ static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); +static time_t bt_get_default_hci_command_timeout(void); int bt_devopen(char const *devname) @@ -534,6 +539,63 @@ return (i - *ii); } +char * +bt_devremote_name(char const *devname, const bdaddr_t *remote, time_t to, + uint16_t clk_off, uint8_t ps_rep_mode, uint8_t ps_mode) +{ + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devreq r; + ng_hci_remote_name_req_cp cp; + ng_hci_remote_name_req_compl_ep ep; + int s; + char *remote_name = NULL; + + if (remote == NULL || to < 0) { + errno = EINVAL; + goto out; + } + + if (to == 0) { + to = bt_get_default_hci_command_timeout(); + if (to < 0) + goto out; + } + to++; + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + if (bt_devenum(bt_devany_cb, _devname) <= 0) + goto out; + } + + memset(&r, 0, sizeof(r)); + memset(&cp, 0, sizeof(cp)); + memset(&ep, 0, sizeof(ep)); + cp.clock_offset = htole16(clk_off); + cp.page_scan_rep_mode = ps_rep_mode; + cp.page_scan_mode = ps_mode; + bdaddr_copy(&cp.bdaddr, remote); + r.opcode = NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, + NG_HCI_OCF_REMOTE_NAME_REQ); + r.event = NG_HCI_EVENT_REMOTE_NAME_REQ_COMPL; + r.cparam = &cp; + r.clen = sizeof(cp); + r.rparam = &ep; + r.rlen = sizeof(ep); + + s = bt_devopen(devname); + if (s < 0) + goto out; + + if (bt_devreq(s, &r, to) == 0 || ep.status == 0x00) + remote_name = strndup((const char *)&ep.name, sizeof(ep.name)); + + bt_devclose(s); +out: + return (remote_name); +} + int bt_devinfo(struct bt_devinfo *di) { @@ -735,3 +797,21 @@ return (NULL); } +static time_t +bt_get_default_hci_command_timeout(void) +{ + int to; + size_t to_size = sizeof(to); + + if (sysctlbyname("net.bluetooth.hci.command_timeout", + &to, &to_size, NULL, 0) < 0) + return (-1); + + /* Should not happen */ + if (to <= 0) { + errno = ERANGE; + return (-1); + } + + return ((time_t)to); +} Index: lib/libsdp/sdp.h =================================================================== --- lib/libsdp/sdp.h +++ lib/libsdp/sdp.h @@ -532,6 +532,7 @@ void * sdp_open_local (char const *control); int32_t sdp_close (void *xs); int32_t sdp_error (void *xs); +int32_t sdp_get_lcaddr (void *xs, bdaddr_t *l); int32_t sdp_search (void *xs, uint32_t plen, uint16_t const *pp, Index: lib/libsdp/sdp.3 =================================================================== --- lib/libsdp/sdp.3 +++ lib/libsdp/sdp.3 @@ -25,7 +25,7 @@ .\" $Id: sdp.3,v 1.1 2003/09/07 20:34:19 max Exp $ .\" $FreeBSD$ .\" -.Dd May 27, 2005 +.Dd January 28, 2018 .Dt SDP 3 .Os .Sh NAME @@ -45,6 +45,7 @@ .Nm sdp_open_local , .Nm sdp_close , .Nm sdp_error , +.Nm sdp_get_lcaddr , .Nm sdp_search , .Nm sdp_attr2desc , .Nm sdp_uuid2desc @@ -75,6 +76,8 @@ .Ft int32_t .Fn sdp_error "void *xs" .Ft int32_t +.Fn sdp_get_lcaddr "void *xs" "bdaddr_t *l" +.Ft int32_t .Fo sdp_search .Fa "void *xs" "uint32_t plen" "uint16_t const *pp" "uint32_t alen" .Fa "uint32_t const *ap" "uint32_t vlen" "sdp_attr_t *vp" @@ -188,6 +191,22 @@ function. .Pp The +.Fn sdp_get_lcaddr +function returns the SDP session actual source BD_ADDR. +The +.Fa xs +parameter should point to a valid SDP session object created with +.Fn sdp_open . +The +.Fa l +parameter should point to a buffer in which the value for the requested BD_ADDR +is to be returned. +.Fn sdp_get_lcaddr +function is useful if the current SDP session has been opened with the +.Dv NG_HCI_BDADDR_ANY +value passed as a source address. +.Pp +The .Fn sdp_search function is used to perform SDP Service Search Attribute Request. The @@ -394,9 +413,10 @@ to check if there was connection error. .Pp The +.Fn sdp_get_lcaddr , .Fn sdp_search , .Fn sdp_register_service , -.Fn sdp_unregister_service +.Fn sdp_unregister_service , and .Fn sdp_change_service functions return non-zero value on error. Index: lib/libsdp/session.c =================================================================== --- lib/libsdp/session.c +++ lib/libsdp/session.c @@ -180,3 +180,25 @@ return ((ss != NULL)? ss->error : EINVAL); } + +int32_t +sdp_get_lcaddr(void *xss, bdaddr_t *l) +{ + sdp_session_p ss = (sdp_session_p) xss; + struct sockaddr_l2cap sa; + socklen_t size; + + if (l == NULL || ss == NULL || ss->flags & SDP_SESSION_LOCAL) { + ss->error = EINVAL; + goto fail; + } + + size = sizeof(sa); + if (getsockname(ss->s, (struct sockaddr *)&sa, &size) == 0) { + bdaddr_copy(l, &sa.l2cap_bdaddr); + ss->error = 0; + } else + ss->error = errno; +fail: + return ((ss->error == 0) ? 0 : -1); +} Index: sys/dev/evdev/uinput.h =================================================================== --- sys/dev/evdev/uinput.h +++ sys/dev/evdev/uinput.h @@ -90,6 +90,13 @@ #define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase) #define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase) +/* + * FreeBSD specific. Set unique identifier of input device. + * Name and magic are chosen to reduce chances of clashing + * with possible future Linux extensions. + */ +#define UI_SET_BSDUNIQ _IO(UINPUT_IOCTL_BASE, 109) + #define EV_UINPUT 0x0101 #define UI_FF_UPLOAD 1 #define UI_FF_ERASE 2 Index: sys/dev/evdev/uinput.c =================================================================== --- sys/dev/evdev/uinput.c +++ sys/dev/evdev/uinput.c @@ -604,6 +604,15 @@ evdev_set_phys(state->ucs_evdev, buf); return (0); + case UI_SET_BSDUNIQ: + if (state->ucs_state == UINPUT_RUNNING) + return (EINVAL); + ret = copyinstr(*(void **)data, buf, sizeof(buf), NULL); + if (ret != 0) + return (ret); + evdev_set_serial(state->ucs_evdev, buf); + return (0); + case UI_SET_SWBIT: if (state->ucs_state == UINPUT_RUNNING || intdata > SW_MAX || intdata < 0) Index: usr.sbin/bluetooth/bthidcontrol/Makefile =================================================================== --- usr.sbin/bluetooth/bthidcontrol/Makefile +++ usr.sbin/bluetooth/bthidcontrol/Makefile @@ -7,7 +7,8 @@ MAN= bthidcontrol.8 SRCS= bthidcontrol.c hid.c lexer.l parser.y sdp.c WARNS?= 1 -CFLAGS+= -DBTHIDCONTROL=1 -I${.CURDIR:H}/bthidd +CFLAGS+= -DBTHIDCONTROL=1 -I${.CURDIR:H}/bthidd -I${SRCTOP}/lib/libsdp \ + -I${SRCTOP}/lib/libbluetooth LIBADD+= bluetooth sdp usbhid Index: usr.sbin/bluetooth/bthidcontrol/sdp.c =================================================================== --- usr.sbin/bluetooth/bthidcontrol/sdp.c +++ usr.sbin/bluetooth/bthidcontrol/sdp.c @@ -31,7 +31,9 @@ * $FreeBSD$ */ +#include #include +#include #define L2CAP_SOCKET_CHECKED #include #include @@ -114,13 +116,15 @@ static int32_t hid_sdp_query(bdaddr_t const *local, struct hid_device *hd, int32_t *error) { - void *ss = NULL; - uint8_t *hid_descriptor = NULL, *v; - int32_t i, control_psm = -1, interrupt_psm = -1, - reconnect_initiate = -1, - normally_connectable = 0, battery_power = 0, - hid_descriptor_length = -1, type; - int16_t vendor_id = 0, product_id = 0, version = 0; + void *ss = NULL; + uint8_t *hid_descriptor = NULL, *v; + int32_t i, control_psm = -1, interrupt_psm = -1, + reconnect_initiate = -1, + normally_connectable = 0, battery_power = 0, + hid_descriptor_length = -1, type; + int16_t vendor_id = 0, product_id = 0, version = 0; + bdaddr_t sdp_local; + char devname[HCI_DEVNAME_SIZE]; if (local == NULL) local = NG_HCI_BDADDR_ANY; @@ -175,6 +179,11 @@ if (sdp_search(ss, 1, &service_devid, 1, &attrs_devid, nvalues, values) != 0) hid_sdp_query_exit(sdp_error(ss)); + /* Try extract HCI bdaddr from opened SDP session */ + if (sdp_get_lcaddr(ss, &sdp_local) != 0 || + bt_devname(devname, &sdp_local) == 0) + hid_sdp_query_exit(ENOATTR); + sdp_close(ss); ss = NULL; @@ -212,6 +221,7 @@ reconnect_initiate == -1 || hid_descriptor == NULL || hid_descriptor_length == -1) hid_sdp_query_exit(ENOATTR); + hd->name = bt_devremote_name_gen(devname, &hd->bdaddr); hd->vendor_id = vendor_id; hd->product_id = product_id; hd->version = version; Index: usr.sbin/bluetooth/bthidd/Makefile =================================================================== --- usr.sbin/bluetooth/bthidd/Makefile +++ usr.sbin/bluetooth/bthidd/Makefile @@ -4,8 +4,8 @@ PROG= bthidd MAN= bthidd.8 # bthidd.conf.5 -SRCS= bthidd.c client.c hid.c kbd.c lexer.l parser.y server.c \ - session.c +SRCS= bthidd.c btuinput.c client.c hid.c kbd.c lexer.l parser.y \ + server.c session.c CFLAGS+= -I${.CURDIR} Index: usr.sbin/bluetooth/bthidd/bthid_config.h =================================================================== --- usr.sbin/bluetooth/bthidd/bthid_config.h +++ usr.sbin/bluetooth/bthidd/bthid_config.h @@ -42,6 +42,7 @@ struct hid_device { bdaddr_t bdaddr; /* HID device BDADDR */ + char * name; /* HID device name */ uint16_t control_psm; /* control PSM */ uint16_t interrupt_psm; /* interrupt PSM */ uint16_t vendor_id; /* primary vendor id */ @@ -52,7 +53,11 @@ unsigned battery_power : 1; unsigned normally_connectable : 1; unsigned keyboard : 1; - unsigned reserved : 11; + unsigned mouse : 1; + unsigned has_wheel : 1; + unsigned has_hwheel : 1; + unsigned has_cons : 1; + unsigned reserved : 7; report_desc_t desc; /* HID report descriptor */ LIST_ENTRY(hid_device) next; /* link to the next */ }; Index: usr.sbin/bluetooth/bthidd/bthidd.h =================================================================== --- usr.sbin/bluetooth/bthidd/bthidd.h +++ usr.sbin/bluetooth/bthidd/bthidd.h @@ -48,6 +48,7 @@ int32_t ctrl; /* control channel (listen) */ int32_t intr; /* intr. channel (listen) */ int32_t maxfd; /* max fd in sets */ + int32_t uinput; /* enable evdev support */ fd_set rfdset; /* read descriptor set */ fd_set wfdset; /* write descriptor set */ LIST_HEAD(, bthid_session) sessions; @@ -63,6 +64,10 @@ int32_t intr; /* interrupt channel */ int32_t vkbd; /* virual keyboard */ void *ctx; /* product specific dev state */ + int32_t ukbd; /* evdev user input */ + int32_t umouse;/* evdev user input */ + int32_t obutt; /* previous mouse buttons */ + int32_t consk; /* last consumer page key */ bdaddr_t bdaddr;/* remote bdaddr */ uint16_t state; /* session state */ #define CLOSED 0 @@ -87,6 +92,7 @@ bthid_session_p session_open (bthid_server_p srv, hid_device_p const d); bthid_session_p session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr); bthid_session_p session_by_fd (bthid_server_p srv, int32_t fd); +int32_t session_run (bthid_session_p s); void session_close (bthid_session_p s); void hid_initialise (bthid_session_p s); Index: usr.sbin/bluetooth/bthidd/bthidd.8 =================================================================== --- usr.sbin/bluetooth/bthidd/bthidd.8 +++ usr.sbin/bluetooth/bthidd/bthidd.8 @@ -25,7 +25,7 @@ .\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $ .\" $FreeBSD$ .\" -.Dd September 7, 2006 +.Dd December 19, 2017 .Dt BTHIDD 8 .Os .Sh NAME @@ -40,6 +40,7 @@ .Op Fl H Ar file .Op Fl p Ar file .Op Fl t Ar val +.Op Fl u .Sh DESCRIPTION The .Nm @@ -82,6 +83,11 @@ .Dq passive Bluetooth HID devices and will attempt to establish an outgoing connection. The default rescan interval is 10 seconds. +.It Fl u +Enable support for input event device protocol. +Requires evdev and uinput drivers to be loaded with +.Xr kldload 8 +or compiled into the kernel. .El .Sh KNOWN LIMITATIONS The Index: usr.sbin/bluetooth/bthidd/bthidd.c =================================================================== --- usr.sbin/bluetooth/bthidd/bthidd.c +++ usr.sbin/bluetooth/bthidd/bthidd.c @@ -69,14 +69,15 @@ struct sigaction sa; char const *pid_file = BTHIDD_PIDFILE; char *ep; - int32_t opt, detach, tval; + int32_t opt, detach, tval, uinput; memset(&srv, 0, sizeof(srv)); memset(&srv.bdaddr, 0, sizeof(srv.bdaddr)); detach = 1; tval = 10; /* sec */ + uinput = 0; - while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) { + while ((opt = getopt(argc, argv, "a:c:dH:hp:t:u")) != -1) { switch (opt) { case 'a': /* BDADDR */ if (!bt_aton(optarg, &srv.bdaddr)) { @@ -111,6 +112,10 @@ usage(); break; + case 'u': /* enable evdev support */ + uinput = 1; + break; + case 'h': default: usage(); @@ -158,6 +163,8 @@ server_init(&srv) < 0 || write_pid_file(pid_file) < 0) exit(1); + srv.uinput = uinput; + for (done = 0; !done; ) { if (elapsed(tval)) client_rescan(&srv); @@ -263,6 +270,7 @@ " -h display this message\n" \ " -p file specify PID file name\n" \ " -t tval specify client rescan interval (sec)\n" \ +" -u enable evdev protocol support\n" \ "", BTHIDD_IDENT); exit(255); } Index: usr.sbin/bluetooth/bthidd/bthidd.conf.sample =================================================================== --- usr.sbin/bluetooth/bthidd/bthidd.conf.sample +++ usr.sbin/bluetooth/bthidd/bthidd.conf.sample @@ -2,6 +2,7 @@ device { bdaddr 00:50:f2:e5:68:84; + name "Bluetooth Mouse"; vendor_id 0x0000; product_id 0x0000; version 0x0000; @@ -27,6 +28,7 @@ device { bdaddr 00:50:f2:e3:fb:e1; + name "Bluetooth Keyboard"; vendor_id 0x0000; product_id 0x0000; version 0x0000; Index: usr.sbin/bluetooth/bthidd/btuinput.h =================================================================== --- /dev/null +++ usr.sbin/bluetooth/bthidd/btuinput.h @@ -0,0 +1,44 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2015-2017 Vladimir Kondratyev + * 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. + * + * $FreeBSD$ + */ + +#ifndef _UINPUT_H_ +#define _UINPUT_H_ + +int32_t uinput_open_mouse(hid_device_p const d, bdaddr_p local); +int32_t uinput_open_keyboard(hid_device_p const d, bdaddr_p local); +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); +int32_t uinput_rep_key(int32_t fd, int32_t key, int32_t make); +int32_t uinput_rep_cons(int32_t fd, int32_t key, int32_t make); +int32_t uinput_rep_leds(int32_t fd, int state, int mask); +int32_t uinput_kbd_status_changed(bthid_session_p s, uint8_t *data, + int32_t len); + +#endif /* ndef _UINPUT_H_ */ Index: usr.sbin/bluetooth/bthidd/btuinput.c =================================================================== --- /dev/null +++ usr.sbin/bluetooth/bthidd/btuinput.c @@ -0,0 +1,618 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2015-2017 Vladimir Kondratyev + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#define L2CAP_SOCKET_CHECKED +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} Index: usr.sbin/bluetooth/bthidd/client.c =================================================================== --- usr.sbin/bluetooth/bthidd/client.c +++ usr.sbin/bluetooth/bthidd/client.c @@ -188,14 +188,11 @@ s->state = OPEN; connect_in_progress = 0; - /* Register session's vkbd descriptor (if any) for read */ - if (s->state == OPEN && d->keyboard) { - assert(s->vkbd != -1); - - FD_SET(s->vkbd, &srv->rfdset); - if (s->vkbd > srv->maxfd) - srv->maxfd = s->vkbd; - } + /* Create kbd/mouse after both channels are established */ + if (session_run(s) < 0) { + session_close(s); + return (-1); + } break; default: Index: usr.sbin/bluetooth/bthidd/hid.c =================================================================== --- usr.sbin/bluetooth/bthidd/hid.c +++ usr.sbin/bluetooth/bthidd/hid.c @@ -50,6 +50,7 @@ #include #include "bthid_config.h" #include "bthidd.h" +#include "btuinput.h" #include "kbd.h" /* @@ -280,6 +281,19 @@ break; case HUP_CONSUMER: + if (hid_device->keyboard && s->srv->uinput) { + if (h.flags & HIO_VARIABLE) { + uinput_rep_cons(s->ukbd, usage, !!val); + } else { + if (s->consk > 0) + uinput_rep_cons(s->ukbd, + s->consk, 0); + if (uinput_rep_cons(s->ukbd, val, 1) + == 0) + s->consk = val; + } + } + if (!val) break; @@ -551,6 +565,14 @@ syslog(LOG_ERR, "Could not process mouse events from " \ "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno); + + if (hid_device->mouse && s->srv->uinput && + uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z, + mouse_t, mouse_butt, s->obutt) < 0) + syslog(LOG_ERR, "Could not process mouse events from " \ + "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + s->obutt = mouse_butt; } return (0); Index: usr.sbin/bluetooth/bthidd/kbd.c =================================================================== --- usr.sbin/bluetooth/bthidd/kbd.c +++ usr.sbin/bluetooth/bthidd/kbd.c @@ -56,10 +56,12 @@ #include #include "bthid_config.h" #include "bthidd.h" +#include "btuinput.h" #include "kbd.h" static void kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd); static int32_t kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob); +static void uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd); /* * HID code to PS/2 set 1 code translation table. @@ -354,6 +356,7 @@ if (f2 != -1) { /* release old keys */ kbd_write(s->keys2, f2, 0, s->vkbd); + uinput_kbd_write(s->keys2, f2, 0, s->ukbd); memset(s->keys2, 0, bitstr_size(xsize)); } @@ -366,6 +369,7 @@ memcpy(s->keys2, s->keys1, bitstr_size(xsize)); kbd_write(s->keys1, f1, 1, s->vkbd); + uinput_kbd_write(s->keys1, f1, 1, s->ukbd); memset(s->keys1, 0, bitstr_size(xsize)); return (0); @@ -393,18 +397,37 @@ } bit_ffs(diff, xsize, &f2); - if (f2 > 0) + if (f2 > 0) { kbd_write(diff, f2, 0, s->vkbd); + uinput_kbd_write(diff, f2, 0, s->ukbd); + } bit_ffs(s->keys1, xsize, &f1); if (f1 > 0) { kbd_write(s->keys1, f1, 1, s->vkbd); + uinput_kbd_write(s->keys1, f1, 1, s->ukbd); memset(s->keys1, 0, bitstr_size(xsize)); } return (0); } +/* + * Translate given keymap and write keyscodes + */ +void +uinput_kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd) +{ + int32_t i; + + if (fd >= 0) { + for (i = fb; i < xsize; i++) { + if (bit_test(m, i)) + uinput_rep_key(fd, i, make); + } + } +} + /* * Translate given keymap and write keyscodes */ @@ -520,6 +543,7 @@ hid_device_p hid_device; hid_data_t d; hid_item_t h; + uint8_t leds_mask = 0; assert(s != NULL); assert(len == sizeof(vkbd_status_t)); @@ -553,16 +577,19 @@ case 0x01: /* Num Lock LED */ if (st.leds & LED_NUM) hid_set_data(&data[1], &h, 1); + leds_mask |= LED_NUM; break; case 0x02: /* Caps Lock LED */ if (st.leds & LED_CAP) hid_set_data(&data[1], &h, 1); + leds_mask |= LED_CAP; break; case 0x03: /* Scroll Lock LED */ if (st.leds & LED_SCR) hid_set_data(&data[1], &h, 1); + leds_mask |= LED_SCR; break; /* XXX add other LEDs ? */ @@ -579,6 +606,9 @@ if (found) write(s->intr, data, (report_id != NO_REPORT_ID) ? 3 : 2); + if (found && s->srv->uinput && hid_device->keyboard) + uinput_rep_leds(s->ukbd, st.leds, leds_mask); + return (0); } Index: usr.sbin/bluetooth/bthidd/lexer.l =================================================================== --- usr.sbin/bluetooth/bthidd/lexer.l +++ usr.sbin/bluetooth/bthidd/lexer.l @@ -56,6 +56,7 @@ device_word device bdaddr_word bdaddr +name_word name vendor_id_word vendor_id product_id_word product_id version_word version @@ -71,6 +72,7 @@ bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte} hexbytestring 0x{hexbyte} hexwordstring 0x{hexword} +string \".+\" %% @@ -85,6 +87,7 @@ {device_word} return (T_DEVICE); {bdaddr_word} return (T_BDADDR); +{name_word} return (T_NAME); {vendor_id_word} return (T_VENDOR_ID); {product_id_word} return (T_PRODUCT_ID); {version_word} return (T_VERSION); @@ -118,6 +121,12 @@ return (*ep == '\0'? T_HEXWORD : T_ERROR); } +{string} { + yytext[strlen(yytext) - 1] = 0; + yylval.string = &yytext[1]; + return (T_STRING); + } + . return (T_ERROR); %% Index: usr.sbin/bluetooth/bthidd/parser.y =================================================================== --- usr.sbin/bluetooth/bthidd/parser.y +++ usr.sbin/bluetooth/bthidd/parser.y @@ -63,6 +63,8 @@ #define EOL "\n" #endif /* ndef BTHIDCONTROL */ +#define NAMELESS_DEVICE "No Name" + #include "bthid_config.h" int yylex (void); @@ -85,11 +87,14 @@ %union { bdaddr_t bdaddr; int32_t num; + char *string; } %token T_BDADDRSTRING %token T_HEXBYTE %token T_HEXWORD +%token T_STRING +%token T_NAME %token T_DEVICE T_BDADDR T_VENDOR_ID T_PRODUCT_ID T_VERSION T_CONTROL_PSM %token T_INTERRUPT_PSM T_RECONNECT_INITIATE T_BATTERY_POWER %token T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR @@ -128,6 +133,7 @@ ; option: bdaddr + | name | vendor_id | product_id | version @@ -146,6 +152,24 @@ } ; +name: T_NAME T_STRING + { + if (hid_device->name != NULL) { + free(hid_device->name); + hid_device->name = NULL; + } + + if (strcmp($2, NAMELESS_DEVICE)) { + hid_device->name = strdup($2); + if (hid_device->name == NULL) { + SYSLOG(LOGCRIT, "Could not allocate new " \ + "device name" EOL); + YYABORT; + } + } + } + ; + vendor_id: T_VENDOR_ID T_HEXWORD { hid_device->vendor_id = $2; @@ -332,6 +356,7 @@ fprintf(f, "device {\n" \ " bdaddr %s;\n" \ +" name \"%s\";\n" \ " vendor_id 0x%04x;\n" \ " product_id 0x%04x;\n" \ " version 0x%04x;\n" \ @@ -342,6 +367,7 @@ " normally_connectable %s;\n" \ " hid_descriptor {", bt_ntoa(&d->bdaddr, NULL), + (d->name != NULL)? d->name : NAMELESS_DEVICE, d->vendor_id, d->product_id, d->version, d->control_psm, d->interrupt_psm, d->reconnect_initiate? "true" : "false", @@ -367,7 +393,7 @@ { hid_data_t hd; hid_item_t hi; - int32_t page; + int32_t page, mdepth; if (get_hid_device(&d->bdaddr) != NULL) { SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL, @@ -390,11 +416,23 @@ return (0); } + mdepth = 0; + /* XXX somehow need to make sure descriptor is valid */ for (hd = hid_start_parse(d->desc, ~0, -1); hid_get_item(hd, &hi) > 0; ) { switch (hi.kind) { case hid_collection: + if (mdepth != 0) + mdepth++; + else if (hi.collection == 1 && + hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) + mdepth++; + break; case hid_endcollection: + if (mdepth != 0) + mdepth--; + break; case hid_output: case hid_feature: break; @@ -404,6 +442,28 @@ page = HID_PAGE(hi.usage); if (page == HUP_KEYBOARD) d->keyboard = 1; + if (page == HUP_CONSUMER && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == 0) + d->has_cons = 1; + /* Check if the device may send relative motion events */ + if (mdepth == 0) + break; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->mouse = 1; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->mouse = 1; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->has_wheel = 1; + if (hi.usage == + HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + d->has_hwheel = 1; break; } } @@ -419,6 +479,7 @@ if (d->desc != NULL) hid_dispose_report_desc(d->desc); + free(d->name); memset(d, 0, sizeof(*d)); free(d); } Index: usr.sbin/bluetooth/bthidd/server.c =================================================================== --- usr.sbin/bluetooth/bthidd/server.c +++ usr.sbin/bluetooth/bthidd/server.c @@ -37,6 +37,7 @@ #include #define L2CAP_SOCKET_CHECKED #include +#include #include #include #include @@ -48,6 +49,7 @@ #include #include "bthid_config.h" #include "bthidd.h" +#include "btuinput.h" #include "kbd.h" #undef max @@ -282,19 +284,12 @@ (fd == srv->ctrl)? "control" : "interrupt", bt_ntoa(&l2addr.l2cap_bdaddr, NULL)); - /* Register session's vkbd descriptor (if needed) for read */ - if (s->state == OPEN && d->keyboard) { - assert(s->vkbd != -1); - - FD_SET(s->vkbd, &srv->rfdset); - if (s->vkbd > srv->maxfd) - srv->maxfd = s->vkbd; + /* Create virtual kbd/mouse after both channels are established */ + if (s->state == OPEN && session_run(s) < 0) { + session_close(s); + return (-1); } - /* Pass device for probing after both channels are established */ - if (s->state == OPEN) - hid_initialise(s); - return (0); } @@ -309,9 +304,10 @@ int32_t len, to_read; int32_t (*cb)(bthid_session_p, uint8_t *, int32_t); union { - uint8_t b[1024]; - vkbd_status_t s; - } data; + uint8_t b[1024]; + vkbd_status_t s; + struct input_event ie; + } data; if (s == NULL) return (0); /* can happen on device disconnect */ @@ -323,6 +319,9 @@ } else if (fd == s->intr) { cb = hid_interrupt; to_read = sizeof(data.b); + } else if (fd == s->ukbd) { + cb = uinput_kbd_status_changed; + to_read = sizeof(data.ie); } else { assert(fd == s->vkbd); Index: usr.sbin/bluetooth/bthidd/session.c =================================================================== --- usr.sbin/bluetooth/bthidd/session.c +++ usr.sbin/bluetooth/bthidd/session.c @@ -47,6 +47,7 @@ #include #include "bthid_config.h" #include "bthidd.h" +#include "btuinput.h" #include "kbd.h" /* @@ -68,22 +69,12 @@ memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr)); s->ctrl = -1; s->intr = -1; + s->vkbd = -1; s->ctx = NULL; - - if (d->keyboard) { - /* Open /dev/vkbdctl */ - s->vkbd = open("/dev/vkbdctl", O_RDWR); - if (s->vkbd < 0) { - syslog(LOG_ERR, "Could not open /dev/vkbdctl " \ - "for %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), - strerror(errno), errno); - free(s); - return (NULL); - } - } else - s->vkbd = -1; - s->state = CLOSED; + s->ukbd = -1; + s->umouse = -1; + s->obutt = 0; s->keys1 = bit_alloc(kbd_maxkey()); if (s->keys1 == NULL) { @@ -103,6 +94,64 @@ return (s); } +/* + * Initialize virtual keyboard and mouse after both channels are established + */ + +int32_t +session_run(bthid_session_p s) +{ + hid_device_p d = get_hid_device(&s->bdaddr); + struct sockaddr_l2cap local; + socklen_t len; + + if (d->keyboard) { + /* Open /dev/vkbdctl */ + s->vkbd = open("/dev/vkbdctl", O_RDWR); + if (s->vkbd < 0) { + syslog(LOG_ERR, "Could not open /dev/vkbdctl " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL), + strerror(errno), errno); + return (-1); + } + /* Register session's vkbd descriptor (if needed) for read */ + FD_SET(s->vkbd, &s->srv->rfdset); + if (s->vkbd > s->srv->maxfd) + s->srv->maxfd = s->vkbd; + } + + /* Pass device for probing */ + hid_initialise(s); + + /* Take local bdaddr */ + len = sizeof(local); + getsockname(s->ctrl, (struct sockaddr *) &local, &len); + + if (d->mouse && s->srv->uinput) { + s->umouse = uinput_open_mouse(d, &local.l2cap_bdaddr); + if (s->umouse < 0) { + syslog(LOG_ERR, "Could not open /dev/uinput " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, + NULL), strerror(errno), errno); + return (-1); + } + } + if (d->keyboard && s->srv->uinput) { + s->ukbd = uinput_open_keyboard(d, &local.l2cap_bdaddr); + if (s->ukbd < 0) { + syslog(LOG_ERR, "Could not open /dev/uinput " \ + "for %s. %s (%d)", bt_ntoa(&s->bdaddr, + NULL), strerror(errno), errno); + return (-1); + } + /* Register session's ukbd descriptor (if needed) for read */ + FD_SET(s->ukbd, &s->srv->rfdset); + if (s->ukbd > s->srv->maxfd) + s->srv->maxfd = s->ukbd; + } + return (0); +} + /* * Lookup session by bdaddr */ @@ -135,7 +184,8 @@ assert(fd >= 0); LIST_FOREACH(s, &srv->sessions, next) - if (s->ctrl == fd || s->intr == fd || s->vkbd == fd) + if (s->ctrl == fd || s->intr == fd || + s->vkbd == fd || s->ukbd == fd) break; return (s); @@ -179,6 +229,17 @@ s->srv->maxfd --; } + if (s->umouse != -1) + close(s->umouse); + + if (s->ukbd != -1) { + FD_CLR(s->ukbd, &s->srv->rfdset); + close(s->ukbd); + + if (s->srv->maxfd == s->ukbd) + s->srv->maxfd --; + } + free(s->ctx); free(s->keys1); free(s->keys2);