summaryrefslogtreecommitdiffstats
path: root/usr.sbin/bluetooth/bthidd
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/bluetooth/bthidd')
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile16
-rw-r--r--usr.sbin/bluetooth/bthidd/Makefile.depend27
-rw-r--r--usr.sbin/bluetooth/bthidd/bthid_config.h70
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.8127
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.c267
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.conf.sample72
-rw-r--r--usr.sbin/bluetooth/bthidd/bthidd.h93
-rw-r--r--usr.sbin/bluetooth/bthidd/client.c259
-rw-r--r--usr.sbin/bluetooth/bthidd/hid.c415
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.c581
-rw-r--r--usr.sbin/bluetooth/bthidd/kbd.h41
-rw-r--r--usr.sbin/bluetooth/bthidd/lexer.l106
-rw-r--r--usr.sbin/bluetooth/bthidd/parser.y476
-rw-r--r--usr.sbin/bluetooth/bthidd/server.c352
-rw-r--r--usr.sbin/bluetooth/bthidd/session.c185
15 files changed, 3087 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/bthidd/Makefile b/usr.sbin/bluetooth/bthidd/Makefile
new file mode 100644
index 0000000..f36d216
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/Makefile
@@ -0,0 +1,16 @@
+# $Id: Makefile,v 1.6 2006/09/07 21:36:55 max Exp $
+# $FreeBSD$
+
+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
+
+CFLAGS+= -I${.CURDIR}
+
+LIBADD+= bluetooth usbhid
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/bluetooth/bthidd/Makefile.depend b/usr.sbin/bluetooth/bthidd/Makefile.depend
new file mode 100644
index 0000000..a1ed9d7
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/Makefile.depend
@@ -0,0 +1,27 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libbluetooth \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libusbhid \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+lexer.o: lexer.c
+lexer.o: parser.h
+lexer.po: lexer.c
+lexer.po: parser.h
+parser.o: parser.c
+parser.po: parser.c
+.endif
diff --git a/usr.sbin/bluetooth/bthidd/bthid_config.h b/usr.sbin/bluetooth/bthidd/bthid_config.h
new file mode 100644
index 0000000..71cb425
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthid_config.h
@@ -0,0 +1,70 @@
+/*
+ * bthid_config.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: bthid_config.h,v 1.4 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _BTHID_CONFIG_H_
+#define _BTHID_CONFIG_H_ 1
+
+#define BTHIDD_CONFFILE "/etc/bluetooth/bthidd.conf"
+#define BTHIDD_HIDSFILE "/var/db/bthidd.hids"
+
+struct hid_device
+{
+ bdaddr_t bdaddr; /* HID device BDADDR */
+ uint16_t control_psm; /* control PSM */
+ uint16_t interrupt_psm; /* interrupt PSM */
+ unsigned new_device : 1;
+ unsigned reconnect_initiate : 1;
+ unsigned battery_power : 1;
+ unsigned normally_connectable : 1;
+ unsigned keyboard : 1;
+ unsigned reserved : 11;
+ report_desc_t desc; /* HID report descriptor */
+ LIST_ENTRY(hid_device) next; /* link to the next */
+};
+typedef struct hid_device hid_device_t;
+typedef struct hid_device * hid_device_p;
+
+extern char const *config_file;
+extern char const *hids_file;
+
+int32_t read_config_file (void);
+void clean_config (void);
+hid_device_p get_hid_device (bdaddr_p bdaddr);
+hid_device_p get_next_hid_device (hid_device_p d);
+void print_hid_device (hid_device_p hid_device, FILE *f);
+
+int32_t read_hids_file (void);
+int32_t write_hids_file (void);
+
+#endif /* ndef _BTHID_CONFIG_H_ */
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.8 b/usr.sbin/bluetooth/bthidd/bthidd.8
new file mode 100644
index 0000000..53db4cb
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.8
@@ -0,0 +1,127 @@
+.\" Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+.\" 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.
+.\"
+.\" $Id: bthidd.8,v 1.1 2006/09/07 21:36:55 max Exp $
+.\" $FreeBSD$
+.\"
+.Dd September 7, 2006
+.Dt BTHIDD 8
+.Os
+.Sh NAME
+.Nm bthidd
+.Nd Bluetooth HID daemon
+.Sh SYNOPSIS
+.Nm
+.Fl h
+.Nm
+.Op Fl a Ar BD_ADDR
+.Op Fl c Ar file
+.Op Fl H Ar file
+.Op Fl p Ar file
+.Op Fl t Ar val
+.Sh DESCRIPTION
+The
+.Nm
+daemon handles remote Bluetooth HID devices.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl a Ar BD_ADDR
+Specify the local address to listen on.
+By default, the server will listen on
+.Dv ANY
+address.
+The address can be specified as BD_ADDR or name.
+If a name was specified, the
+.Nm
+daemon will attempt to resolve the name via
+.Xr bt_gethostbyname 3 .
+.It Fl c Ar file
+Specify path to the configuration file.
+The default path is
+.Pa /etc/bluetooth/bthidd.conf .
+.It Fl d
+Do not detach from the controlling terminal, i.e., run in foreground.
+.It Fl H Ar file
+Specify path to the known HIDs file.
+The default path is
+.Pa /var/db/bthidd.hids .
+.It Fl h
+Display usage message and exit.
+.It Fl p Ar file
+Specify path to the PID file.
+The default path is
+.Pa /var/run/bthidd.pid .
+.It Fl t Ar val
+Specify client rescan interval in seconds.
+The
+.Nm
+daemon will periodically scan for newly configured Bluetooth HID devices or
+disconnected
+.Dq passive
+Bluetooth HID devices and will attempt to establish an outgoing connection.
+The default rescan interval is 10 seconds.
+.El
+.Sh KNOWN LIMITATIONS
+The
+.Nm
+daemon currently does not handle key auto repeat and double click mouse events.
+Those events work under
+.Xr X 7
+just fine,
+but not in text console.
+.Pp
+This manual page needs more work.
+A manual page documenting the format of the
+.Pa /etc/bluetooth/bthidd.conf
+configuration file is needed as well.
+.Sh FILES
+.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact
+.It Pa /etc/bluetooth/bthidd.conf
+.It Pa /var/db/bthidd.hids
+.It Pa /var/run/bthidd.pid
+.El
+.Sh SEE ALSO
+.Xr kbdmux 4 ,
+.Xr vkbd 4 ,
+.Xr bthidcontrol 8
+.Sh AUTHORS
+.An Maksim Yevmenkin Aq Mt m_evmenkin@yahoo.com
+.Sh CAVEATS
+Any Bluetooth HID device that has
+.Dv HUP_KEYBOARD
+or
+.Dv HUP_CONSUMER
+entries in its descriptor is considered as
+.Dq keyboard .
+For each
+.Dq keyboard
+Bluetooth HID device,
+the
+.Nm
+daemon will use a separate instance of the virtual keyboard interface
+.Xr vkbd 4 .
+Therefore the
+.Xr kbdmux 4
+driver must be used to properly multiplex input from multiple keyboards.
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.c b/usr.sbin/bluetooth/bthidd/bthidd.c
new file mode 100644
index 0000000..7e988fc
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.c
@@ -0,0 +1,267 @@
+/*
+ * bthidd.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: bthidd.c,v 1.8 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <err.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+
+static int32_t write_pid_file (char const *file);
+static int32_t remove_pid_file (char const *file);
+static int32_t elapsed (int32_t tval);
+static void sighandler (int32_t s);
+static void usage (void);
+
+/*
+ * bthidd
+ */
+
+static int32_t done = 0; /* are we done? */
+
+int32_t
+main(int32_t argc, char *argv[])
+{
+ struct bthid_server srv;
+ struct sigaction sa;
+ char const *pid_file = BTHIDD_PIDFILE;
+ char *ep;
+ int32_t opt, detach, tval;
+
+ memset(&srv, 0, sizeof(srv));
+ memset(&srv.bdaddr, 0, sizeof(srv.bdaddr));
+ detach = 1;
+ tval = 10; /* sec */
+
+ while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) {
+ switch (opt) {
+ case 'a': /* BDADDR */
+ if (!bt_aton(optarg, &srv.bdaddr)) {
+ struct hostent *he;
+
+ if ((he = bt_gethostbyname(optarg)) == NULL)
+ errx(1, "%s: %s", optarg, hstrerror(h_errno));
+
+ memcpy(&srv.bdaddr, he->h_addr, sizeof(srv.bdaddr));
+ }
+ break;
+
+ case 'c': /* config file */
+ config_file = optarg;
+ break;
+
+ case 'd': /* do not detach */
+ detach = 0;
+ break;
+
+ case 'H': /* hids file */
+ hids_file = optarg;
+ break;
+
+ case 'p': /* pid file */
+ pid_file = optarg;
+ break;
+
+ case 't': /* rescan interval */
+ tval = strtol(optarg, (char **) &ep, 10);
+ if (*ep != '\0' || tval <= 0)
+ usage();
+ break;
+
+ case 'h':
+ default:
+ usage();
+ /* NOT REACHED */
+ }
+ }
+
+ openlog(BTHIDD_IDENT, LOG_PID|LOG_PERROR|LOG_NDELAY, LOG_USER);
+
+ /* Become daemon if required */
+ if (detach && daemon(0, 0) < 0) {
+ syslog(LOG_CRIT, "Could not become daemon. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Install signal handler */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = sighandler;
+
+ if (sigaction(SIGTERM, &sa, NULL) < 0 ||
+ sigaction(SIGHUP, &sa, NULL) < 0 ||
+ sigaction(SIGINT, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT;
+ if (sigaction(SIGCHLD, &sa, NULL) < 0) {
+ syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ if (read_config_file() < 0 || read_hids_file() < 0 ||
+ server_init(&srv) < 0 || write_pid_file(pid_file) < 0)
+ exit(1);
+
+ for (done = 0; !done; ) {
+ if (elapsed(tval))
+ client_rescan(&srv);
+
+ if (server_do(&srv) < 0)
+ break;
+ }
+
+ server_shutdown(&srv);
+ remove_pid_file(pid_file);
+ clean_config();
+ closelog();
+
+ return (0);
+}
+
+/*
+ * Write pid file
+ */
+
+static int32_t
+write_pid_file(char const *file)
+{
+ FILE *pid;
+
+ assert(file != NULL);
+
+ if ((pid = fopen(file, "w")) == NULL) {
+ syslog(LOG_ERR, "Could not open file %s. %s (%d)",
+ file, strerror(errno), errno);
+ return (-1);
+ }
+
+ fprintf(pid, "%d", getpid());
+ fclose(pid);
+
+ return (0);
+}
+
+/*
+ * Remote pid file
+ */
+
+static int32_t
+remove_pid_file(char const *file)
+{
+ assert(file != NULL);
+
+ if (unlink(file) < 0) {
+ syslog(LOG_ERR, "Could not unlink file %s. %s (%d)",
+ file, strerror(errno), errno);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Returns true if desired time interval has elapsed
+ */
+
+static int32_t
+elapsed(int32_t tval)
+{
+ static struct timeval last = { 0, 0 };
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ if (now.tv_sec - last.tv_sec >= tval) {
+ last = now;
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * Signal handler
+ */
+
+static void
+sighandler(int32_t s)
+{
+ syslog(LOG_NOTICE, "Got signal %d, total number of signals %d",
+ s, ++ done);
+}
+
+/*
+ * Display usage and exit
+ */
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [options]\n" \
+"Where options are:\n" \
+" -a address specify address to listen on (default ANY)\n" \
+" -c file specify config file name\n" \
+" -d run in foreground\n" \
+" -H file specify known HIDs file name\n" \
+" -h display this message\n" \
+" -p file specify PID file name\n" \
+" -t tval specify client rescan interval (sec)\n" \
+"", BTHIDD_IDENT);
+ exit(255);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.conf.sample b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample
new file mode 100644
index 0000000..e23dc12
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.conf.sample
@@ -0,0 +1,72 @@
+# $FreeBSD$
+
+device {
+ bdaddr 00:50:f2:e5:68:84;
+ control_psm 0x11;
+ interrupt_psm 0x13;
+ reconnect_initiate true;
+ normally_connectable false;
+ hid_descriptor {
+ 0x05 0x01 0x09 0x02 0xa1 0x01 0x85 0x02
+ 0x09 0x01 0xa1 0x00 0x05 0x09 0x19 0x01
+ 0x29 0x05 0x15 0x00 0x25 0x01 0x75 0x01
+ 0x95 0x05 0x81 0x02 0x75 0x03 0x95 0x01
+ 0x81 0x01 0x05 0x01 0x09 0x30 0x09 0x31
+ 0x09 0x38 0x15 0x81 0x25 0x7f 0x75 0x08
+ 0x95 0x03 0x81 0x06 0xc0 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0x03 0x05 0x01
+ 0x09 0x02 0xa1 0x02 0x06 0x00 0xff 0x15
+ 0x00 0x25 0x03 0x95 0x01 0x75 0x02 0x0a
+ 0x01 0xfe 0x81 0x02 0x75 0x06 0x81 0x01
+ 0xc0 0xc0
+ };
+}
+
+device {
+ bdaddr 00:50:f2:e3:fb:e1;
+ control_psm 0x11;
+ interrupt_psm 0x13;
+ reconnect_initiate true;
+ normally_connectable false;
+ hid_descriptor {
+ 0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01
+ 0x05 0x08 0x19 0x01 0x29 0x03 0x15 0x00
+ 0x25 0x01 0x75 0x01 0x95 0x03 0x91 0x02
+ 0x09 0x4b 0x95 0x01 0x91 0x02 0x95 0x04
+ 0x91 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7
+ 0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01
+ 0x81 0x01 0x19 0x00 0x29 0x91 0x26 0xff
+ 0x00 0x95 0x06 0x81 0x00 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0x02 0x05 0x0c
+ 0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x1c
+ 0x09 0xe2 0x09 0xb7 0x09 0xcd 0x09 0xea
+ 0x09 0xe9 0x09 0xb6 0x09 0xb5 0x0a 0x83
+ 0x01 0x0a 0x1a 0x02 0x0a 0x79 0x02 0x0a
+ 0xab 0x01 0x0a 0x08 0x02 0x0a 0x02 0x02
+ 0x0a 0x03 0x02 0x0a 0x07 0x02 0x0a 0x01
+ 0x02 0x0a 0x92 0x01 0x0a 0x9c 0x01 0x09
+ 0x95 0x0a 0x23 0x02 0x0a 0x89 0x02 0x0a
+ 0x8b 0x02 0x0a 0x8c 0x02 0x0a 0x8a 0x01
+ 0x0a 0x99 0x01 0x0a 0xa7 0x01 0x0a 0xb6
+ 0x01 0x0a 0xb7 0x01 0x81 0x02 0x75 0x01
+ 0x95 0x04 0x81 0x01 0x06 0x00 0xff 0x0a
+ 0x02 0xff 0x26 0xff 0x00 0x95 0x01 0x75
+ 0x08 0x81 0x02 0xc0 0x05 0x01 0x09 0x80
+ 0xa1 0x01 0x85 0x03 0x19 0x81 0x29 0x83
+ 0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02
+ 0x95 0x05 0x81 0x01 0xc0 0x05 0x0c 0x09
+ 0x01 0xa1 0x01 0x85 0x04 0x05 0x01 0x09
+ 0x06 0xa1 0x02 0x06 0x00 0xff 0x15 0x00
+ 0x25 0x03 0x95 0x01 0x75 0x02 0x0a 0x01
+ 0xfe 0x81 0x02 0x75 0x06 0x81 0x01 0xc0
+ 0xc0 0x05 0x0c 0x09 0x01 0xa1 0x01 0x85
+ 0x05 0x05 0x01 0x09 0x06 0xa1 0x02 0x06
+ 0x00 0xff 0x25 0x01 0x75 0x01 0x95 0x02
+ 0x0a 0x03 0xfe 0x0a 0x04 0xfe 0x81 0x02
+ 0x95 0x06 0x81 0x01 0xc0 0xc0 0x05 0x0c
+ 0x09 0x01 0xa1 0x01 0x85 0xff 0x05 0x06
+ 0x95 0x01 0x75 0x02 0x19 0x24 0x29 0x26
+ 0x81 0x02 0x75 0x06 0x81 0x01 0xc0
+ };
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/bthidd.h b/usr.sbin/bluetooth/bthidd/bthidd.h
new file mode 100644
index 0000000..3485fc3
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/bthidd.h
@@ -0,0 +1,93 @@
+/*
+ * bthidd.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: bthidd.h,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _BTHIDD_H_
+#define _BTHIDD_H_ 1
+
+#define BTHIDD_IDENT "bthidd"
+#define BTHIDD_PIDFILE "/var/run/" BTHIDD_IDENT ".pid"
+
+struct bthid_session;
+
+struct bthid_server
+{
+ bdaddr_t bdaddr; /* local bdaddr */
+ int32_t cons; /* /dev/consolectl */
+ int32_t ctrl; /* control channel (listen) */
+ int32_t intr; /* intr. channel (listen) */
+ int32_t maxfd; /* max fd in sets */
+ fd_set rfdset; /* read descriptor set */
+ fd_set wfdset; /* write descriptor set */
+ LIST_HEAD(, bthid_session) sessions;
+};
+
+typedef struct bthid_server bthid_server_t;
+typedef struct bthid_server * bthid_server_p;
+
+struct bthid_session
+{
+ bthid_server_p srv; /* pointer back to server */
+ int32_t ctrl; /* control channel */
+ int32_t intr; /* interrupt channel */
+ int32_t vkbd; /* virual keyboard */
+ bdaddr_t bdaddr;/* remote bdaddr */
+ uint16_t state; /* session state */
+#define CLOSED 0
+#define W4CTRL 1
+#define W4INTR 2
+#define OPEN 3
+ bitstr_t *keys1; /* keys map (new) */
+ bitstr_t *keys2; /* keys map (old) */
+ LIST_ENTRY(bthid_session) next; /* link to next */
+};
+
+typedef struct bthid_session bthid_session_t;
+typedef struct bthid_session * bthid_session_p;
+
+int32_t server_init (bthid_server_p srv);
+void server_shutdown (bthid_server_p srv);
+int32_t server_do (bthid_server_p srv);
+
+int32_t client_rescan (bthid_server_p srv);
+int32_t client_connect (bthid_server_p srv, int fd);
+
+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);
+void session_close (bthid_session_p s);
+
+int32_t hid_control (bthid_session_p s, uint8_t *data, int32_t len);
+int32_t hid_interrupt (bthid_session_p s, uint8_t *data, int32_t len);
+
+#endif /* ndef _BTHIDD_H_ */
+
diff --git a/usr.sbin/bluetooth/bthidd/client.c b/usr.sbin/bluetooth/bthidd/client.c
new file mode 100644
index 0000000..5f01133
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/client.c
@@ -0,0 +1,259 @@
+/*
+ * client.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: client.c,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+
+static int32_t client_socket(bdaddr_p bdaddr, uint16_t psm);
+
+/*
+ * Get next config entry and create outbound connection (if required)
+ *
+ * XXX Do only one device at a time. At least one of my devices (3COM
+ * Bluetooth PCCARD) rejects Create_Connection command if another
+ * Create_Connection command is still pending. Weird...
+ */
+
+static int32_t connect_in_progress = 0;
+
+int32_t
+client_rescan(bthid_server_p srv)
+{
+ static hid_device_p d;
+ bthid_session_p s;
+
+ assert(srv != NULL);
+
+ if (connect_in_progress)
+ return (0); /* another connect is still pending */
+
+ d = get_next_hid_device(d);
+ if (d == NULL)
+ return (0); /* XXX should not happen? empty config? */
+
+ if ((s = session_by_bdaddr(srv, &d->bdaddr)) != NULL)
+ return (0); /* session already active */
+
+ if (!d->new_device) {
+ if (d->reconnect_initiate)
+ return (0); /* device will initiate reconnect */
+ }
+
+ syslog(LOG_NOTICE, "Opening outbound session for %s " \
+ "(new_device=%d, reconnect_initiate=%d)",
+ bt_ntoa(&d->bdaddr, NULL), d->new_device, d->reconnect_initiate);
+
+ if ((s = session_open(srv, d)) == NULL) {
+ syslog(LOG_CRIT, "Could not create outbound session for %s",
+ bt_ntoa(&d->bdaddr, NULL));
+ return (-1);
+ }
+
+ /* Open control channel */
+ s->ctrl = client_socket(&s->bdaddr, d->control_psm);
+ if (s->ctrl < 0) {
+ syslog(LOG_ERR, "Could not open control channel to %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
+ session_close(s);
+ return (-1);
+ }
+
+ s->state = W4CTRL;
+
+ FD_SET(s->ctrl, &srv->wfdset);
+ if (s->ctrl > srv->maxfd)
+ srv->maxfd = s->ctrl;
+
+ connect_in_progress = 1;
+
+ return (0);
+}
+
+/*
+ * Process connect on the socket
+ */
+
+int32_t
+client_connect(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+ hid_device_p d;
+ int32_t error;
+ socklen_t len;
+
+ assert(srv != NULL);
+ assert(fd >= 0);
+
+ s = session_by_fd(srv, fd);
+ assert(s != NULL);
+
+ d = get_hid_device(&s->bdaddr);
+ assert(d != NULL);
+
+ error = 0;
+ len = sizeof(error);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+ syslog(LOG_ERR, "Could not get socket error for %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (-1);
+ }
+
+ if (error != 0) {
+ syslog(LOG_ERR, "Could not connect to %s. %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL), strerror(error), error);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (0);
+ }
+
+ switch (s->state) {
+ case W4CTRL: /* Control channel is open */
+ assert(s->ctrl == fd);
+ assert(s->intr == -1);
+
+ /* Open interrupt channel */
+ s->intr = client_socket(&s->bdaddr, d->interrupt_psm);
+ if (s->intr < 0) {
+ syslog(LOG_ERR, "Could not open interrupt channel " \
+ "to %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+ strerror(errno), errno);
+ session_close(s);
+ connect_in_progress = 0;
+
+ return (-1);
+ }
+
+ s->state = W4INTR;
+
+ FD_SET(s->intr, &srv->wfdset);
+ if (s->intr > srv->maxfd)
+ srv->maxfd = s->intr;
+
+ d->new_device = 0; /* reset new device flag */
+ write_hids_file();
+ break;
+
+ case W4INTR: /* Interrupt channel is open */
+ assert(s->ctrl != -1);
+ assert(s->intr == fd);
+
+ 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;
+ }
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ /* Move fd to from the write fd set into read fd set */
+ FD_CLR(fd, &srv->wfdset);
+ FD_SET(fd, &srv->rfdset);
+
+ return (0);
+}
+
+/*
+ * Create bound non-blocking socket and initiate connect
+ */
+
+static int
+client_socket(bdaddr_p bdaddr, uint16_t psm)
+{
+ struct sockaddr_l2cap l2addr;
+ int32_t s, m;
+
+ s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (s < 0)
+ return (-1);
+
+ m = fcntl(s, F_GETFL);
+ if (m < 0) {
+ close(s);
+ return (-1);
+ }
+
+ if (fcntl(s, F_SETFL, (m|O_NONBLOCK)) < 0) {
+ close(s);
+ return (-1);
+ }
+
+ l2addr.l2cap_len = sizeof(l2addr);
+ l2addr.l2cap_family = AF_BLUETOOTH;
+ memset(&l2addr.l2cap_bdaddr, 0, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = 0;
+ l2addr.l2cap_bdaddr_type = BDADDR_BREDR;
+ l2addr.l2cap_cid = 0;
+
+ if (bind(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ close(s);
+ return (-1);
+ }
+
+ memcpy(&l2addr.l2cap_bdaddr, bdaddr, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = htole16(psm);
+
+ if (connect(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0 &&
+ errno != EINPROGRESS) {
+ close(s);
+ return (-1);
+ }
+
+ return (s);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/hid.c b/usr.sbin/bluetooth/bthidd/hid.c
new file mode 100644
index 0000000..69a6fdc
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/hid.c
@@ -0,0 +1,415 @@
+/*
+ * hid.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/consio.h>
+#include <sys/mouse.h>
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+/*
+ * Process data from control channel
+ */
+
+int32_t
+hid_control(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ assert(s != NULL);
+ assert(data != NULL);
+ assert(len > 0);
+
+ switch (data[0] >> 4) {
+ case 0: /* Handshake (response to command) */
+ if (data[0] & 0xf)
+ syslog(LOG_ERR, "Got handshake message with error " \
+ "response 0x%x from %s",
+ data[0], bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 1: /* HID Control */
+ switch (data[0] & 0xf) {
+ case 0: /* NOP */
+ break;
+
+ case 1: /* Hard reset */
+ case 2: /* Soft reset */
+ syslog(LOG_WARNING, "Device %s requested %s reset",
+ bt_ntoa(&s->bdaddr, NULL),
+ ((data[0] & 0xf) == 1)? "hard" : "soft");
+ break;
+
+ case 3: /* Suspend */
+ syslog(LOG_NOTICE, "Device %s requested Suspend",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 4: /* Exit suspend */
+ syslog(LOG_NOTICE, "Device %s requested Exit Suspend",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 5: /* Virtual cable unplug */
+ syslog(LOG_NOTICE, "Device %s unplugged virtual cable",
+ bt_ntoa(&s->bdaddr, NULL));
+ session_close(s);
+ break;
+
+ default:
+ syslog(LOG_WARNING, "Device %s sent unknown " \
+ "HID_Control message 0x%x",
+ bt_ntoa(&s->bdaddr, NULL), data[0]);
+ break;
+ }
+ break;
+
+ default:
+ syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \
+ "channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL));
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * Process data from the interrupt channel
+ */
+
+int32_t
+hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ hid_device_p hid_device;
+ hid_data_t d;
+ hid_item_t h;
+ int32_t report_id, usage, page, val,
+ mouse_x, mouse_y, mouse_z, mouse_butt,
+ mevents, kevents, i;
+
+ assert(s != NULL);
+ assert(s->srv != NULL);
+ assert(data != NULL);
+
+ if (len < 3) {
+ syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \
+ "channel from %s", len, bt_ntoa(&s->bdaddr, NULL));
+ return (-1);
+ }
+
+ if (data[0] != 0xa1) {
+ syslog(LOG_ERR, "Got unexpected message 0x%x on " \
+ "Interrupt channel from %s",
+ data[0], bt_ntoa(&s->bdaddr, NULL));
+ return (-1);
+ }
+
+ report_id = data[1];
+ data ++;
+ len --;
+
+ hid_device = get_hid_device(&s->bdaddr);
+ assert(hid_device != NULL);
+
+ mouse_x = mouse_y = mouse_z = mouse_butt = mevents = kevents = 0;
+
+ for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1);
+ hid_get_item(d, &h) > 0; ) {
+ if ((h.flags & HIO_CONST) || (h.report_ID != report_id) ||
+ (h.kind != hid_input))
+ continue;
+
+ page = HID_PAGE(h.usage);
+ val = hid_get_data(data, &h);
+
+ /*
+ * When the input field is an array and the usage is specified
+ * with a range instead of an ID, we have to derive the actual
+ * usage by using the item value as an index in the usage range
+ * list.
+ */
+ if ((h.flags & HIO_VARIABLE)) {
+ usage = HID_USAGE(h.usage);
+ } else {
+ const uint32_t usage_offset = val - h.logical_minimum;
+ usage = HID_USAGE(h.usage_minimum + usage_offset);
+ }
+
+ switch (page) {
+ case HUP_GENERIC_DESKTOP:
+ switch (usage) {
+ case HUG_X:
+ mouse_x = val;
+ mevents ++;
+ break;
+
+ case HUG_Y:
+ mouse_y = val;
+ mevents ++;
+ break;
+
+ case HUG_WHEEL:
+ mouse_z = -val;
+ mevents ++;
+ break;
+
+ case HUG_SYSTEM_SLEEP:
+ if (val)
+ syslog(LOG_NOTICE, "Sleep button pressed");
+ break;
+ }
+ break;
+
+ case HUP_KEYBOARD:
+ kevents ++;
+
+ if (h.flags & HIO_VARIABLE) {
+ if (val && usage < kbd_maxkey())
+ bit_set(s->keys1, usage);
+ } else {
+ if (val && val < kbd_maxkey())
+ bit_set(s->keys1, val);
+
+ for (i = 1; i < h.report_count; i++) {
+ h.pos += h.report_size;
+ val = hid_get_data(data, &h);
+ if (val && val < kbd_maxkey())
+ bit_set(s->keys1, val);
+ }
+ }
+ break;
+
+ case HUP_BUTTON:
+ if (usage != 0) {
+ if (usage == 2)
+ usage = 3;
+ else if (usage == 3)
+ usage = 2;
+
+ mouse_butt |= (val << (usage - 1));
+ mevents ++;
+ }
+ break;
+
+ case HUP_CONSUMER:
+ if (!val)
+ break;
+
+ switch (usage) {
+ case HUC_AC_PAN:
+ /* Horizontal scroll */
+ if (val < 0)
+ mouse_butt |= (1 << 5);
+ else
+ mouse_butt |= (1 << 6);
+
+ mevents ++;
+ val = 0;
+ break;
+
+ case 0xb5: /* Scan Next Track */
+ val = 0x19;
+ break;
+
+ case 0xb6: /* Scan Previous Track */
+ val = 0x10;
+ break;
+
+ case 0xb7: /* Stop */
+ val = 0x24;
+ break;
+
+ case 0xcd: /* Play/Pause */
+ val = 0x22;
+ break;
+
+ case 0xe2: /* Mute */
+ val = 0x20;
+ break;
+
+ case 0xe9: /* Volume Up */
+ val = 0x30;
+ break;
+
+ case 0xea: /* Volume Down */
+ val = 0x2E;
+ break;
+
+ case 0x183: /* Media Select */
+ val = 0x6D;
+ break;
+
+ case 0x018a: /* Mail */
+ val = 0x6C;
+ break;
+
+ case 0x192: /* Calculator */
+ val = 0x21;
+ break;
+
+ case 0x194: /* My Computer */
+ val = 0x6B;
+ break;
+
+ case 0x221: /* WWW Search */
+ val = 0x65;
+ break;
+
+ case 0x223: /* WWW Home */
+ val = 0x32;
+ break;
+
+ case 0x224: /* WWW Back */
+ val = 0x6A;
+ break;
+
+ case 0x225: /* WWW Forward */
+ val = 0x69;
+ break;
+
+ case 0x226: /* WWW Stop */
+ val = 0x68;
+ break;
+
+ case 0x227: /* WWW Refresh */
+ val = 0x67;
+ break;
+
+ case 0x22a: /* WWW Favorites */
+ val = 0x66;
+ break;
+
+ default:
+ val = 0;
+ break;
+ }
+
+ /* XXX FIXME - UGLY HACK */
+ if (val != 0) {
+ if (hid_device->keyboard) {
+ int32_t buf[4] = { 0xe0, val,
+ 0xe0, val|0x80 };
+
+ assert(s->vkbd != -1);
+ write(s->vkbd, buf, sizeof(buf));
+ } else
+ syslog(LOG_ERR, "Keyboard events " \
+ "received from non-keyboard " \
+ "device %s. Please report",
+ bt_ntoa(&s->bdaddr, NULL));
+ }
+ break;
+
+ case HUP_MICROSOFT:
+ switch (usage) {
+ case 0xfe01:
+ if (!hid_device->battery_power)
+ break;
+
+ switch (val) {
+ case 1:
+ syslog(LOG_INFO, "Battery is OK on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 2:
+ syslog(LOG_NOTICE, "Low battery on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+
+ case 3:
+ syslog(LOG_WARNING, "Very low battery "\
+ "on %s",
+ bt_ntoa(&s->bdaddr, NULL));
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ }
+ hid_end_parse(d);
+
+ /*
+ * XXX FIXME Feed keyboard events into kernel.
+ * The code below works, bit host also needs to track
+ * and handle repeat.
+ *
+ * Key repeat currently works in X, but not in console.
+ */
+
+ if (kevents > 0) {
+ if (hid_device->keyboard) {
+ assert(s->vkbd != -1);
+ kbd_process_keys(s);
+ } else
+ syslog(LOG_ERR, "Keyboard events received from " \
+ "non-keyboard device %s. Please report",
+ bt_ntoa(&s->bdaddr, NULL));
+ }
+
+ /*
+ * XXX FIXME Feed mouse events into kernel.
+ * The code block below works, but it is not good enough.
+ * Need to track double-clicks etc.
+ *
+ * Double click currently works in X, but not in console.
+ */
+
+ if (mevents > 0) {
+ struct mouse_info mi;
+
+ mi.operation = MOUSE_ACTION;
+ mi.u.data.x = mouse_x;
+ mi.u.data.y = mouse_y;
+ mi.u.data.z = mouse_z;
+ mi.u.data.buttons = mouse_butt;
+
+ if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
+ syslog(LOG_ERR, "Could not process mouse events from " \
+ "%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
+ strerror(errno), errno);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/bluetooth/bthidd/kbd.c b/usr.sbin/bluetooth/bthidd/kbd.c
new file mode 100644
index 0000000..cd9f70b
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/kbd.c
@@ -0,0 +1,581 @@
+/*
+ * kbd.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: kbd.c,v 1.4 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/consio.h>
+#include <sys/ioctl.h>
+#include <sys/kbio.h>
+#include <sys/queue.h>
+#include <sys/wait.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <dev/vkbd/vkbd_var.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.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);
+
+/*
+ * HID code to PS/2 set 1 code translation table.
+ *
+ * http://www.microsoft.com/whdc/device/input/Scancode.mspx
+ *
+ * The table only contains "make" (key pressed) codes.
+ * The "break" (key released) code is generated as "make" | 0x80
+ */
+
+#define E0PREFIX (1U << 31)
+#define NOBREAK (1 << 30)
+#define CODEMASK (~(E0PREFIX|NOBREAK))
+
+static int32_t const x[] =
+{
+/*==================================================*/
+/* Name HID code Make Break*/
+/*==================================================*/
+/* No Event 00 */ -1, /* None */
+/* Overrun Error 01 */ NOBREAK|0xFF, /* None */
+/* POST Fail 02 */ NOBREAK|0xFC, /* None */
+/* ErrorUndefined 03 */ -1, /* Unassigned */
+/* a A 04 */ 0x1E, /* 9E */
+/* b B 05 */ 0x30, /* B0 */
+/* c C 06 */ 0x2E, /* AE */
+/* d D 07 */ 0x20, /* A0 */
+/* e E 08 */ 0x12, /* 92 */
+/* f F 09 */ 0x21, /* A1 */
+/* g G 0A */ 0x22, /* A2 */
+/* h H 0B */ 0x23, /* A3 */
+/* i I 0C */ 0x17, /* 97 */
+/* j J 0D */ 0x24, /* A4 */
+/* k K 0E */ 0x25, /* A5 */
+/* l L 0F */ 0x26, /* A6 */
+/* m M 10 */ 0x32, /* B2 */
+/* n N 11 */ 0x31, /* B1 */
+/* o O 12 */ 0x18, /* 98 */
+/* p P 13 */ 0x19, /* 99 */
+/* q Q 14 */ 0x10, /* 90 */
+/* r R 15 */ 0x13, /* 93 */
+/* s S 16 */ 0x1F, /* 9F */
+/* t T 17 */ 0x14, /* 94 */
+/* u U 18 */ 0x16, /* 96 */
+/* v V 19 */ 0x2F, /* AF */
+/* w W 1A */ 0x11, /* 91 */
+/* x X 1B */ 0x2D, /* AD */
+/* y Y 1C */ 0x15, /* 95 */
+/* z Z 1D */ 0x2C, /* AC */
+/* 1 ! 1E */ 0x02, /* 82 */
+/* 2 @ 1F */ 0x03, /* 83 */
+/* 3 # 20 */ 0x04, /* 84 */
+/* 4 $ 21 */ 0x05, /* 85 */
+/* 5 % 22 */ 0x06, /* 86 */
+/* 6 ^ 23 */ 0x07, /* 87 */
+/* 7 & 24 */ 0x08, /* 88 */
+/* 8 * 25 */ 0x09, /* 89 */
+/* 9 ( 26 */ 0x0A, /* 8A */
+/* 0 ) 27 */ 0x0B, /* 8B */
+/* Return 28 */ 0x1C, /* 9C */
+/* Escape 29 */ 0x01, /* 81 */
+/* Backspace 2A */ 0x0E, /* 8E */
+/* Tab 2B */ 0x0F, /* 8F */
+/* Space 2C */ 0x39, /* B9 */
+/* - _ 2D */ 0x0C, /* 8C */
+/* = + 2E */ 0x0D, /* 8D */
+/* [ { 2F */ 0x1A, /* 9A */
+/* ] } 30 */ 0x1B, /* 9B */
+/* \ | 31 */ 0x2B, /* AB */
+/* Europe 1 32 */ 0x2B, /* AB */
+/* ; : 33 */ 0x27, /* A7 */
+/* " ' 34 */ 0x28, /* A8 */
+/* ` ~ 35 */ 0x29, /* A9 */
+/* comma < 36 */ 0x33, /* B3 */
+/* . > 37 */ 0x34, /* B4 */
+/* / ? 38 */ 0x35, /* B5 */
+/* Caps Lock 39 */ 0x3A, /* BA */
+/* F1 3A */ 0x3B, /* BB */
+/* F2 3B */ 0x3C, /* BC */
+/* F3 3C */ 0x3D, /* BD */
+/* F4 3D */ 0x3E, /* BE */
+/* F5 3E */ 0x3F, /* BF */
+/* F6 3F */ 0x40, /* C0 */
+/* F7 40 */ 0x41, /* C1 */
+/* F8 41 */ 0x42, /* C2 */
+/* F9 42 */ 0x43, /* C3 */
+/* F10 43 */ 0x44, /* C4 */
+/* F11 44 */ 0x57, /* D7 */
+/* F12 45 */ 0x58, /* D8 */
+/* Print Screen 46 */ E0PREFIX|0x37, /* E0 B7 */
+/* Scroll Lock 47 */ 0x46, /* C6 */
+#if 0
+/* Break (Ctrl-Pause) 48 */ E0 46 E0 C6, /* None */
+/* Pause 48 */ E1 1D 45 E1 9D C5, /* None */
+#else
+/* Break (Ctrl-Pause)/Pause 48 */ NOBREAK /* Special case */, /* None */
+#endif
+/* Insert 49 */ E0PREFIX|0x52, /* E0 D2 */
+/* Home 4A */ E0PREFIX|0x47, /* E0 C7 */
+/* Page Up 4B */ E0PREFIX|0x49, /* E0 C9 */
+/* Delete 4C */ E0PREFIX|0x53, /* E0 D3 */
+/* End 4D */ E0PREFIX|0x4F, /* E0 CF */
+/* Page Down 4E */ E0PREFIX|0x51, /* E0 D1 */
+/* Right Arrow 4F */ E0PREFIX|0x4D, /* E0 CD */
+/* Left Arrow 50 */ E0PREFIX|0x4B, /* E0 CB */
+/* Down Arrow 51 */ E0PREFIX|0x50, /* E0 D0 */
+/* Up Arrow 52 */ E0PREFIX|0x48, /* E0 C8 */
+/* Num Lock 53 */ 0x45, /* C5 */
+/* Keypad / 54 */ E0PREFIX|0x35, /* E0 B5 */
+/* Keypad * 55 */ 0x37, /* B7 */
+/* Keypad - 56 */ 0x4A, /* CA */
+/* Keypad + 57 */ 0x4E, /* CE */
+/* Keypad Enter 58 */ E0PREFIX|0x1C, /* E0 9C */
+/* Keypad 1 End 59 */ 0x4F, /* CF */
+/* Keypad 2 Down 5A */ 0x50, /* D0 */
+/* Keypad 3 PageDn 5B */ 0x51, /* D1 */
+/* Keypad 4 Left 5C */ 0x4B, /* CB */
+/* Keypad 5 5D */ 0x4C, /* CC */
+/* Keypad 6 Right 5E */ 0x4D, /* CD */
+/* Keypad 7 Home 5F */ 0x47, /* C7 */
+/* Keypad 8 Up 60 */ 0x48, /* C8 */
+/* Keypad 9 PageUp 61 */ 0x49, /* C9 */
+/* Keypad 0 Insert 62 */ 0x52, /* D2 */
+/* Keypad . Delete 63 */ 0x53, /* D3 */
+/* Europe 2 64 */ 0x56, /* D6 */
+/* App 65 */ E0PREFIX|0x5D, /* E0 DD */
+/* Keyboard Power 66 */ E0PREFIX|0x5E, /* E0 DE */
+/* Keypad = 67 */ 0x59, /* D9 */
+/* F13 68 */ 0x64, /* E4 */
+/* F14 69 */ 0x65, /* E5 */
+/* F15 6A */ 0x66, /* E6 */
+/* F16 6B */ 0x67, /* E7 */
+/* F17 6C */ 0x68, /* E8 */
+/* F18 6D */ 0x69, /* E9 */
+/* F19 6E */ 0x6A, /* EA */
+/* F20 6F */ 0x6B, /* EB */
+/* F21 70 */ 0x6C, /* EC */
+/* F22 71 */ 0x6D, /* ED */
+/* F23 72 */ 0x6E, /* EE */
+/* F24 73 */ 0x76, /* F6 */
+/* Keyboard Execute 74 */ -1, /* Unassigned */
+/* Keyboard Help 75 */ -1, /* Unassigned */
+/* Keyboard Menu 76 */ -1, /* Unassigned */
+/* Keyboard Select 77 */ -1, /* Unassigned */
+/* Keyboard Stop 78 */ -1, /* Unassigned */
+/* Keyboard Again 79 */ -1, /* Unassigned */
+/* Keyboard Undo 7A */ -1, /* Unassigned */
+/* Keyboard Cut 7B */ -1, /* Unassigned */
+/* Keyboard Copy 7C */ -1, /* Unassigned */
+/* Keyboard Paste 7D */ -1, /* Unassigned */
+/* Keyboard Find 7E */ -1, /* Unassigned */
+/* Keyboard Mute 7F */ -1, /* Unassigned */
+/* Keyboard Volume Up 80 */ -1, /* Unassigned */
+/* Keyboard Volume Dn 81 */ -1, /* Unassigned */
+/* Keyboard Locking Caps Lock 82 */ -1, /* Unassigned */
+/* Keyboard Locking Num Lock 83 */ -1, /* Unassigned */
+/* Keyboard Locking Scroll Lock 84 */ -1, /* Unassigned */
+/* Keypad comma 85 */ 0x7E, /* FE */
+/* Keyboard Equal Sign 86 */ -1, /* Unassigned */
+/* Keyboard Int'l 1 87 */ 0x73, /* F3 */
+/* Keyboard Int'l 2 88 */ 0x70, /* F0 */
+/* Keyboard Int'l 2 89 */ 0x7D, /* FD */
+/* Keyboard Int'l 4 8A */ 0x79, /* F9 */
+/* Keyboard Int'l 5 8B */ 0x7B, /* FB */
+/* Keyboard Int'l 6 8C */ 0x5C, /* DC */
+/* Keyboard Int'l 7 8D */ -1, /* Unassigned */
+/* Keyboard Int'l 8 8E */ -1, /* Unassigned */
+/* Keyboard Int'l 9 8F */ -1, /* Unassigned */
+/* Keyboard Lang 1 90 */ 0x71, /* Kana */
+/* Keyboard Lang 2 91 */ 0x72, /* Eisu */
+/* Keyboard Lang 3 92 */ 0x78, /* F8 */
+/* Keyboard Lang 4 93 */ 0x77, /* F7 */
+/* Keyboard Lang 5 94 */ 0x76, /* F6 */
+/* Keyboard Lang 6 95 */ -1, /* Unassigned */
+/* Keyboard Lang 7 96 */ -1, /* Unassigned */
+/* Keyboard Lang 8 97 */ -1, /* Unassigned */
+/* Keyboard Lang 9 98 */ -1, /* Unassigned */
+/* Keyboard Alternate Erase 99 */ -1, /* Unassigned */
+/* Keyboard SysReq/Attention 9A */ -1, /* Unassigned */
+/* Keyboard Cancel 9B */ -1, /* Unassigned */
+/* Keyboard Clear 9C */ -1, /* Unassigned */
+/* Keyboard Prior 9D */ -1, /* Unassigned */
+/* Keyboard Return 9E */ -1, /* Unassigned */
+/* Keyboard Separator 9F */ -1, /* Unassigned */
+/* Keyboard Out A0 */ -1, /* Unassigned */
+/* Keyboard Oper A1 */ -1, /* Unassigned */
+/* Keyboard Clear/Again A2 */ -1, /* Unassigned */
+/* Keyboard CrSel/Props A3 */ -1, /* Unassigned */
+/* Keyboard ExSel A4 */ -1, /* Unassigned */
+/* Reserved A5 */ -1, /* Reserved */
+/* Reserved A6 */ -1, /* Reserved */
+/* Reserved A7 */ -1, /* Reserved */
+/* Reserved A8 */ -1, /* Reserved */
+/* Reserved A9 */ -1, /* Reserved */
+/* Reserved AA */ -1, /* Reserved */
+/* Reserved AB */ -1, /* Reserved */
+/* Reserved AC */ -1, /* Reserved */
+/* Reserved AD */ -1, /* Reserved */
+/* Reserved AE */ -1, /* Reserved */
+/* Reserved AF */ -1, /* Reserved */
+/* Reserved B0 */ -1, /* Reserved */
+/* Reserved B1 */ -1, /* Reserved */
+/* Reserved B2 */ -1, /* Reserved */
+/* Reserved B3 */ -1, /* Reserved */
+/* Reserved B4 */ -1, /* Reserved */
+/* Reserved B5 */ -1, /* Reserved */
+/* Reserved B6 */ -1, /* Reserved */
+/* Reserved B7 */ -1, /* Reserved */
+/* Reserved B8 */ -1, /* Reserved */
+/* Reserved B9 */ -1, /* Reserved */
+/* Reserved BA */ -1, /* Reserved */
+/* Reserved BB */ -1, /* Reserved */
+/* Reserved BC */ -1, /* Reserved */
+/* Reserved BD */ -1, /* Reserved */
+/* Reserved BE */ -1, /* Reserved */
+/* Reserved BF */ -1, /* Reserved */
+/* Reserved C0 */ -1, /* Reserved */
+/* Reserved C1 */ -1, /* Reserved */
+/* Reserved C2 */ -1, /* Reserved */
+/* Reserved C3 */ -1, /* Reserved */
+/* Reserved C4 */ -1, /* Reserved */
+/* Reserved C5 */ -1, /* Reserved */
+/* Reserved C6 */ -1, /* Reserved */
+/* Reserved C7 */ -1, /* Reserved */
+/* Reserved C8 */ -1, /* Reserved */
+/* Reserved C9 */ -1, /* Reserved */
+/* Reserved CA */ -1, /* Reserved */
+/* Reserved CB */ -1, /* Reserved */
+/* Reserved CC */ -1, /* Reserved */
+/* Reserved CD */ -1, /* Reserved */
+/* Reserved CE */ -1, /* Reserved */
+/* Reserved CF */ -1, /* Reserved */
+/* Reserved D0 */ -1, /* Reserved */
+/* Reserved D1 */ -1, /* Reserved */
+/* Reserved D2 */ -1, /* Reserved */
+/* Reserved D3 */ -1, /* Reserved */
+/* Reserved D4 */ -1, /* Reserved */
+/* Reserved D5 */ -1, /* Reserved */
+/* Reserved D6 */ -1, /* Reserved */
+/* Reserved D7 */ -1, /* Reserved */
+/* Reserved D8 */ -1, /* Reserved */
+/* Reserved D9 */ -1, /* Reserved */
+/* Reserved DA */ -1, /* Reserved */
+/* Reserved DB */ -1, /* Reserved */
+/* Reserved DC */ -1, /* Reserved */
+/* Reserved DD */ -1, /* Reserved */
+/* Reserved DE */ -1, /* Reserved */
+/* Reserved DF */ -1, /* Reserved */
+/* Left Control E0 */ 0x1D, /* 9D */
+/* Left Shift E1 */ 0x2A, /* AA */
+/* Left Alt E2 */ 0x38, /* B8 */
+/* Left GUI E3 */ E0PREFIX|0x5B, /* E0 DB */
+/* Right Control E4 */ E0PREFIX|0x1D, /* E0 9D */
+/* Right Shift E5 */ 0x36, /* B6 */
+/* Right Alt E6 */ E0PREFIX|0x38, /* E0 B8 */
+/* Right GUI E7 */ E0PREFIX|0x5C /* E0 DC */
+};
+
+#define xsize ((int32_t)(sizeof(x)/sizeof(x[0])))
+
+/*
+ * Get a max HID keycode (aligned)
+ */
+
+int32_t
+kbd_maxkey(void)
+{
+ return (xsize);
+}
+
+/*
+ * Process keys
+ */
+
+int32_t
+kbd_process_keys(bthid_session_p s)
+{
+ bitstr_t diff[bitstr_size(xsize)];
+ int32_t f1, f2, i;
+
+ assert(s != NULL);
+ assert(s->srv != NULL);
+
+ /* Check if the new keys have been pressed */
+ bit_ffs(s->keys1, xsize, &f1);
+
+ /* Check if old keys still pressed */
+ bit_ffs(s->keys2, xsize, &f2);
+
+ if (f1 == -1) {
+ /* no new key pressed */
+ if (f2 != -1) {
+ /* release old keys */
+ kbd_write(s->keys2, f2, 0, s->vkbd);
+ memset(s->keys2, 0, bitstr_size(xsize));
+ }
+
+ return (0);
+ }
+
+ if (f2 == -1) {
+ /* no old keys, but new keys pressed */
+ assert(f1 != -1);
+
+ memcpy(s->keys2, s->keys1, bitstr_size(xsize));
+ kbd_write(s->keys1, f1, 1, s->vkbd);
+ memset(s->keys1, 0, bitstr_size(xsize));
+
+ return (0);
+ }
+
+ /* new keys got pressed, old keys got released */
+ memset(diff, 0, bitstr_size(xsize));
+
+ for (i = f2; i < xsize; i ++) {
+ if (bit_test(s->keys2, i)) {
+ if (!bit_test(s->keys1, i)) {
+ bit_clear(s->keys2, i);
+ bit_set(diff, i);
+ }
+ }
+ }
+
+ for (i = f1; i < xsize; i++) {
+ if (bit_test(s->keys1, i)) {
+ if (!bit_test(s->keys2, i))
+ bit_set(s->keys2, i);
+ else
+ bit_clear(s->keys1, i);
+ }
+ }
+
+ bit_ffs(diff, xsize, &f2);
+ if (f2 > 0)
+ kbd_write(diff, f2, 0, s->vkbd);
+
+ bit_ffs(s->keys1, xsize, &f1);
+ if (f1 > 0) {
+ kbd_write(s->keys1, f1, 1, s->vkbd);
+ memset(s->keys1, 0, bitstr_size(xsize));
+ }
+
+ return (0);
+}
+
+/*
+ * Translate given keymap and write keyscodes
+ */
+
+static void
+kbd_write(bitstr_t *m, int32_t fb, int32_t make, int32_t fd)
+{
+ int32_t i, *b, *eob, n, buf[64];
+
+ b = buf;
+ eob = b + sizeof(buf)/sizeof(buf[0]);
+ i = fb;
+
+ while (i < xsize) {
+ if (bit_test(m, i)) {
+ n = kbd_xlate(i, make, b, eob);
+ if (n == -1) {
+ write(fd, buf, (b - buf) * sizeof(buf[0]));
+ b = buf;
+ continue;
+ }
+
+ b += n;
+ }
+
+ i ++;
+ }
+
+ if (b != buf)
+ write(fd, buf, (b - buf) * sizeof(buf[0]));
+}
+
+/*
+ * Translate HID code into PS/2 code and put codes into buffer b.
+ * Returns the number of codes put in b. Return -1 if buffer has not
+ * enough space.
+ */
+
+#undef PUT
+#define PUT(c, n, b, eob) \
+do { \
+ if ((b) >= (eob)) \
+ return (-1); \
+ *(b) = (c); \
+ (b) ++; \
+ (n) ++; \
+} while (0)
+
+static int32_t
+kbd_xlate(int32_t code, int32_t make, int32_t *b, int32_t const *eob)
+{
+ int32_t c, n;
+
+ n = 0;
+
+ if (code >= xsize)
+ return (0); /* HID code is not in the table */
+
+ /* Handle special case - Pause/Break */
+ if (code == 0x48) {
+ if (!make)
+ return (0); /* No break code */
+
+#if 0
+XXX FIXME
+ if (ctrl_is_pressed) {
+ /* Break (Ctrl-Pause) */
+ PUT(0xe0, n, b, eob);
+ PUT(0x46, n, b, eob);
+ PUT(0xe0, n, b, eob);
+ PUT(0xc6, n, b, eob);
+ } else {
+ /* Pause */
+ PUT(0xe1, n, b, eob);
+ PUT(0x1d, n, b, eob);
+ PUT(0x45, n, b, eob);
+ PUT(0xe1, n, b, eob);
+ PUT(0x9d, n, b, eob);
+ PUT(0xc5, n, b, eob);
+ }
+#endif
+
+ return (n);
+ }
+
+ if ((c = x[code]) == -1)
+ return (0); /* HID code translation is not defined */
+
+ if (make) {
+ if (c & E0PREFIX)
+ PUT(0xe0, n, b, eob);
+
+ PUT((c & CODEMASK), n, b, eob);
+ } else if (!(c & NOBREAK)) {
+ if (c & E0PREFIX)
+ PUT(0xe0, n, b, eob);
+
+ PUT((0x80|(c & CODEMASK)), n, b, eob);
+ }
+
+ return (n);
+}
+
+/*
+ * Process status change from vkbd(4)
+ */
+
+int32_t
+kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len)
+{
+ vkbd_status_t st;
+ uint8_t leds, report_id;
+ hid_device_p hid_device;
+ hid_data_t d;
+ hid_item_t h;
+
+ assert(s != NULL);
+ assert(len == sizeof(vkbd_status_t));
+
+ memcpy(&st, data, sizeof(st));
+ leds = 0;
+ report_id = NO_REPORT_ID;
+
+ hid_device = get_hid_device(&s->bdaddr);
+ assert(hid_device != NULL);
+
+ for (d = hid_start_parse(hid_device->desc, 1 << hid_output, -1);
+ hid_get_item(d, &h) > 0; ) {
+ if (HID_PAGE(h.usage) == HUP_LEDS) {
+ if (report_id == NO_REPORT_ID)
+ report_id = h.report_ID;
+ else if (h.report_ID != report_id)
+ syslog(LOG_WARNING, "Output HID report IDs " \
+ "for %s do not match: %d vs. %d. " \
+ "Please report",
+ bt_ntoa(&s->bdaddr, NULL),
+ h.report_ID, report_id);
+
+ switch(HID_USAGE(h.usage)) {
+ case 0x01: /* Num Lock LED */
+ if (st.leds & LED_NUM)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ case 0x02: /* Caps Lock LED */
+ if (st.leds & LED_CAP)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ case 0x03: /* Scroll Lock LED */
+ if (st.leds & LED_SCR)
+ hid_set_data(&leds, &h, 1);
+ break;
+
+ /* XXX add other LEDs ? */
+ }
+ }
+ }
+ hid_end_parse(d);
+
+ data[0] = 0xa2; /* DATA output (HID output report) */
+
+ if (report_id != NO_REPORT_ID) {
+ data[1] = report_id;
+ data[2] = leds;
+ len = 3;
+ } else {
+ data[1] = leds;
+ len = 2;
+ }
+
+ write(s->intr, data, len);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/kbd.h b/usr.sbin/bluetooth/bthidd/kbd.h
new file mode 100644
index 0000000..552f310
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/kbd.h
@@ -0,0 +1,41 @@
+/*
+ * kbd.h
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: kbd.h,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#ifndef _KBD_H_
+#define _KBD_H_
+
+int32_t kbd_maxkey (void);
+int32_t kbd_process_keys (bthid_session_p s);
+int32_t kbd_status_changed(bthid_session_p s, uint8_t *data, int32_t len);
+
+#endif /* ndef _KBD_H_ */
diff --git a/usr.sbin/bluetooth/bthidd/lexer.l b/usr.sbin/bluetooth/bthidd/lexer.l
new file mode 100644
index 0000000..6d913ee
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/lexer.l
@@ -0,0 +1,106 @@
+%{
+/*
+ * lexer.l
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: lexer.l,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <stdlib.h>
+#include "parser.h"
+
+ int yylex (void);
+
+#define YY_DECL int yylex(void)
+%}
+
+%option yylineno noyywrap nounput noinput
+
+delim [ \t\n]
+ws {delim}+
+empty {delim}*
+comment \#.*
+
+hexdigit [0-9a-fA-F]
+hexbyte {hexdigit}{hexdigit}?
+
+device_word device
+bdaddr_word bdaddr
+control_psm_word control_psm
+interrupt_psm_word interrupt_psm
+reconnect_initiate_word reconnect_initiate
+battery_power_word battery_power
+normally_connectable_word normally_connectable
+hid_descriptor_word hid_descriptor
+true_word true
+false_word false
+
+bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}
+hexbytestring 0x{hexbyte}
+
+%%
+
+\; return (';');
+\: return (':');
+\{ return ('{');
+\} return ('}');
+
+{ws} ;
+{empty} ;
+{comment} ;
+
+{device_word} return (T_DEVICE);
+{bdaddr_word} return (T_BDADDR);
+{control_psm_word} return (T_CONTROL_PSM);
+{interrupt_psm_word} return (T_INTERRUPT_PSM);
+{reconnect_initiate_word} return (T_RECONNECT_INITIATE);
+{battery_power_word} return (T_BATTERY_POWER);
+{normally_connectable_word} return (T_NORMALLY_CONNECTABLE);
+{hid_descriptor_word} return (T_HID_DESCRIPTOR);
+{true_word} return (T_TRUE);
+{false_word} return (T_FALSE);
+
+{bdaddrstring} {
+ return (bt_aton(yytext, &yylval.bdaddr)?
+ T_BDADDRSTRING : T_ERROR);
+ }
+
+{hexbytestring} {
+ char *ep;
+
+ yylval.num = strtoul(yytext, &ep, 16);
+
+ return (*ep == '\0'? T_HEXBYTE : T_ERROR);
+ }
+
+. return (T_ERROR);
+
+%%
+
diff --git a/usr.sbin/bluetooth/bthidd/parser.y b/usr.sbin/bluetooth/bthidd/parser.y
new file mode 100644
index 0000000..dbb2763
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/parser.y
@@ -0,0 +1,476 @@
+%{
+/*
+ * parser.y
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: parser.y,v 1.7 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <usbhid.h>
+
+#ifndef BTHIDCONTROL
+#include <stdarg.h>
+#include <syslog.h>
+#define SYSLOG syslog
+#define LOGCRIT LOG_CRIT
+#define LOGERR LOG_ERR
+#define LOGWARNING LOG_WARNING
+#define EOL
+#else
+#define SYSLOG fprintf
+#define LOGCRIT stderr
+#define LOGERR stderr
+#define LOGWARNING stderr
+#define EOL "\n"
+#endif /* ndef BTHIDCONTROL */
+
+#include "bthid_config.h"
+
+ int yylex (void);
+ void yyerror (char const *);
+static int32_t check_hid_device(hid_device_p hid_device);
+static void free_hid_device (hid_device_p hid_device);
+
+extern FILE *yyin;
+extern int yylineno;
+ char const *config_file = BTHIDD_CONFFILE;
+ char const *hids_file = BTHIDD_HIDSFILE;
+
+static char buffer[1024];
+static int32_t hid_descriptor_size;
+static hid_device_t *hid_device = NULL;
+static LIST_HEAD(, hid_device) hid_devices;
+
+%}
+
+%union {
+ bdaddr_t bdaddr;
+ int32_t num;
+}
+
+%token <bdaddr> T_BDADDRSTRING
+%token <num> T_HEXBYTE
+%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE
+%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
+%token T_TRUE T_FALSE T_ERROR
+
+%%
+
+config: line
+ | config line
+ ;
+
+line: T_DEVICE
+ {
+ hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
+ if (hid_device == NULL) {
+ SYSLOG(LOGCRIT, "Could not allocate new " \
+ "config entry" EOL);
+ YYABORT;
+ }
+
+ hid_device->new_device = 1;
+ }
+ '{' options '}'
+ {
+ if (check_hid_device(hid_device))
+ LIST_INSERT_HEAD(&hid_devices,hid_device,next);
+ else
+ free_hid_device(hid_device);
+
+ hid_device = NULL;
+ }
+ ;
+
+options: option ';'
+ | options option ';'
+ ;
+
+option: bdaddr
+ | control_psm
+ | interrupt_psm
+ | reconnect_initiate
+ | battery_power
+ | normally_connectable
+ | hid_descriptor
+ | parser_error
+ ;
+
+bdaddr: T_BDADDR T_BDADDRSTRING
+ {
+ memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
+ }
+ ;
+
+control_psm: T_CONTROL_PSM T_HEXBYTE
+ {
+ hid_device->control_psm = $2;
+ }
+ ;
+
+interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE
+ {
+ hid_device->interrupt_psm = $2;
+ }
+ ;
+
+reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
+ {
+ hid_device->reconnect_initiate = 1;
+ }
+ | T_RECONNECT_INITIATE T_FALSE
+ {
+ hid_device->reconnect_initiate = 0;
+ }
+ ;
+
+battery_power: T_BATTERY_POWER T_TRUE
+ {
+ hid_device->battery_power = 1;
+ }
+ | T_BATTERY_POWER T_FALSE
+ {
+ hid_device->battery_power = 0;
+ }
+ ;
+
+normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
+ {
+ hid_device->normally_connectable = 1;
+ }
+ | T_NORMALLY_CONNECTABLE T_FALSE
+ {
+ hid_device->normally_connectable = 0;
+ }
+ ;
+
+hid_descriptor: T_HID_DESCRIPTOR
+ {
+ hid_descriptor_size = 0;
+ }
+ '{' hid_descriptor_bytes '}'
+ {
+ if (hid_device->desc != NULL)
+ hid_dispose_report_desc(hid_device->desc);
+
+ hid_device->desc = hid_use_report_desc((unsigned char *) buffer, hid_descriptor_size);
+ if (hid_device->desc == NULL) {
+ SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
+ YYABORT;
+ }
+ }
+ ;
+
+hid_descriptor_bytes: hid_descriptor_byte
+ | hid_descriptor_bytes hid_descriptor_byte
+ ;
+
+hid_descriptor_byte: T_HEXBYTE
+ {
+ if (hid_descriptor_size >= (int32_t) sizeof(buffer)) {
+ SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
+ YYABORT;
+ }
+
+ buffer[hid_descriptor_size ++] = $1;
+ }
+ ;
+
+parser_error: T_ERROR
+ {
+ YYABORT;
+ }
+
+%%
+
+/* Display parser error message */
+void
+yyerror(char const *message)
+{
+ SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
+}
+
+/* Re-read config file */
+int32_t
+read_config_file(void)
+{
+ int32_t e;
+
+ if (config_file == NULL) {
+ SYSLOG(LOGERR, "Unknown config file name!" EOL);
+ return (-1);
+ }
+
+ if ((yyin = fopen(config_file, "r")) == NULL) {
+ SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
+ config_file, strerror(errno), errno);
+ return (-1);
+ }
+
+ clean_config();
+ if (yyparse() < 0) {
+ SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
+ config_file);
+ e = -1;
+ } else
+ e = 0;
+
+ fclose(yyin);
+ yyin = NULL;
+
+ return (e);
+}
+
+/* Clean config */
+void
+clean_config(void)
+{
+ while (!LIST_EMPTY(&hid_devices)) {
+ hid_device_p d = LIST_FIRST(&hid_devices);
+
+ LIST_REMOVE(d, next);
+ free_hid_device(d);
+ }
+}
+
+/* Lookup config entry */
+hid_device_p
+get_hid_device(bdaddr_p bdaddr)
+{
+ hid_device_p d;
+
+ LIST_FOREACH(d, &hid_devices, next)
+ if (memcmp(&d->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
+ break;
+
+ return (d);
+}
+
+/* Get next config entry */
+hid_device_p
+get_next_hid_device(hid_device_p d)
+{
+ return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
+}
+
+/* Print config entry */
+void
+print_hid_device(hid_device_p d, FILE *f)
+{
+ /* XXX FIXME hack! */
+ struct report_desc {
+ unsigned int size;
+ unsigned char data[1];
+ };
+ /* XXX FIXME hack! */
+
+ struct report_desc *desc = (struct report_desc *) d->desc;
+ uint32_t i;
+
+ fprintf(f,
+"device {\n" \
+" bdaddr %s;\n" \
+" control_psm 0x%x;\n" \
+" interrupt_psm 0x%x;\n" \
+" reconnect_initiate %s;\n" \
+" battery_power %s;\n" \
+" normally_connectable %s;\n" \
+" hid_descriptor {",
+ bt_ntoa(&d->bdaddr, NULL),
+ d->control_psm, d->interrupt_psm,
+ d->reconnect_initiate? "true" : "false",
+ d->battery_power? "true" : "false",
+ d->normally_connectable? "true" : "false");
+
+ for (i = 0; i < desc->size; i ++) {
+ if ((i % 8) == 0)
+ fprintf(f, "\n ");
+
+ fprintf(f, "0x%2.2x ", desc->data[i]);
+ }
+
+ fprintf(f,
+"\n" \
+" };\n" \
+"}\n");
+}
+
+/* Check config entry */
+static int32_t
+check_hid_device(hid_device_p d)
+{
+ hid_data_t hd;
+ hid_item_t hi;
+ int32_t page;
+
+ if (get_hid_device(&d->bdaddr) != NULL) {
+ SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
+ bt_ntoa(&d->bdaddr, NULL));
+ return (0);
+ }
+
+ if (d->control_psm == 0) {
+ SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
+ return (0);
+ }
+
+ if (d->interrupt_psm == 0) {
+ SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
+ return (0);
+ }
+
+ if (d->desc == NULL) {
+ SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
+ return (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:
+ case hid_endcollection:
+ case hid_output:
+ case hid_feature:
+ break;
+
+ case hid_input:
+ /* Check if the device may send keystrokes */
+ page = HID_PAGE(hi.usage);
+ if (page == HUP_KEYBOARD)
+ d->keyboard = 1;
+ break;
+ }
+ }
+ hid_end_parse(hd);
+
+ return (1);
+}
+
+/* Free config entry */
+static void
+free_hid_device(hid_device_p d)
+{
+ if (d->desc != NULL)
+ hid_dispose_report_desc(d->desc);
+
+ memset(d, 0, sizeof(*d));
+ free(d);
+}
+
+/* Re-read hids file */
+int32_t
+read_hids_file(void)
+{
+ FILE *f;
+ hid_device_t *d;
+ char *line;
+ bdaddr_t bdaddr;
+ int32_t lineno;
+
+ if (hids_file == NULL) {
+ SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
+ return (-1);
+ }
+
+ if ((f = fopen(hids_file, "r")) == NULL) {
+ if (errno == ENOENT)
+ return (0);
+
+ SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
+ hids_file, strerror(errno), errno);
+ return (-1);
+ }
+
+ for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
+ if ((line = strtok(buffer, "\r\n\t ")) == NULL)
+ continue; /* ignore empty lines */
+
+ if (!bt_aton(line, &bdaddr)) {
+ SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
+ "%s:%d" EOL, hids_file, lineno);
+ continue;
+ }
+
+ if ((d = get_hid_device(&bdaddr)) != NULL)
+ d->new_device = 0;
+ }
+
+ fclose(f);
+
+ return (0);
+}
+
+/* Write hids file */
+int32_t
+write_hids_file(void)
+{
+ char path[PATH_MAX];
+ FILE *f;
+ hid_device_t *d;
+
+ if (hids_file == NULL) {
+ SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
+ return (-1);
+ }
+
+ snprintf(path, sizeof(path), "%s.new", hids_file);
+
+ if ((f = fopen(path, "w")) == NULL) {
+ SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
+ path, strerror(errno), errno);
+ return (-1);
+ }
+
+ LIST_FOREACH(d, &hid_devices, next)
+ if (!d->new_device)
+ fprintf(f, "%s\n", bt_ntoa(&d->bdaddr, NULL));
+
+ fclose(f);
+
+ if (rename(path, hids_file) < 0) {
+ SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
+ "%s (%d)" EOL, path, hids_file, strerror(errno), errno);
+ unlink(path);
+ return (-1);
+ }
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/server.c b/usr.sbin/bluetooth/bthidd/server.c
new file mode 100644
index 0000000..26aeb4a
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/server.c
@@ -0,0 +1,352 @@
+/*
+ * server.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: server.c,v 1.9 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <dev/vkbd/vkbd_var.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+#undef max
+#define max(x, y) (((x) > (y))? (x) : (y))
+
+static int32_t server_accept (bthid_server_p srv, int32_t fd);
+static int32_t server_process(bthid_server_p srv, int32_t fd);
+
+/*
+ * Initialize server
+ */
+
+int32_t
+server_init(bthid_server_p srv)
+{
+ struct sockaddr_l2cap l2addr;
+
+ assert(srv != NULL);
+
+ srv->ctrl = srv->intr = -1;
+ FD_ZERO(&srv->rfdset);
+ FD_ZERO(&srv->wfdset);
+ LIST_INIT(&srv->sessions);
+
+ /* Open /dev/consolectl */
+ srv->cons = open("/dev/consolectl", O_RDWR);
+ if (srv->cons < 0) {
+ syslog(LOG_ERR, "Could not open /dev/consolectl. %s (%d)",
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ /* Create control socket */
+ srv->ctrl = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (srv->ctrl < 0) {
+ syslog(LOG_ERR, "Could not create control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->cons);
+ return (-1);
+ }
+
+ l2addr.l2cap_len = sizeof(l2addr);
+ l2addr.l2cap_family = AF_BLUETOOTH;
+ memcpy(&l2addr.l2cap_bdaddr, &srv->bdaddr, sizeof(l2addr.l2cap_bdaddr));
+ l2addr.l2cap_psm = htole16(0x11);
+ l2addr.l2cap_bdaddr_type = BDADDR_BREDR;
+ l2addr.l2cap_cid = 0;
+
+ if (bind(srv->ctrl, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ if (listen(srv->ctrl, 10) < 0) {
+ syslog(LOG_ERR, "Could not listen on control L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ /* Create intrrupt socket */
+ srv->intr = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
+ if (srv->intr < 0) {
+ syslog(LOG_ERR, "Could not create interrupt L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ l2addr.l2cap_psm = htole16(0x13);
+
+ if (bind(srv->intr, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
+ syslog(LOG_ERR, "Could not bind interrupt L2CAP socket. " \
+ "%s (%d)", strerror(errno), errno);
+ close(srv->intr);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ if (listen(srv->intr, 10) < 0) {
+ syslog(LOG_ERR, "Could not listen on interrupt L2CAP socket. "\
+ "%s (%d)", strerror(errno), errno);
+ close(srv->intr);
+ close(srv->ctrl);
+ close(srv->cons);
+ return (-1);
+ }
+
+ FD_SET(srv->ctrl, &srv->rfdset);
+ FD_SET(srv->intr, &srv->rfdset);
+ srv->maxfd = max(srv->ctrl, srv->intr);
+
+ return (0);
+}
+
+/*
+ * Shutdown server
+ */
+
+void
+server_shutdown(bthid_server_p srv)
+{
+ assert(srv != NULL);
+
+ close(srv->cons);
+ close(srv->ctrl);
+ close(srv->intr);
+
+ while (!LIST_EMPTY(&srv->sessions))
+ session_close(LIST_FIRST(&srv->sessions));
+
+ memset(srv, 0, sizeof(*srv));
+}
+
+/*
+ * Do one server iteration
+ */
+
+int32_t
+server_do(bthid_server_p srv)
+{
+ struct timeval tv;
+ fd_set rfdset, wfdset;
+ int32_t n, fd;
+
+ assert(srv != NULL);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ /* Copy cached version of the fd sets and call select */
+ memcpy(&rfdset, &srv->rfdset, sizeof(rfdset));
+ memcpy(&wfdset, &srv->wfdset, sizeof(wfdset));
+
+ n = select(srv->maxfd + 1, &rfdset, &wfdset, NULL, &tv);
+ if (n < 0) {
+ if (errno == EINTR)
+ return (0);
+
+ syslog(LOG_ERR, "Could not select(%d, %p, %p). %s (%d)",
+ srv->maxfd + 1, &rfdset, &wfdset, strerror(errno), errno);
+
+ return (-1);
+ }
+
+ /* Process descriptors (if any) */
+ for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) {
+ if (FD_ISSET(fd, &rfdset)) {
+ n --;
+
+ if (fd == srv->ctrl || fd == srv->intr)
+ server_accept(srv, fd);
+ else
+ server_process(srv, fd);
+ } else if (FD_ISSET(fd, &wfdset)) {
+ n --;
+
+ client_connect(srv, fd);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Accept new connection
+ */
+
+static int32_t
+server_accept(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+ hid_device_p d;
+ struct sockaddr_l2cap l2addr;
+ int32_t new_fd;
+ socklen_t len;
+
+ len = sizeof(l2addr);
+ if ((new_fd = accept(fd, (struct sockaddr *) &l2addr, &len)) < 0) {
+ syslog(LOG_ERR, "Could not accept %s connection. %s (%d)",
+ (fd == srv->ctrl)? "control" : "interrupt",
+ strerror(errno), errno);
+ return (-1);
+ }
+
+ /* Is device configured? */
+ if ((d = get_hid_device(&l2addr.l2cap_bdaddr)) == NULL) {
+ syslog(LOG_ERR, "Rejecting %s connection from %s. " \
+ "Device not configured",
+ (fd == srv->ctrl)? "control" : "interrupt",
+ bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
+ close(new_fd);
+ return (-1);
+ }
+
+ /* Check if we have session for the device */
+ if ((s = session_by_bdaddr(srv, &l2addr.l2cap_bdaddr)) == NULL) {
+ d->new_device = 0; /* reset new device flag */
+ write_hids_file();
+
+ /* Create new inbound session */
+ if ((s = session_open(srv, d)) == NULL) {
+ syslog(LOG_CRIT, "Could not open inbound session "
+ "for %s", bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
+ close(new_fd);
+ return (-1);
+ }
+ }
+
+ /* Update descriptors */
+ if (fd == srv->ctrl) {
+ assert(s->ctrl == -1);
+ s->ctrl = new_fd;
+ s->state = (s->intr == -1)? W4INTR : OPEN;
+ } else {
+ assert(s->intr == -1);
+ s->intr = new_fd;
+ s->state = (s->ctrl == -1)? W4CTRL : OPEN;
+ }
+
+ FD_SET(new_fd, &srv->rfdset);
+ if (new_fd > srv->maxfd)
+ srv->maxfd = new_fd;
+
+ syslog(LOG_NOTICE, "Accepted %s connection from %s",
+ (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;
+ }
+
+ return (0);
+}
+
+/*
+ * Process data on the connection
+ */
+
+static int32_t
+server_process(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s = session_by_fd(srv, fd);
+ 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;
+
+ if (s == NULL)
+ return (0); /* can happen on device disconnect */
+
+
+ if (fd == s->ctrl) {
+ cb = hid_control;
+ to_read = sizeof(data.b);
+ } else if (fd == s->intr) {
+ cb = hid_interrupt;
+ to_read = sizeof(data.b);
+ } else {
+ assert(fd == s->vkbd);
+
+ cb = kbd_status_changed;
+ to_read = sizeof(data.s);
+ }
+
+ do {
+ len = read(fd, &data, to_read);
+ } while (len < 0 && errno == EINTR);
+
+ if (len < 0) {
+ syslog(LOG_ERR, "Could not read data from %s (%s). %s (%d)",
+ bt_ntoa(&s->bdaddr, NULL),
+ (fd == s->ctrl)? "control" : "interrupt",
+ strerror(errno), errno);
+ session_close(s);
+ return (0);
+ }
+
+ if (len == 0) {
+ syslog(LOG_NOTICE, "Remote device %s has closed %s connection",
+ bt_ntoa(&s->bdaddr, NULL),
+ (fd == s->ctrl)? "control" : "interrupt");
+ session_close(s);
+ return (0);
+ }
+
+ (*cb)(s, (uint8_t *) &data, len);
+
+ return (0);
+}
+
diff --git a/usr.sbin/bluetooth/bthidd/session.c b/usr.sbin/bluetooth/bthidd/session.c
new file mode 100644
index 0000000..260cb86
--- /dev/null
+++ b/usr.sbin/bluetooth/bthidd/session.c
@@ -0,0 +1,185 @@
+/*
+ * session.c
+ */
+
+/*-
+ * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * 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.
+ *
+ * $Id: session.c,v 1.3 2006/09/07 21:06:53 max Exp $
+ * $FreeBSD$
+ */
+
+#include <sys/queue.h>
+#include <assert.h>
+#define L2CAP_SOCKET_CHECKED
+#include <bluetooth.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include "bthid_config.h"
+#include "bthidd.h"
+#include "kbd.h"
+
+/*
+ * Create new session
+ */
+
+bthid_session_p
+session_open(bthid_server_p srv, hid_device_p const d)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(d != NULL);
+
+ if ((s = (bthid_session_p) malloc(sizeof(*s))) == NULL)
+ return (NULL);
+
+ s->srv = srv;
+ memcpy(&s->bdaddr, &d->bdaddr, sizeof(s->bdaddr));
+ s->ctrl = -1;
+ s->intr = -1;
+
+ 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->keys1 = bit_alloc(kbd_maxkey());
+ if (s->keys1 == NULL) {
+ free(s);
+ return (NULL);
+ }
+
+ s->keys2 = bit_alloc(kbd_maxkey());
+ if (s->keys2 == NULL) {
+ free(s->keys1);
+ free(s);
+ return (NULL);
+ }
+
+ LIST_INSERT_HEAD(&srv->sessions, s, next);
+
+ return (s);
+}
+
+/*
+ * Lookup session by bdaddr
+ */
+
+bthid_session_p
+session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(bdaddr != NULL);
+
+ LIST_FOREACH(s, &srv->sessions, next)
+ if (memcmp(&s->bdaddr, bdaddr, sizeof(s->bdaddr)) == 0)
+ break;
+
+ return (s);
+}
+
+/*
+ * Lookup session by fd
+ */
+
+bthid_session_p
+session_by_fd(bthid_server_p srv, int32_t fd)
+{
+ bthid_session_p s;
+
+ assert(srv != NULL);
+ assert(fd >= 0);
+
+ LIST_FOREACH(s, &srv->sessions, next)
+ if (s->ctrl == fd || s->intr == fd || s->vkbd == fd)
+ break;
+
+ return (s);
+}
+
+/*
+ * Close session
+ */
+
+void
+session_close(bthid_session_p s)
+{
+ assert(s != NULL);
+ assert(s->srv != NULL);
+
+ LIST_REMOVE(s, next);
+
+ if (s->intr != -1) {
+ FD_CLR(s->intr, &s->srv->rfdset);
+ FD_CLR(s->intr, &s->srv->wfdset);
+ close(s->intr);
+
+ if (s->srv->maxfd == s->intr)
+ s->srv->maxfd --;
+ }
+
+ if (s->ctrl != -1) {
+ FD_CLR(s->ctrl, &s->srv->rfdset);
+ FD_CLR(s->ctrl, &s->srv->wfdset);
+ close(s->ctrl);
+
+ if (s->srv->maxfd == s->ctrl)
+ s->srv->maxfd --;
+ }
+
+ if (s->vkbd != -1) {
+ FD_CLR(s->vkbd, &s->srv->rfdset);
+ close(s->vkbd);
+
+ if (s->srv->maxfd == s->vkbd)
+ s->srv->maxfd --;
+ }
+
+ free(s->keys1);
+ free(s->keys2);
+
+ memset(s, 0, sizeof(*s));
+ free(s);
+}
+
OpenPOWER on IntegriCloud