summaryrefslogtreecommitdiffstats
path: root/input
diff options
context:
space:
mode:
Diffstat (limited to 'input')
-rw-r--r--input/Makefile.am24
-rw-r--r--input/device.c1171
-rw-r--r--input/device.h50
-rw-r--r--input/fakehid.c412
-rw-r--r--input/fakehid.h39
-rw-r--r--input/input-api.txt103
-rw-r--r--input/input.conf9
-rw-r--r--input/main.c154
-rw-r--r--input/manager.c801
-rw-r--r--input/manager.h28
-rw-r--r--input/server.c153
-rw-r--r--input/server.h25
-rw-r--r--input/storage.c364
-rw-r--r--input/storage.h41
-rwxr-xr-xinput/test-input45
15 files changed, 3419 insertions, 0 deletions
diff --git a/input/Makefile.am b/input/Makefile.am
new file mode 100644
index 00000000..1bc81fbe
--- /dev/null
+++ b/input/Makefile.am
@@ -0,0 +1,24 @@
+
+if INPUTPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = input.la
+
+input_la_SOURCES = main.c manager.h manager.c \
+ server.h server.c device.h device.c \
+ storage.h storage.c fakehid.c fakehid.h
+
+LDADD = $(top_builddir)/common/libhelper.a \
+ @GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+endif
+
+AM_LDFLAGS = -module -avoid-version -no-undefined \
+ -export-symbols-regex bluetooth_plugin_desc
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/hcid
+
+EXTRA_DIST = input.conf input-api.txt test-input
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/input/device.c b/input/device.c
new file mode 100644
index 00000000..75acaa82
--- /dev/null
+++ b/input/device.c
@@ -0,0 +1,1171 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+#include "uinput.h"
+
+#include "device.h"
+#include "error.h"
+#include "manager.h"
+#include "storage.h"
+#include "fakehid.h"
+#include "glib-helper.h"
+
+#define INPUT_DEVICE_INTERFACE "org.bluez.input.Device"
+
+#define BUF_SIZE 16
+
+#define UPDOWN_ENABLED 1
+
+#define FI_FLAG_CONNECTED 1
+
+struct device;
+
+struct device {
+ bdaddr_t src;
+ bdaddr_t dst;
+ int timeout;
+ char *name;
+ uint8_t major;
+ uint8_t minor;
+ uint16_t product;
+ uint16_t vendor;
+ struct fake_input *fake;
+ DBusMessage *pending_connect;
+ DBusConnection *conn;
+ char *path;
+ int ctrl_sk;
+ int intr_sk;
+ guint ctrl_watch;
+ guint intr_watch;
+};
+
+GSList *devices = NULL;
+
+static struct device *device_new(bdaddr_t *src, bdaddr_t *dst,
+ uint8_t subclass, int timeout)
+{
+ struct device *idev;
+ uint32_t cls;
+ uint8_t major, minor;
+
+ if (!subclass) {
+ if (read_device_class(src, dst, &cls) < 0)
+ return NULL;
+
+ major = (cls >> 8) & 0x1f;
+ minor = (cls >> 2) & 0x3f;
+ } else {
+ major = 0x05; /* Peripheral */
+ minor = (subclass >> 2) & 0x3f;
+ }
+
+ idev = g_new0(struct device, 1);
+
+ bacpy(&idev->src, src);
+ bacpy(&idev->dst, dst);
+ idev->timeout = timeout;
+
+ read_device_name(src, dst, &idev->name);
+
+ idev->major = major;
+ idev->minor = minor;
+ idev->ctrl_sk = -1;
+ idev->intr_sk = -1;
+
+ return idev;
+}
+
+static void device_free(struct device *idev)
+{
+ if (!idev)
+ return;
+ if (idev->name)
+ g_free(idev->name);
+ if (idev->fake)
+ g_free(idev->fake);
+ if (idev->path)
+ g_free(idev->path);
+ if (idev->pending_connect)
+ dbus_message_unref(idev->pending_connect);
+ dbus_connection_unref(idev->conn);
+ g_free(idev);
+}
+
+static int uinput_create(char *name)
+{
+ struct uinput_dev dev;
+ int fd, err;
+
+ fd = open("/dev/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/input/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/misc/uinput", O_RDWR);
+ if (fd < 0) {
+ err = errno;
+ error("Can't open input device: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+ }
+ }
+
+ memset(&dev, 0, sizeof(dev));
+ if (name)
+ strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
+
+ dev.id.bustype = BUS_BLUETOOTH;
+ dev.id.vendor = 0x0000;
+ dev.id.product = 0x0000;
+ dev.id.version = 0x0000;
+
+ if (write(fd, &dev, sizeof(dev)) < 0) {
+ err = errno;
+ error("Can't write device information: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ ioctl(fd, UI_SET_EVBIT, EV_REL);
+ ioctl(fd, UI_SET_EVBIT, EV_REP);
+
+ ioctl(fd, UI_SET_KEYBIT, KEY_UP);
+ ioctl(fd, UI_SET_KEYBIT, KEY_PAGEUP);
+ ioctl(fd, UI_SET_KEYBIT, KEY_DOWN);
+ ioctl(fd, UI_SET_KEYBIT, KEY_PAGEDOWN);
+
+ if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+ err = errno;
+ error("Can't create uinput device: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ return fd;
+}
+
+static const char *create_input_path(uint8_t major, uint8_t minor)
+{
+ static char path[48];
+ char subpath[32];
+ static int next_id = 0;
+
+ switch (major) {
+ case 0x02: /* Phone */
+ strcpy(subpath, "phone");
+ break;
+ case 0x04: /* Audio */
+ switch (minor) {
+ /* FIXME: Testing required */
+ case 0x01: /* Wearable Headset Device */
+ strcpy(subpath, "wearable");
+ break;
+ case 0x02: /* Hands-free */
+ strcpy(subpath, "handsfree");
+ break;
+ case 0x06: /* Headphone */
+ strcpy(subpath, "headphone");
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case 0x05: /* Peripheral */
+ switch (minor & 0x30) {
+ case 0x10:
+ strcpy(subpath, "keyboard");
+ break;
+ case 0x20:
+ strcpy(subpath, "pointing");
+ break;
+ case 0x30:
+ strcpy(subpath, "combo");
+ break;
+ default:
+ subpath[0] = '\0';
+ break;
+ }
+
+ if ((minor & 0x0f) && (strlen(subpath) > 0))
+ strcat(subpath, "/");
+
+ switch (minor & 0x0f) {
+ case 0x00:
+ break;
+ case 0x01:
+ strcat(subpath, "joystick");
+ break;
+ case 0x02:
+ strcat(subpath, "gamepad");
+ break;
+ case 0x03:
+ strcat(subpath, "remotecontrol");
+ break;
+ case 0x04:
+ strcat(subpath, "sensing");
+ break;
+ case 0x05:
+ strcat(subpath, "digitizertablet");
+ break;
+ case 0x06:
+ strcat(subpath, "cardreader");
+ break;
+ default:
+ strcat(subpath, "reserved");
+ break;
+ }
+ break;
+ default:
+ return NULL;
+ }
+
+ snprintf(path, 48, "%s/%s%d", INPUT_PATH, subpath, next_id++);
+ return path;
+}
+
+static int decode_key(const char *str)
+{
+ static int mode = UPDOWN_ENABLED, gain = 0;
+
+ uint16_t key;
+ int new_gain;
+
+ /* Switch from key up/down to page up/down */
+ if (strncmp("AT+CKPD=200", str, 11) == 0) {
+ mode = ~mode;
+ return KEY_RESERVED;
+ }
+
+ if (strncmp("AT+VG", str, 5))
+ return KEY_RESERVED;
+
+ /* Gain key pressed */
+ if (strlen(str) != 10)
+ return KEY_RESERVED;
+
+ new_gain = strtol(&str[7], NULL, 10);
+ if (new_gain <= gain)
+ key = (mode == UPDOWN_ENABLED ? KEY_UP : KEY_PAGEUP);
+ else
+ key = (mode == UPDOWN_ENABLED ? KEY_DOWN : KEY_PAGEDOWN);
+
+ gain = new_gain;
+
+ return key;
+}
+
+static void send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+ struct uinput_event event;
+ int err;
+
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+
+ err = write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key)
+{
+ /* Key press */
+ send_event(fd, EV_KEY, key, 1);
+ send_event(fd, EV_SYN, SYN_REPORT, 0);
+ /* Key release */
+ send_event(fd, EV_KEY, key, 0);
+ send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static gboolean rfcomm_io_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct fake_input *fake = data;
+ const char *ok = "\r\nOK\r\n";
+ char buf[BUF_SIZE];
+ gsize bread = 0, bwritten;
+ uint16_t key;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ error("Hangup or error on rfcomm server socket");
+ goto failed;
+ }
+
+ memset(buf, 0, BUF_SIZE);
+ if (g_io_channel_read(chan, buf, sizeof(buf) - 1,
+ &bread) != G_IO_ERROR_NONE) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ debug("Received: %s", buf);
+
+ if (g_io_channel_write(chan, ok, 6, &bwritten) != G_IO_ERROR_NONE) {
+ error("IO Channel write error");
+ goto failed;
+ }
+
+ key = decode_key(buf);
+ if (key != KEY_RESERVED)
+ send_key(fake->uinput, key);
+
+ return TRUE;
+
+failed:
+ ioctl(fake->uinput, UI_DEV_DESTROY);
+ close(fake->uinput);
+ fake->uinput = -1;
+ g_io_channel_unref(fake->io);
+
+ return FALSE;
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+ "Device connection already in progress");
+}
+
+static inline DBusMessage *already_connected(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected",
+ "Already connected to a device");
+}
+
+static inline DBusMessage *connection_attempt_failed(DBusMessage *msg, int err)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".ConnectionAttemptFailed",
+ err ? strerror(err) : "Connection attempt failed");
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct device *idev = user_data;
+ struct fake_input *fake;
+ DBusMessage *reply;
+ const char *path;
+
+ fake = idev->fake;
+ fake->rfcomm = g_io_channel_unix_get_fd(chan);
+
+ if (err < 0)
+ goto failed;
+
+ /*
+ * FIXME: Some headsets required a sco connection
+ * first to report volume gain key events
+ */
+ fake->uinput = uinput_create(idev->name);
+ if (fake->uinput < 0) {
+ err = errno;
+ goto failed;
+ }
+
+ fake->io = g_io_channel_unix_new(fake->rfcomm);
+ g_io_channel_set_close_on_unref(fake->io, TRUE);
+ g_io_add_watch(fake->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) rfcomm_io_cb, fake);
+
+ /* Replying to the requestor */
+ reply = dbus_message_new_method_return(idev->pending_connect);
+ g_dbus_send_message(idev->conn, reply);
+
+ /* Sending the Connected signal */
+ path = dbus_message_get_path(idev->pending_connect);
+ g_dbus_emit_signal(idev->conn, path,
+ INPUT_DEVICE_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+
+ return;
+
+failed:
+ reply = connection_attempt_failed(idev->pending_connect, err);
+ g_dbus_send_message(idev->conn, reply);
+
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+}
+
+static int rfcomm_connect(struct device *idev)
+{
+ int err;
+
+ err = bt_rfcomm_connect(&idev->src, &idev->dst, idev->fake->ch,
+ rfcomm_connect_cb, idev);
+ if (err < 0) {
+ error("connect() failed: %s (%d)", strerror(-err), -err);
+ return err;
+ }
+
+ return 0;
+}
+
+static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct device *idev = data;
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ g_io_channel_close(chan);
+
+ g_dbus_emit_signal(idev->conn,
+ idev->path,
+ INPUT_DEVICE_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+
+ g_source_remove(idev->ctrl_watch);
+ idev->ctrl_watch = 0;
+ idev->intr_watch = 0;
+
+ /* Close control channel */
+ if (idev->ctrl_sk > 0) {
+ close(idev->ctrl_sk);
+ idev->ctrl_sk = -1;
+ }
+
+ return FALSE;
+
+}
+
+static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct device *idev = data;
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ g_io_channel_close(chan);
+
+ g_dbus_emit_signal(idev->conn,
+ idev->path,
+ INPUT_DEVICE_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+
+ g_source_remove(idev->intr_watch);
+ idev->intr_watch = 0;
+ idev->ctrl_watch = 0;
+
+ /* Close interrupt channel */
+ if (idev->intr_sk > 0) {
+ close(idev->intr_sk);
+ idev->intr_sk = -1;
+ }
+
+ return FALSE;
+}
+
+static guint create_watch(int sk, GIOFunc cb, struct device *idev)
+{
+ guint id;
+ GIOChannel *io;
+
+ io = g_io_channel_unix_new(sk);
+ id = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, cb, idev);
+ g_io_channel_unref(io);
+
+ return id;
+}
+
+static int hidp_connadd(bdaddr_t *src, bdaddr_t *dst,
+ int ctrl_sk, int intr_sk, int timeout, const char *name)
+{
+ struct hidp_connadd_req req;
+ char addr[18];
+ int ctl, err;
+
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+ if (ctl < 0) {
+ error("Can't open HIDP interface");
+ return -errno;
+ }
+
+ ba2str(dst, addr);
+
+ memset(&req, 0, sizeof(req));
+ req.ctrl_sock = ctrl_sk;
+ req.intr_sock = intr_sk;
+ req.flags = 0;
+ req.idle_to = timeout;
+
+ err = get_stored_device_info(src, dst, &req);
+ if (err < 0) {
+ error("Rejected connection from unknown device %s", addr);
+ goto cleanup;
+ }
+
+ if (req.subclass & 0x40) {
+ err = encrypt_link(src, dst);
+ if (err < 0 && err != -EACCES)
+ goto cleanup;
+ }
+
+ if (name)
+ strncpy(req.name, name, 128);
+
+ info("New input device %s (%s)", addr, req.name);
+
+ if (req.vendor == 0x054c && req.product == 0x0268) {
+ unsigned char buf[] = { 0x53, 0xf4, 0x42, 0x03, 0x00, 0x00 };
+ err = write(ctrl_sk, buf, sizeof(buf));
+ }
+
+ err = ioctl(ctl, HIDPCONNADD, &req);
+
+cleanup:
+ close(ctl);
+
+ if (req.rd_data)
+ free(req.rd_data);
+
+ return err;
+}
+
+static void interrupt_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct device *idev = user_data;
+ DBusMessage *reply;
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ idev->intr_sk = g_io_channel_unix_get_fd(chan);
+ err = hidp_connadd(&idev->src, &idev->dst,
+ idev->ctrl_sk, idev->intr_sk,
+ idev->timeout, idev->name);
+ if (err < 0)
+ goto failed;
+
+ idev->intr_watch = create_watch(idev->intr_sk, intr_watch_cb, idev);
+ idev->ctrl_watch = create_watch(idev->ctrl_sk, ctrl_watch_cb, idev);
+ g_dbus_emit_signal(idev->conn,
+ idev->path,
+ INPUT_DEVICE_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+
+ /* Replying to the requestor */
+ g_dbus_send_reply(idev->conn, idev->pending_connect, DBUS_TYPE_INVALID);
+
+ goto cleanup;
+
+failed:
+ reply = connection_attempt_failed(idev->pending_connect, -err);
+ g_dbus_send_message(idev->conn, reply);
+
+ idev->intr_sk = -1;
+ idev->ctrl_sk = -1;
+
+cleanup:
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+}
+
+static void control_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct device *idev = user_data;
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ /* Set HID control channel */
+ idev->ctrl_sk = g_io_channel_unix_get_fd(chan);
+
+ /* Connect to the HID interrupt channel */
+ err = bt_l2cap_connect(&idev->src, &idev->dst, L2CAP_PSM_HIDP_INTR, 0,
+ interrupt_connect_cb, idev);
+ if (err < 0) {
+ error("L2CAP connect failed:%s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ return;
+
+failed:
+ idev->ctrl_sk = -1;
+ error_connection_attempt_failed(idev->conn,
+ idev->pending_connect, -err);
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+}
+
+static int fake_disconnect(struct device *idev)
+{
+ struct fake_input *fake = idev->fake;
+
+ if (!fake->io)
+ return -ENOTCONN;
+
+ g_io_channel_close(fake->io);
+ g_io_channel_unref(fake->io);
+ fake->io = NULL;
+
+ if (fake->uinput >= 0) {
+ ioctl(fake->uinput, UI_DEV_DESTROY);
+ close(fake->uinput);
+ fake->uinput = -1;
+ }
+
+ return 0;
+}
+
+static int disconnect(struct device *idev, uint32_t flags)
+{
+ struct fake_input *fake = idev->fake;
+ struct hidp_conndel_req req;
+ struct hidp_conninfo ci;
+ int ctl, err;
+
+ /* Fake input disconnect */
+ if (fake) {
+ err = fake->disconnect(idev);
+ if (err == 0)
+ fake->flags &= ~FI_FLAG_CONNECTED;
+ return err;
+ }
+
+ /* Standard HID disconnect */
+ if (idev->ctrl_sk >= 0) {
+ close(idev->ctrl_sk);
+ idev->ctrl_sk = -1;
+ }
+ if (idev->intr_sk >= 0) {
+ close(idev->intr_sk);
+ idev->intr_sk = -1;
+ }
+
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+ if (ctl < 0) {
+ error("Can't open HIDP control socket");
+ return -errno;
+ }
+
+ memset(&ci, 0, sizeof(ci));
+ bacpy(&ci.bdaddr, &idev->dst);
+ if ((ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) ||
+ (ci.state != BT_CONNECTED)) {
+ errno = ENOTCONN;
+ goto fail;
+ }
+
+ memset(&req, 0, sizeof(req));
+ bacpy(&req.bdaddr, &idev->dst);
+ req.flags = flags;
+ if (ioctl(ctl, HIDPCONNDEL, &req) < 0) {
+ error("Can't delete the HID device: %s(%d)",
+ strerror(errno), errno);
+ goto fail;
+ }
+
+ close(ctl);
+
+ return 0;
+
+fail:
+ err = errno;
+ close(ctl);
+ errno = err;
+
+ return -err;
+}
+
+static int is_connected(struct device *idev)
+{
+ struct fake_input *fake = idev->fake;
+ struct hidp_conninfo ci;
+ int ctl;
+
+ /* Fake input */
+ if (fake)
+ return fake->flags & FI_FLAG_CONNECTED;
+
+ /* Standard HID */
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+ if (ctl < 0)
+ return 0;
+
+ memset(&ci, 0, sizeof(ci));
+ bacpy(&ci.bdaddr, &idev->dst);
+ if (ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) {
+ close(ctl);
+ return 0;
+ }
+
+ close(ctl);
+
+ if (ci.state != BT_CONNECTED)
+ return 0;
+ else
+ return 1;
+}
+
+/*
+ * Input Device methods
+ */
+static DBusMessage *device_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ struct fake_input *fake = idev->fake;
+ int err;
+
+ if (idev->pending_connect)
+ return in_progress(msg);
+
+ if (is_connected(idev))
+ return already_connected(msg);
+
+ idev->pending_connect = dbus_message_ref(msg);
+
+ /* Fake input device */
+ if (fake) {
+ if (fake->connect(idev) < 0) {
+ int err = errno;
+ const char *str = strerror(err);
+ error("Connect failed: %s(%d)", str, err);
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+ return connection_attempt_failed(msg, err);
+ }
+ fake->flags |= FI_FLAG_CONNECTED;
+ return NULL;
+ }
+
+ /* HID devices */
+ err = bt_l2cap_connect(&idev->src, &idev->dst, L2CAP_PSM_HIDP_CTRL,
+ 0, control_connect_cb, idev);
+ if (err < 0) {
+ error("L2CAP connect failed: %s(%d)", strerror(-err), -err);
+ dbus_message_unref(idev->pending_connect);
+ idev->pending_connect = NULL;
+ return connection_attempt_failed(msg, -err);
+ }
+
+ return NULL;
+}
+
+static DBusMessage *device_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ int err;
+
+ err = disconnect(idev, 0);
+ if (err < 0)
+ return create_errno_message(msg, -err);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_is_connected(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ dbus_bool_t connected = is_connected(idev);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_get_adapter(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ char addr[18];
+ const char *paddr = addr;
+
+ ba2str(&idev->src, addr);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &paddr,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_get_address(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ char addr[18];
+ const char *paddr = addr;
+
+ ba2str(&idev->dst, addr);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &paddr,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_get_name(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+ const char *pname = (idev->name ? idev->name : "");
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_STRING, &pname,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_get_product_id(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_UINT16, &idev->product,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *device_get_vendor_id(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *idev = data;
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_UINT16, &idev->vendor,
+ DBUS_TYPE_INVALID);
+}
+
+static void device_unregister(void *data)
+{
+ struct device *idev = data;
+
+ /* Disconnect if applied */
+ disconnect(idev, (1 << HIDP_VIRTUAL_CABLE_UNPLUG));
+ device_free(idev);
+}
+
+static GDBusMethodTable device_methods[] = {
+ { "Connect", "", "", device_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", device_disconnect },
+ { "IsConnected", "", "b", device_is_connected },
+ { "GetAdapter", "", "s", device_get_adapter },
+ { "GetAddress", "", "s", device_get_address },
+ { "GetName", "", "s", device_get_name },
+ { "GetProductId", "", "q", device_get_product_id },
+ { "GetVendorId", "", "q", device_get_vendor_id },
+ { }
+};
+
+static GDBusSignalTable device_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { }
+};
+
+/*
+ * Input registration functions
+ */
+static int register_path(DBusConnection *conn, const char *path, struct device *idev)
+{
+ if (g_dbus_register_interface(conn, path, INPUT_DEVICE_INTERFACE,
+ device_methods, device_signals, NULL,
+ idev, device_unregister) == FALSE) {
+ error("Failed to register %s interface to %s",
+ INPUT_DEVICE_INTERFACE, path);
+ return -1;
+ }
+
+ devices = g_slist_append(devices, idev);
+
+ info("Created input device: %s", path);
+
+ return 0;
+}
+
+int input_device_register(DBusConnection *conn, bdaddr_t *src, bdaddr_t *dst,
+ struct hidp_connadd_req *hid, const char **ppath)
+{
+ struct device *idev;
+ const char *path;
+ int err;
+
+ idev = device_new(src, dst, hid->subclass, hid->idle_to);
+ if (!idev)
+ return -EINVAL;
+
+ path = create_input_path(idev->major, idev->minor);
+ if (!path) {
+ device_free(idev);
+ return -EINVAL;
+ }
+
+ idev->path = g_strdup(path);
+ idev->product = hid->product;
+ idev->vendor = hid->vendor;
+ idev->conn = dbus_connection_ref(conn);
+
+ err = register_path(conn, path, idev);
+
+ if (!err && ppath)
+ *ppath = path;
+
+ return err;
+}
+
+int fake_input_register(DBusConnection *conn, bdaddr_t *src,
+ bdaddr_t *dst, uint8_t ch, const char **ppath)
+{
+ struct device *idev;
+ const char *path;
+ int err;
+
+ idev = device_new(src, dst, 0, 0);
+ if (!idev)
+ return -EINVAL;
+
+ path = create_input_path(idev->major, idev->minor);
+ if (!path) {
+ device_free(idev);
+ return -EINVAL;
+ }
+
+ idev->path = g_strdup(path);
+ idev->conn = dbus_connection_ref(conn);
+
+ /* FIXME: Missing set product and vendor */
+
+ idev->fake = g_new0(struct fake_input, 1);
+ idev->fake->ch = ch;
+ idev->fake->connect = rfcomm_connect;
+ idev->fake->disconnect = fake_disconnect;
+
+ err = register_path(conn, path, idev);
+
+ if (!err && ppath)
+ *ppath = path;
+
+ return err;
+}
+
+static struct device *find_device(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ GSList *list;
+
+ for (list = devices; list != NULL; list = list->next) {
+ struct device *idev = list->data;
+
+ if (!bacmp(&idev->src, src) && !bacmp(&idev->dst, dst))
+ return idev;
+ }
+
+ return NULL;
+}
+
+static struct device *find_device_by_path(const char *path)
+{
+ GSList *list;
+
+ for (list = devices; list != NULL; list = list->next) {
+ struct device *idev = list->data;
+
+ if (strcmp(idev->path, path) == 0)
+ return idev;
+ }
+
+ return NULL;
+}
+
+int input_device_unregister(DBusConnection *conn, const char *path)
+{
+ struct device *idev;
+
+ idev = find_device_by_path(path);
+ if (idev == NULL)
+ return -EINVAL;
+
+ if (idev->pending_connect) {
+ /* Pending connection running */
+ return -EBUSY;
+ }
+
+ del_stored_device_info(&idev->src, &idev->dst);
+
+ devices = g_slist_remove(devices, idev);
+
+ /*
+ * Workaround: if connected, the watch will not be able
+ * to access the D-Bus data assigned to this path
+ * because the object path data was destroyed.
+ */
+ if (idev->ctrl_watch)
+ g_source_remove(idev->ctrl_watch);
+
+ if (idev->intr_watch) {
+ g_source_remove(idev->intr_watch);
+ g_dbus_emit_signal(conn,
+ path, INPUT_DEVICE_INTERFACE,
+ "Disconnected", DBUS_TYPE_INVALID);
+ }
+
+ g_dbus_emit_signal(conn, INPUT_PATH,
+ INPUT_MANAGER_INTERFACE, "DeviceRemoved" ,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_unregister_interface(conn, path, INPUT_DEVICE_INTERFACE);
+
+ return 0;
+}
+
+gboolean input_device_is_registered(bdaddr_t *src, bdaddr_t *dst)
+{
+ struct device *idev = find_device(src, dst);
+ if (!idev)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, int nsk)
+{
+ struct device *idev = find_device(src, dst);
+ if (!idev)
+ return -ENOENT;
+
+ switch (psm) {
+ case L2CAP_PSM_HIDP_CTRL:
+ idev->ctrl_sk = nsk;
+ break;
+ case L2CAP_PSM_HIDP_INTR:
+ idev->intr_sk = nsk;
+ break;
+ }
+
+ return 0;
+}
+
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct device *idev = find_device(src, dst);
+ if (!idev)
+ return -ENOENT;
+
+ if (idev->ctrl_sk >= 0) {
+ close(idev->ctrl_sk);
+ idev->ctrl_sk = -1;
+ }
+
+ if (idev->intr_sk >= 0) {
+ close(idev->intr_sk);
+ idev->intr_sk = -1;
+ }
+
+ return 0;
+}
+
+static gboolean fake_hid_connect(struct device *dev)
+{
+ struct fake_hid *fhid = dev->fake->priv;
+
+ return fhid->connect(dev->fake);
+}
+
+static int fake_hid_disconnect(struct device *dev)
+{
+ struct fake_hid *fhid = dev->fake->priv;
+
+ return fhid->disconnect(dev->fake);
+}
+
+int input_device_connadd(bdaddr_t *src, bdaddr_t *dst)
+{
+ struct device *idev;
+ struct fake_hid *fake_hid;
+ struct fake_input *fake = NULL;
+ int err;
+
+ idev = find_device(src, dst);
+ if (!idev)
+ return -ENOENT;
+
+ fake_hid = get_fake_hid(idev->vendor, idev->product);
+ if (fake_hid) {
+ fake = g_try_new0(struct fake_input, 1);
+ if (!fake) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ fake->connect = fake_hid_connect;
+ fake->disconnect = fake_hid_disconnect;
+ fake->priv = fake_hid;
+ err = fake_hid_connadd(fake, idev->intr_sk, fake_hid);
+ } else
+ err = hidp_connadd(src, dst, idev->ctrl_sk, idev->intr_sk,
+ idev->timeout, idev->name);
+ if (err < 0)
+ goto error;
+
+ idev->intr_watch = create_watch(idev->intr_sk, intr_watch_cb, idev);
+ idev->ctrl_watch = create_watch(idev->ctrl_sk, ctrl_watch_cb, idev);
+ g_dbus_emit_signal(idev->conn,
+ idev->path,
+ INPUT_DEVICE_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ return 0;
+
+error:
+ close(idev->ctrl_sk);
+ close(idev->intr_sk);
+ idev->ctrl_sk = -1;
+ idev->intr_sk = -1;
+ g_free(fake);
+ return err;
+}
diff --git a/input/device.h b/input/device.h
new file mode 100644
index 00000000..c9296ad0
--- /dev/null
+++ b/input/device.h
@@ -0,0 +1,50 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define L2CAP_PSM_HIDP_CTRL 0x11
+#define L2CAP_PSM_HIDP_INTR 0x13
+
+struct device;
+
+struct fake_input {
+ int flags;
+ GIOChannel *io;
+ int uinput; /* uinput socket */
+ int rfcomm; /* RFCOMM socket */
+ uint8_t ch; /* RFCOMM channel number */
+ gboolean (*connect) (struct device *dev);
+ int (*disconnect) (struct device *dev);
+ void *priv;
+};
+
+int input_device_register(DBusConnection *conn, bdaddr_t *src, bdaddr_t *dst,
+ struct hidp_connadd_req *hidp, const char **ppath);
+int fake_input_register(DBusConnection *conn, bdaddr_t *src,
+ bdaddr_t *dst, uint8_t ch, const char **ppath);
+int input_device_unregister(DBusConnection *conn, const char *path);
+
+gboolean input_device_is_registered(bdaddr_t *src, bdaddr_t *dst);
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, int nsk);
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst);
+int input_device_connadd(bdaddr_t *src, bdaddr_t *dst);
diff --git a/input/fakehid.c b/input/fakehid.c
new file mode 100644
index 00000000..d752d85a
--- /dev/null
+++ b/input/fakehid.c
@@ -0,0 +1,412 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/hidp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "logging.h"
+#include "device.h"
+#include "fakehid.h"
+#include "uinput.h"
+
+#ifndef KEY_REMOTE_1
+#define KEY_REMOTE_1 0x1b6
+#endif
+#ifndef KEY_REMOTE_2
+#define KEY_REMOTE_2 0x1b7
+#endif
+#ifndef KEY_REMOTE_3
+#define KEY_REMOTE_3 0x1b8
+#endif
+#ifndef KEY_REMOTE_4
+#define KEY_REMOTE_4 0x1b9
+#endif
+#ifndef KEY_REMOTE_5
+#define KEY_REMOTE_5 0x1ba
+#endif
+#ifndef KEY_REMOTE_6
+#define KEY_REMOTE_6 0x1bb
+#endif
+#ifndef KEY_REMOTE_7
+#define KEY_REMOTE_7 0x1bc
+#endif
+#ifndef KEY_REMOTE_8
+#define KEY_REMOTE_8 0x1bd
+#endif
+#ifndef KEY_REMOTE_9
+#define KEY_REMOTE_9 0x1be
+#endif
+#ifndef KEY_REMOTE_0
+#define KEY_REMOTE_0 0x1bf
+#endif
+
+#define PS3_FLAGS_MASK 0xFFFFFF00
+
+enum ps3remote_special_keys {
+ PS3R_BIT_PS = 0,
+ PS3R_BIT_ENTER = 3,
+ PS3R_BIT_L2 = 8,
+ PS3R_BIT_R2 = 9,
+ PS3R_BIT_L1 = 10,
+ PS3R_BIT_R1 = 11,
+ PS3R_BIT_TRIANGLE = 12,
+ PS3R_BIT_CIRCLE = 13,
+ PS3R_BIT_CROSS = 14,
+ PS3R_BIT_SQUARE = 15,
+ PS3R_BIT_SELECT = 16,
+ PS3R_BIT_L3 = 17,
+ PS3R_BIT_R3 = 18,
+ PS3R_BIT_START = 19,
+ PS3R_BIT_UP = 20,
+ PS3R_BIT_RIGHT = 21,
+ PS3R_BIT_DOWN = 22,
+ PS3R_BIT_LEFT = 23,
+};
+
+static unsigned int ps3remote_bits[] = {
+ [PS3R_BIT_ENTER] = 0x0b,
+ [PS3R_BIT_PS] = 0x43,
+ [PS3R_BIT_SQUARE] = 0x5f,
+ [PS3R_BIT_CROSS] = 0x5e,
+ [PS3R_BIT_CIRCLE] = 0x5d,
+ [PS3R_BIT_TRIANGLE] = 0x5c,
+ [PS3R_BIT_R1] = 0x5b,
+ [PS3R_BIT_L1] = 0x5a,
+ [PS3R_BIT_R2] = 0x59,
+ [PS3R_BIT_L2] = 0x58,
+ [PS3R_BIT_LEFT] = 0x57,
+ [PS3R_BIT_DOWN] = 0x56,
+ [PS3R_BIT_RIGHT] = 0x55,
+ [PS3R_BIT_UP] = 0x54,
+ [PS3R_BIT_START] = 0x53,
+ [PS3R_BIT_R3] = 0x52,
+ [PS3R_BIT_L3] = 0x51,
+ [PS3R_BIT_SELECT] = 0x50,
+};
+
+static unsigned int ps3remote_keymap[] = {
+ [0x16] = KEY_EJECTCD,
+ [0x64] = KEY_AUDIO,
+ [0x65] = KEY_ANGLE,
+ [0x63] = KEY_SUBTITLE,
+ [0x0f] = KEY_CLEAR,
+ [0x28] = KEY_TIME,
+ [0x00] = KEY_REMOTE_1,
+ [0x01] = KEY_REMOTE_2,
+ [0x02] = KEY_REMOTE_3,
+ [0x03] = KEY_REMOTE_4,
+ [0x04] = KEY_REMOTE_5,
+ [0x05] = KEY_REMOTE_6,
+ [0x06] = KEY_REMOTE_7,
+ [0x07] = KEY_REMOTE_8,
+ [0x08] = KEY_REMOTE_9,
+ [0x09] = KEY_REMOTE_0,
+ [0x81] = KEY_RED,
+ [0x82] = KEY_GREEN,
+ [0x80] = KEY_BLUE,
+ [0x83] = KEY_YELLOW,
+ [0x70] = KEY_INFO, /* display */
+ [0x1a] = KEY_MENU, /* top menu */
+ [0x40] = KEY_CONTEXT_MENU, /* pop up/menu */
+ [0x0e] = KEY_ESC, /* return */
+ [0x5c] = KEY_OPTION, /* options/triangle */
+ [0x5d] = KEY_BACK, /* back/circle */
+ [0x5f] = KEY_SCREEN, /* view/square */
+ [0x5e] = BTN_0, /* cross */
+ [0x54] = KEY_UP,
+ [0x56] = KEY_DOWN,
+ [0x57] = KEY_LEFT,
+ [0x55] = KEY_RIGHT,
+ [0x0b] = KEY_ENTER,
+ [0x5a] = BTN_TL, /* L1 */
+ [0x58] = BTN_TL2, /* L2 */
+ [0x51] = BTN_THUMBL, /* L3 */
+ [0x5b] = BTN_TR, /* R1 */
+ [0x59] = BTN_TR2, /* R2 */
+ [0x52] = BTN_THUMBR, /* R3 */
+ [0x43] = KEY_HOMEPAGE, /* PS button */
+ [0x50] = KEY_SELECT,
+ [0x53] = BTN_START,
+ [0x33] = KEY_REWIND, /* scan back */
+ [0x32] = KEY_PLAY,
+ [0x34] = KEY_FORWARD, /* scan forward */
+ [0x30] = KEY_PREVIOUS,
+ [0x38] = KEY_STOP,
+ [0x31] = KEY_NEXT,
+ [0x60] = KEY_FRAMEBACK, /* slow/step back */
+ [0x39] = KEY_PAUSE,
+ [0x61] = KEY_FRAMEFORWARD, /* slow/step forward */
+ [0xff] = KEY_MAX,
+};
+
+static int ps3remote_decode(char *buff, int size, unsigned int *value)
+{
+ static unsigned int lastkey = 0;
+ static unsigned int lastmask = 0;
+ int retval, mask, key, i;
+
+ if (size < 12) {
+ error("Got a shorter packet! (size %i)\n", size);
+ return KEY_RESERVED;
+ }
+
+ mask = (buff[2] << 16) + (buff[3] << 8) + buff[4];
+ key = buff[5];
+
+ /* first, check flags */
+ for (i = 0; i < 24; i++) {
+ if ((lastmask & (1 << i)) == (mask & (1 << i)))
+ continue;
+ if (ps3remote_bits[i] == 0)
+ goto error;
+ retval = ps3remote_keymap[ps3remote_bits[i]];
+ if (mask & (1 << i))
+ /* key pressed */
+ *value = 1;
+ else
+ /* key released */
+ *value = 0;
+
+ goto out;
+ }
+
+ *value = buff[11];
+ if (buff[11] == 1) {
+ retval = ps3remote_keymap[key];
+ } else
+ retval = lastkey;
+
+ if (retval == KEY_RESERVED)
+ goto error;
+ if (retval == KEY_MAX)
+ return retval;
+
+ lastkey = retval;
+
+out:
+ fflush(stdout);
+
+ lastmask = mask;
+
+ return retval;
+
+error:
+ error("ps3remote: unrecognized sequence [%#x][%#x][%#x][%#x] [%#x],"
+ "last: [%#x][%#x][%#x][%#x]",
+ buff[2], buff[3], buff[4], buff[5], buff[11],
+ lastmask >> 16, lastmask >> 8 & 0xff,
+ lastmask & 0xff, lastkey);
+ return -1;
+}
+
+static gboolean ps3remote_event(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct fake_input *fake = data;
+ struct uinput_event event;
+ unsigned int key, value = 0;
+ gsize size;
+ char buff[50];
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ error("Hangup or error on rfcomm server socket");
+ goto failed;
+ }
+
+ memset(buff, 0, sizeof(buff));
+
+ if (g_io_channel_read(chan, buff, sizeof(buff), &size) !=
+ G_IO_ERROR_NONE) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ key = ps3remote_decode(buff, size, &value);
+ if (key == KEY_RESERVED) {
+ error("Got invalid key from decode");
+ goto failed;
+ } else if (key == KEY_MAX)
+ return TRUE;
+
+ memset(&event, 0, sizeof(event));
+ gettimeofday(&event.time, NULL);
+ event.type = EV_KEY;
+ event.code = key;
+ event.value = value;
+ if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
+ error("Error writing to uinput device");
+ goto failed;
+ }
+
+ memset(&event, 0, sizeof(event));
+ gettimeofday(&event.time, NULL);
+ event.type = EV_SYN;
+ event.code = SYN_REPORT;
+ if (write(fake->uinput, &event, sizeof(event)) != sizeof(event)) {
+ error("Error writing to uinput device");
+ goto failed;
+ }
+
+ return TRUE;
+
+failed:
+ ioctl(fake->uinput, UI_DEV_DESTROY);
+ close(fake->uinput);
+ fake->uinput = -1;
+ g_io_channel_unref(fake->io);
+
+ return FALSE;
+}
+
+static int ps3remote_setup_uinput(struct fake_input *fake,
+ struct fake_hid *fake_hid)
+{
+ struct uinput_dev dev;
+ int i;
+
+ fake->uinput = open("/dev/input/uinput", O_RDWR);
+ if (fake->uinput < 0) {
+ fake->uinput = open("/dev/uinput", O_RDWR);
+ if (fake->uinput < 0) {
+ fake->uinput = open("/dev/misc/uinput", O_RDWR);
+ if (fake->uinput < 0) {
+ error("Error opening uinput device file");
+ return 1;
+ }
+ }
+ }
+
+ memset(&dev, 0, sizeof(dev));
+ snprintf(dev.name, sizeof(dev.name), "%s", "PS3 Remote Controller");
+ dev.id.bustype = BUS_BLUETOOTH;
+ dev.id.vendor = fake_hid->vendor;
+ dev.id.product = fake_hid->product;
+
+ if (write(fake->uinput, &dev, sizeof(dev)) != sizeof(dev)) {
+ error("Error creating uinput device");
+ goto err;
+ }
+
+ /* enabling key events */
+ if (ioctl(fake->uinput, UI_SET_EVBIT, EV_KEY) < 0) {
+ error("Error enabling uinput device key events");
+ goto err;
+ }
+
+ /* enabling keys */
+ for (i = 0; i < 256; i++)
+ if (ps3remote_keymap[i] != KEY_RESERVED)
+ if (ioctl(fake->uinput, UI_SET_KEYBIT,
+ ps3remote_keymap[i]) < 0) {
+ error("Error enabling uinput key %i",
+ ps3remote_keymap[i]);
+ goto err;
+ }
+
+ /* creating the device */
+ if (ioctl(fake->uinput, UI_DEV_CREATE) < 0) {
+ error("Error creating uinput device");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ close(fake->uinput);
+ return 1;
+}
+
+static gboolean fake_hid_common_connect(struct fake_input *fake)
+{
+ return TRUE;
+}
+
+static int fake_hid_common_disconnect(struct fake_input *fake)
+{
+ return 0;
+}
+
+static struct fake_hid fake_hid_table[] = {
+ /* Sony PS3 remote device */
+ {
+ .vendor = 0x054c,
+ .product = 0x0306,
+ .connect = fake_hid_common_connect,
+ .disconnect = fake_hid_common_disconnect,
+ .event = ps3remote_event,
+ .setup_uinput = ps3remote_setup_uinput,
+ },
+
+ { },
+};
+
+static inline int fake_hid_match_device(uint16_t vendor, uint16_t product,
+ struct fake_hid *fhid)
+{
+ return vendor == fhid->vendor && product == fhid->product;
+}
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product)
+{
+ int i;
+
+ for (i = 0; fake_hid_table[i].vendor != 0; i++)
+ if (fake_hid_match_device(vendor, product, &fake_hid_table[i]))
+ return &fake_hid_table[i];
+
+ return NULL;
+}
+
+int fake_hid_connadd(struct fake_input *fake, int intr_sk,
+ struct fake_hid *fake_hid)
+{
+ if (fake_hid->setup_uinput(fake, fake_hid)) {
+ error("Error setting up uinput");
+ return ENOMEM;
+ }
+
+ fake->io = g_io_channel_unix_new(intr_sk);
+ g_io_channel_set_close_on_unref(fake->io, TRUE);
+ g_io_add_watch(fake->io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) fake_hid->event, fake);
+
+ return 0;
+}
diff --git a/input/fakehid.h b/input/fakehid.h
new file mode 100644
index 00000000..3bc2f3d6
--- /dev/null
+++ b/input/fakehid.h
@@ -0,0 +1,39 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+struct fake_hid;
+struct fake_input;
+
+struct fake_hid {
+ uint16_t vendor;
+ uint16_t product;
+ gboolean (*connect) (struct fake_input *fake_input);
+ int (*disconnect) (struct fake_input *fake_input);
+ gboolean (*event) (GIOChannel *chan, GIOCondition cond, gpointer data);
+ int (*setup_uinput) (struct fake_input *fake, struct fake_hid *fake_hid);
+};
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product);
+
+int fake_hid_connadd(struct fake_input *fake, int intr_sk,
+ struct fake_hid *fake_hid);
diff --git a/input/input-api.txt b/input/input-api.txt
new file mode 100644
index 00000000..8eb13126
--- /dev/null
+++ b/input/input-api.txt
@@ -0,0 +1,103 @@
+Bluetooth input service API description
+***************************************
+
+Copyright (C) 2006-2007 Marcel Holtmann <marcel@holtmann.org>
+
+
+Input Manager hierarchy
+=======================
+
+Interface org.bluez.input.Manager
+Object path /org/bluez/input
+
+Methods array{string} ListDevices()
+
+ Returns an array of available input devices path.
+
+ string CreateDevice(string address)
+
+ Create an input device object.
+
+ On success it will return the path of the
+ newly created device object.
+
+ Possible errors: org.bluez.Error.AlreadyExists
+ org.bluez.Error.NotSupported
+ org.bluez.Error.ConnectionAttemptFailed
+ org.bluez.Error.Failed
+
+ string CreateSecureDevice(string address)
+
+ Create an input device object. Pairing will
+ be initiated if needed(keyboard/combo devices).
+
+ On success it will return the path of the
+ newly created device object.
+
+ Possible errors: org.bluez.Error.AlreadyExists
+ org.bluez.Error.NotSupported
+ org.bluez.Error.ConnectionAttemptFailed
+ org.bluez.Error.Failed
+
+ void RemoveDevice(string path)
+
+ Remove the input device object for a given path.
+
+ Possible errors:org.bluez.Error.DoesNotExist
+ org.bluez.Error.Failed
+
+Signals void DeviceCreated(string path)
+
+ void DeviceRemoved(string path)
+
+
+Input Device hierarchy
+======================
+
+Interface org.bluez.input.Device
+Object path /org/bluez/input/{keyboard*|mouse*|...}
+
+Methods string GetAdapter()
+
+ Returns the adapter address.
+
+ Example: "00:11:22:33:44:55"
+
+ string GetAddress()
+
+ Returns the device address.
+
+ Example: "00:11:22:33:44:55"
+
+ string GetName()
+
+ Returns the service name.
+
+ uint16 GetProductId()
+
+ Returns the product id.
+
+ uint16 GetVendorId()
+
+ Returns the vendor id.
+
+ boolean IsConnected()
+
+ Returns the connection status.
+
+ void Connect()
+
+ Connect to the input device.
+
+ Possible errors: org.bluez.Error.AlreadyConnected
+ org.bluez.Error.ConnectionAttemptFailed
+
+ void Disconnect()
+
+ Disconnect from the input device.
+
+ Possible errors: org.bluez.Error.Failed
+
+Signals void Connected()
+
+ void Disconnected()
diff --git a/input/input.conf b/input/input.conf
new file mode 100644
index 00000000..abfb64f2
--- /dev/null
+++ b/input/input.conf
@@ -0,0 +1,9 @@
+# Configuration file for the input service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+
+# Set idle timeout (in minutes) before the connection will
+# be disconnect (defaults to 0 for no timeout)
+#IdleTimeout=30
diff --git a/input/main.c b/input/main.c
new file mode 100644
index 00000000..b269c76e
--- /dev/null
+++ b/input/main.c
@@ -0,0 +1,154 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "../hcid/device.h"
+#include "logging.h"
+#include "dbus-service.h"
+#include "manager.h"
+
+#define INPUT_INTERFACE "org.bluez.Input"
+
+static DBusMessage *input_connect(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *input_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *input_is_connected(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ dbus_bool_t connected = FALSE;
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+}
+
+static GDBusMethodTable input_methods[] = {
+ { "Connect", "", "", input_connect },
+ { "Disconnect", "", "", input_disconnect },
+ { "IsConnected", "", "b", input_is_connected },
+ { }
+};
+
+static GDBusSignalTable input_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { }
+};
+
+static DBusConnection *conn;
+
+static int input_probe(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+
+ if (g_dbus_register_interface(conn, device->path, INPUT_INTERFACE,
+ input_methods, input_signals, NULL,
+ device, NULL) == FALSE)
+ return -1;
+
+ return 0;
+}
+
+static void input_remove(struct btd_device *device)
+{
+ DBG("path %s", device->path);
+
+ g_dbus_unregister_interface(conn, device->path, INPUT_INTERFACE);
+}
+
+static struct btd_device_driver input_driver = {
+ .name = "input",
+ .uuids = BTD_UUIDS("00001124-0000-1000-8000-00805f9b34fb"),
+ .probe = input_probe,
+ .remove = input_remove,
+};
+
+static GKeyFile *load_config_file(const char *file)
+{
+ GKeyFile *keyfile;
+ GError *err = NULL;
+
+ keyfile = g_key_file_new();
+
+ if (!g_key_file_load_from_file(keyfile, file, 0, &err)) {
+ error("Parsing %s failed: %s", file, err->message);
+ g_error_free(err);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ return keyfile;
+}
+
+static int input_init(void)
+{
+ GKeyFile *config;
+
+ conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (conn == NULL)
+ return -EIO;
+
+ config = load_config_file(CONFIGDIR "/input.conf");
+
+ if (input_manager_init(conn, config) < 0) {
+ dbus_connection_unref(conn);
+ return -EIO;
+ }
+
+ if (config)
+ g_key_file_free(config);
+
+ btd_register_device_driver(&input_driver);
+
+ return 0;
+}
+
+static void input_exit(void)
+{
+ btd_unregister_device_driver(&input_driver);
+
+ input_manager_exit();
+
+ dbus_connection_unref(conn);
+}
+
+BLUETOOTH_PLUGIN_DEFINE("input", input_init, input_exit)
diff --git a/input/manager.c b/input/manager.c
new file mode 100644
index 00000000..2dfb9b2d
--- /dev/null
+++ b/input/manager.c
@@ -0,0 +1,801 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "device.h"
+#include "server.h"
+#include "error.h"
+#include "manager.h"
+#include "storage.h"
+#include "glib-helper.h"
+
+struct pending_req {
+ char *adapter_path; /* Local adapter D-Bus path */
+ bdaddr_t src; /* Local adapter BT address */
+ bdaddr_t dst; /* Peer BT address */
+ DBusConnection *conn;
+ DBusMessage *msg;
+ sdp_list_t *pnp_recs;
+ sdp_list_t *hid_recs;
+ GIOChannel *ctrl_channel;
+};
+
+static int idle_timeout = 0;
+
+static GSList *device_paths = NULL; /* Input registered paths */
+
+static DBusConnection *connection = NULL;
+
+static struct pending_req *pending_req_new(DBusConnection *conn,
+ DBusMessage *msg, bdaddr_t *src, bdaddr_t *dst)
+{
+ char adapter[18], adapter_path[32];
+ struct pending_req *pr;
+ int dev_id;
+
+ pr = g_try_new0(struct pending_req, 1);
+ if (!pr)
+ return NULL;
+
+ ba2str(src, adapter);
+ dev_id = hci_devid(adapter);
+ snprintf(adapter_path, 32, "/org/bluez/hci%d", dev_id);
+
+ pr->adapter_path = g_strdup(adapter_path);
+ bacpy(&pr->src, src);
+ bacpy(&pr->dst, dst);
+ pr->conn = dbus_connection_ref(conn);
+ pr->msg = dbus_message_ref(msg);
+
+ return pr;
+}
+
+static void pending_req_free(struct pending_req *pr)
+{
+ if (!pr)
+ return;
+
+ if (pr->adapter_path)
+ g_free(pr->adapter_path);
+
+ if (pr->conn)
+ dbus_connection_unref(pr->conn);
+
+ if (pr->msg)
+ dbus_message_unref(pr->msg);
+
+ if (pr->pnp_recs)
+ sdp_list_free(pr->pnp_recs, (sdp_free_func_t) sdp_record_free);
+
+ if (pr->hid_recs)
+ sdp_list_free(pr->hid_recs, (sdp_free_func_t) sdp_record_free);
+
+ g_free(pr);
+}
+
+static void epox_endian_quirk(unsigned char *data, int size)
+{
+ /* USAGE_PAGE (Keyboard) 05 07
+ * USAGE_MINIMUM (0) 19 00
+ * USAGE_MAXIMUM (65280) 2A 00 FF <= must be FF 00
+ * LOGICAL_MINIMUM (0) 15 00
+ * LOGICAL_MAXIMUM (65280) 26 00 FF <= must be FF 00
+ */
+ unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff,
+ 0x15, 0x00, 0x26, 0x00, 0xff };
+ int i;
+
+ if (!data)
+ return;
+
+ for (i = 0; i < size - sizeof(pattern); i++) {
+ if (!memcmp(data + i, pattern, sizeof(pattern))) {
+ data[i + 5] = 0xff;
+ data[i + 6] = 0x00;
+ data[i + 10] = 0xff;
+ data[i + 11] = 0x00;
+ }
+ }
+}
+
+static void extract_hid_record(sdp_record_t *rec, struct hidp_connadd_req *req)
+{
+ sdp_data_t *pdlist, *pdlist2;
+ uint8_t attr_val;
+
+ pdlist = sdp_data_get(rec, 0x0101);
+ pdlist2 = sdp_data_get(rec, 0x0102);
+ if (pdlist) {
+ if (pdlist2) {
+ if (strncmp(pdlist->val.str, pdlist2->val.str, 5)) {
+ strncpy(req->name, pdlist2->val.str, 127);
+ strcat(req->name, " ");
+ }
+ strncat(req->name, pdlist->val.str, 127 - strlen(req->name));
+ } else
+ strncpy(req->name, pdlist->val.str, 127);
+ } else {
+ pdlist2 = sdp_data_get(rec, 0x0100);
+ if (pdlist2)
+ strncpy(req->name, pdlist2->val.str, 127);
+ }
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION);
+ req->parser = pdlist ? pdlist->val.uint16 : 0x0100;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS);
+ req->subclass = pdlist ? pdlist->val.uint8 : 0;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE);
+ req->country = pdlist ? pdlist->val.uint8 : 0;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE);
+ attr_val = pdlist ? pdlist->val.uint8 : 0;
+ if (attr_val)
+ req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG);
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE);
+ attr_val = pdlist ? pdlist->val.uint8 : 0;
+ if (attr_val)
+ req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST);
+ if (pdlist) {
+ pdlist = pdlist->val.dataseq;
+ pdlist = pdlist->val.dataseq;
+ pdlist = pdlist->next;
+
+ req->rd_data = g_try_malloc0(pdlist->unitSize);
+ if (req->rd_data) {
+ memcpy(req->rd_data, (unsigned char *) pdlist->val.str,
+ pdlist->unitSize);
+ req->rd_size = pdlist->unitSize;
+ epox_endian_quirk(req->rd_data, req->rd_size);
+ }
+ }
+}
+
+static void extract_pnp_record(sdp_record_t *rec, struct hidp_connadd_req *req)
+{
+ sdp_data_t *pdlist;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID);
+ req->vendor = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID);
+ req->product = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_VERSION);
+ req->version = pdlist ? pdlist->val.uint16 : 0x0000;
+}
+
+static void interrupt_connect_cb(GIOChannel *chan, int err,
+ const bdaddr_t *src, const bdaddr_t *dst,
+ gpointer user_data)
+{
+ struct pending_req *pr = user_data;
+ struct hidp_connadd_req hidp;
+ const char *path;
+
+ memset(&hidp, 0, sizeof(hidp));
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ g_io_channel_close(chan);
+ g_io_channel_unref(chan);
+
+ hidp.idle_to = idle_timeout * 60;
+
+ extract_hid_record(pr->hid_recs->data, &hidp);
+ if (pr->pnp_recs)
+ extract_pnp_record(pr->pnp_recs->data, &hidp);
+
+ store_device_info(&pr->src, &pr->dst, &hidp);
+
+ if (input_device_register(pr->conn, &pr->src,
+ &pr->dst, &hidp, &path) < 0) {
+ error_failed(pr->conn, pr->msg, "path registration failed");
+ goto cleanup;
+ }
+
+ g_dbus_emit_signal(pr->conn, INPUT_PATH,
+ INPUT_MANAGER_INTERFACE, "DeviceCreated",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ device_paths = g_slist_append(device_paths, g_strdup(path));
+
+ g_dbus_send_reply(pr->conn, pr->msg, DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ goto cleanup;
+
+failed:
+ error_connection_attempt_failed(pr->conn, pr->msg, err);
+
+cleanup:
+ g_io_channel_close(pr->ctrl_channel);
+ g_io_channel_unref(pr->ctrl_channel);
+ pending_req_free(pr);
+
+ if (hidp.rd_data)
+ g_free(hidp.rd_data);
+}
+
+static void control_connect_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer user_data)
+{
+ struct pending_req *pr = user_data;
+
+ if (err < 0) {
+ error("connect(): %s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ /* Set HID control channel */
+ pr->ctrl_channel = chan;
+
+ /* Connect to the HID interrupt channel */
+ err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_INTR, 0,
+ interrupt_connect_cb, pr);
+ if (err < 0) {
+ error("L2CAP connect failed:%s (%d)", strerror(-err), -err);
+ goto failed;
+ }
+
+ return;
+
+failed:
+ error_connection_attempt_failed(pr->conn, pr->msg, -err);
+ pending_req_free(pr);
+}
+
+static void create_bonding_reply(DBusPendingCall *call, void *data)
+{
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ struct pending_req *pr = data;
+ DBusError derr;
+ int err;
+
+ dbus_error_init(&derr);
+ if (dbus_set_error_from_message(&derr, reply)) {
+ error("CreateBonding failed: %s(%s)",
+ derr.name, derr.message);
+ error_failed(pr->conn, pr->msg, "Authentication failed (CreateBonding)");
+ dbus_error_free(&derr);
+ dbus_message_unref(reply);
+ pending_req_free(pr);
+ return;
+ }
+
+ dbus_message_unref(reply);
+
+ err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_CTRL, 0,
+ control_connect_cb, pr);
+ if (err < 0) {
+ error("L2CAP connect failed:%s (%d)", strerror(-err), -err);
+ error_connection_attempt_failed(pr->conn, pr->msg, -err);
+ pending_req_free(pr);
+ }
+}
+
+static int create_bonding(struct pending_req *pr)
+{
+ DBusPendingCall *pending;
+ DBusMessage *msg;
+ char address[18], *addr_ptr = address;
+
+ msg = dbus_message_new_method_call("org.bluez", pr->adapter_path,
+ "org.bluez.Adapter", "CreateBonding");
+ if (!msg) {
+ error("Unable to allocate new method call");
+ return -1;
+ }
+
+ ba2str(&pr->dst, address);
+ dbus_message_append_args(msg, DBUS_TYPE_STRING, &addr_ptr, DBUS_TYPE_INVALID);
+ if (dbus_connection_send_with_reply(pr->conn, msg, &pending, -1) == FALSE) {
+ error("Can't send D-Bus message.");
+ dbus_message_unref(msg);
+ return -1;
+ }
+ dbus_pending_call_set_notify(pending, create_bonding_reply, pr, NULL);
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(msg);
+ return 0;
+}
+
+static void hid_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct pending_req *pr = user_data;
+
+ if (err < 0) {
+ error_not_supported(pr->conn, pr->msg);
+ error("SDP search error: %s (%d)", strerror(-err), -err);
+ goto fail;
+ }
+
+ if (!recs || !recs->data) {
+ error_not_supported(pr->conn, pr->msg);
+ error("Invalid HID service record length");
+ goto fail;
+ }
+
+ pr->hid_recs = recs;
+
+ if (strcmp("CreateSecureDevice", dbus_message_get_member(pr->msg)) == 0) {
+ sdp_data_t *d;
+
+ /* Pairing mandatory for keyboard and combo */
+ d = sdp_data_get(pr->hid_recs->data,
+ SDP_ATTR_HID_DEVICE_SUBCLASS);
+ if (d && (d->val.uint8 & 0x40) &&
+ !has_bonding(&pr->src, &pr->dst)) {
+ if (create_bonding(pr) < 0) {
+ error_failed(pr->conn, pr->msg,
+ "Unable to initialize bonding process");
+ goto fail;
+ }
+ /* Wait bonding reply */
+ return;
+ }
+
+ /* Otherwise proceede L2CAP connection */
+ }
+
+ /* No encryption or link key already exists -- connect control channel */
+ err = bt_l2cap_connect(&pr->src, &pr->dst, L2CAP_PSM_HIDP_CTRL, 0,
+ control_connect_cb, pr);
+ if (err < 0) {
+ error("L2CAP connect failed:%s (%d)", strerror(-err), -err);
+ error_connection_attempt_failed(pr->conn, pr->msg, -err);
+ goto fail;
+ }
+
+ /* Wait L2CAP connect */
+ return;
+
+fail:
+ pending_req_free(pr);
+}
+
+static void pnp_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct pending_req *pr = user_data;
+ uuid_t uuid;
+
+ if (err < 0) {
+ error_not_supported(pr->conn, pr->msg);
+ error("SDP search error: %s (%d)", strerror(-err), -err);
+ goto fail;
+ }
+
+ if (!recs || !recs->data) {
+ error_not_supported(pr->conn, pr->msg);
+ error("Invalid PnP service record length");
+ goto fail;
+ }
+
+ pr->pnp_recs = recs;
+ sdp_uuid16_create(&uuid, HID_SVCLASS_ID);
+ err = bt_search_service(&pr->src, &pr->dst, &uuid, hid_record_cb,
+ pr, NULL);
+ if (err < 0) {
+ error_not_supported(pr->conn, pr->msg);
+ error("HID service search request failed");
+ goto fail;
+ }
+
+ return;
+
+fail:
+ pending_req_free(pr);
+}
+
+static void headset_record_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct pending_req *pr = user_data;
+ sdp_record_t *rec;
+ sdp_list_t *protos;
+ const char *path;
+ uint8_t ch;
+
+ if (err < 0) {
+ error_not_supported(pr->conn, pr->msg);
+ error("SDP search error: %s (%d)", strerror(-err), -err);
+ goto fail;
+ }
+
+ if (!recs || !recs->data) {
+ error_not_supported(pr->conn, pr->msg);
+ error("Invalid headset service record length");
+ goto fail;
+ }
+
+ rec = recs->data;
+
+ if (sdp_get_access_protos(rec, &protos) < 0) {
+ error_not_supported(pr->conn, pr->msg);
+ goto fail;
+ }
+
+ ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+ sdp_record_free(rec);
+
+ if (ch <= 0) {
+ error_not_supported(pr->conn, pr->msg);
+ error("Invalid RFCOMM channel");
+ goto fail;
+ }
+
+ /* FIXME: Store the fake input data */
+
+ if (fake_input_register(pr->conn, &pr->src, &pr->dst, ch, &path) < 0) {
+ error("D-Bus path registration failed:%s", path);
+ error_failed(pr->conn, pr->msg, "Path registration failed");
+ goto fail;
+ }
+
+ g_dbus_emit_signal(pr->conn, INPUT_PATH,
+ INPUT_MANAGER_INTERFACE, "DeviceCreated",
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+ device_paths = g_slist_append(device_paths, g_strdup(path));
+
+ g_dbus_send_reply(pr->conn, pr->msg, DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID);
+
+fail:
+ pending_req_free(pr);
+}
+
+static inline DBusMessage *adapter_not_available(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Adapter not available");
+}
+
+static inline DBusMessage *already_exists(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists",
+ "Input Already exists");
+}
+
+static inline DBusMessage *not_supported(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported",
+ "Not supported");
+}
+
+static inline DBusMessage *does_not_exist(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists",
+ "Input doesn't exist");
+}
+
+static DBusMessage *create_device(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct pending_req *pr;
+ const char *addr;
+ bdaddr_t src, dst;
+ uint32_t cls = 0;
+ int dev_id, err;
+ uuid_t uuid;
+ bt_callback_t cb;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ /* Get the default adapter */
+ dev_id = hci_get_route(NULL);
+ if (dev_id < 0) {
+ error("Bluetooth adapter not available");
+ return adapter_not_available(msg);
+ }
+
+ if (hci_devba(dev_id, &src) < 0) {
+ error("Can't get local adapter device info");
+ return adapter_not_available(msg);
+ }
+
+ str2ba(addr, &dst);
+ if (input_device_is_registered(&src, &dst))
+ return already_exists(msg);
+
+ if (read_device_class(&src, &dst, &cls) < 0) {
+ error("Device class not available");
+ return not_supported(msg);
+ }
+
+ pr = pending_req_new(conn, msg, &src, &dst);
+ if (!pr)
+ return NULL;
+
+ switch (cls & 0x1f00) {
+ case 0x0500: /* Peripheral */
+ case 0x0200: /* Phone */
+ sdp_uuid16_create(&uuid, PNP_INFO_SVCLASS_ID);
+ cb = pnp_record_cb;
+ break;
+ case 0x0400: /* Fake input */
+ sdp_uuid16_create(&uuid, HEADSET_SVCLASS_ID);
+ cb = headset_record_cb;
+ break;
+ default:
+ pending_req_free(pr);
+ return not_supported(msg);
+ }
+
+ err = bt_search_service(&src, &dst, &uuid, cb, pr, NULL);
+ if (err < 0) {
+ pending_req_free(pr);
+ return not_supported(msg);
+ }
+
+ return NULL;
+}
+
+static DBusMessage *remove_device(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ GSList *l;
+ const char *path;
+ int err;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return NULL;
+
+ l = g_slist_find_custom(device_paths, path, (GCompareFunc) strcmp);
+ if (!l)
+ return does_not_exist(msg);
+
+ err = input_device_unregister(conn, path);
+ if (err < 0)
+ return create_errno_message(msg, -err);
+
+ g_free(l->data);
+ device_paths = g_slist_remove(device_paths, l->data);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *list_devices(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter, iter_array;
+ DBusMessage *reply;
+ GSList *paths;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &iter_array);
+
+ for (paths = device_paths; paths != NULL; paths = paths->next) {
+ const char *ppath = paths->data;
+ dbus_message_iter_append_basic(&iter_array,
+ DBUS_TYPE_STRING, &ppath);
+ }
+
+ dbus_message_iter_close_container(&iter, &iter_array);
+
+ return reply;
+}
+
+static void manager_unregister(void *data)
+{
+ info("Unregistered manager path");
+
+ g_slist_foreach(device_paths, (GFunc) free, NULL);
+
+ g_slist_free(device_paths);
+}
+
+/*
+ * Stored inputs registration functions
+ */
+
+static void stored_input(char *key, char *value, void *data)
+{
+ const char *path;
+ struct hidp_connadd_req hidp;
+ bdaddr_t dst, *src = data;
+
+ str2ba(key, &dst);
+
+ memset(&hidp, 0, sizeof(hidp));
+
+ if (parse_stored_device_info(value, &hidp) < 0)
+ return;
+
+ /*
+ * Repeated entries for the same remote device are
+ * acceptable since the source is different.
+ */
+ if (input_device_register(connection, src, &dst, &hidp, &path) < 0)
+ goto cleanup;
+
+ device_paths = g_slist_append(device_paths, g_strdup(path));
+cleanup:
+ if (hidp.rd_data)
+ g_free(hidp.rd_data);
+}
+
+/* hidd to input transition function */
+static void stored_hidd(char *key, char *value, void *data)
+{
+ struct hidp_connadd_req hidp;
+ char *str, filename[PATH_MAX + 1], addr[18];
+ bdaddr_t dst, *src = data;
+
+ ba2str(src, addr);
+ create_name(filename, PATH_MAX, STORAGEDIR, addr, "input");
+
+ str = textfile_get(filename, key);
+ if (str) {
+ /* Skip: entry found in input file */
+ free(str);
+ return;
+ }
+
+ memset(&hidp, 0, sizeof(hidp));
+
+ if (parse_stored_hidd(value, &hidp) < 0)
+ return;
+
+ str2ba(key, &dst);
+ store_device_info(src, &dst, &hidp);
+ if (hidp.rd_data)
+ g_free(hidp.rd_data);
+}
+
+static void register_stored_inputs(void)
+{
+ char dirname[PATH_MAX + 1];
+ char filename[PATH_MAX + 1];
+ struct dirent *de;
+ DIR *dir;
+ bdaddr_t src;
+
+ snprintf(dirname, PATH_MAX, "%s", STORAGEDIR);
+
+ dir = opendir(dirname);
+ if (!dir)
+ return;
+
+ while ((de = readdir(dir)) != NULL) {
+ if (!isdigit(de->d_name[0]))
+ continue;
+
+ str2ba(de->d_name, &src);
+
+ /* move the hidd entries to the input storage */
+ create_name(filename, PATH_MAX, STORAGEDIR,
+ de->d_name, "hidd");
+ textfile_foreach(filename, stored_hidd, &src);
+
+ /* load the input stored devices */
+ create_name(filename, PATH_MAX, STORAGEDIR,
+ de->d_name, "input");
+
+ textfile_foreach(filename, stored_input, &src);
+ }
+
+ closedir(dir);
+}
+
+static GDBusMethodTable manager_methods[] = {
+ { "ListDevices", "", "as", list_devices },
+ { "CreateDevice", "s", "s", create_device,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "CreateSecureDevice", "s", "s", create_device,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "RemoveDevice", "s", "", remove_device },
+ { }
+};
+
+static GDBusSignalTable manager_signals[] = {
+ { "DeviceCreated", "s" },
+ { "DeviceRemoved", "s" },
+ { }
+};
+
+int input_manager_init(DBusConnection *conn, GKeyFile *config)
+{
+ GError *err = NULL;
+
+ if (config) {
+ idle_timeout = g_key_file_get_integer(config, "General",
+ "IdleTimeout", &err);
+ if (err) {
+ debug("input.conf: %s", err->message);
+ g_error_free(err);
+ }
+ }
+
+ if (g_dbus_register_interface(conn, INPUT_PATH, INPUT_MANAGER_INTERFACE,
+ manager_methods, manager_signals, NULL,
+ NULL, manager_unregister) == FALSE) {
+ error("Failed to register %s interface to %s",
+ INPUT_MANAGER_INTERFACE, INPUT_PATH);
+ return -1;
+ }
+
+ connection = dbus_connection_ref(conn);
+
+ info("Registered input manager path:%s", INPUT_PATH);
+
+ /* Register well known HID devices */
+ register_stored_inputs();
+
+ server_start();
+
+ return 0;
+}
+
+void input_manager_exit(void)
+{
+ g_dbus_unregister_interface(connection, INPUT_PATH,
+ INPUT_MANAGER_INTERFACE);
+
+ server_stop();
+
+ dbus_connection_unref(connection);
+
+ connection = NULL;
+}
diff --git a/input/manager.h b/input/manager.h
new file mode 100644
index 00000000..8f9d999e
--- /dev/null
+++ b/input/manager.h
@@ -0,0 +1,28 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define INPUT_PATH "/org/bluez/input"
+#define INPUT_MANAGER_INTERFACE "org.bluez.input.Manager"
+
+int input_manager_init(DBusConnection *conn, GKeyFile *config);
+void input_manager_exit(void);
diff --git a/input/server.c b/input/server.c
new file mode 100644
index 00000000..9267e4aa
--- /dev/null
+++ b/input/server.c
@@ -0,0 +1,153 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+
+#include "device.h"
+#include "server.h"
+#include "storage.h"
+#include "dbus-service.h"
+#include "glib-helper.h"
+
+static const char *HID_UUID = "00001124-0000-1000-8000-00805f9b34fb";
+
+struct authorization_data {
+ bdaddr_t src;
+ bdaddr_t dst;
+};
+
+static void auth_callback(DBusError *derr, void *user_data)
+{
+ struct authorization_data *auth = user_data;
+
+ if (derr) {
+ error("Access denied: %s", derr->message);
+ if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY))
+ service_cancel_auth(&auth->src, &auth->dst);
+
+ input_device_close_channels(&auth->src, &auth->dst);
+ } else
+ input_device_connadd(&auth->src, &auth->dst);
+
+ g_free(auth);
+}
+
+static int authorize_device(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct authorization_data *auth;
+
+ auth = g_new0(struct authorization_data, 1);
+ bacpy(&auth->src, src);
+ bacpy(&auth->dst, dst);
+
+ return service_req_auth(src, dst, HID_UUID,
+ auth_callback, auth);
+}
+
+static void connect_event_cb(GIOChannel *chan, int err, const bdaddr_t *src,
+ const bdaddr_t *dst, gpointer data)
+{
+ int sk, psm = GPOINTER_TO_UINT(data);
+
+ if (err < 0) {
+ error("accept: %s (%d)", strerror(-err), -err);
+ return;
+ }
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ debug("Incoming connection on PSM %d", psm);
+
+ if (input_device_set_channel(src, dst, psm, sk) < 0) {
+ /* Send unplug virtual cable to unknown devices */
+ if (psm == L2CAP_PSM_HIDP_CTRL) {
+ unsigned char unplug[] = { 0x15 };
+ int err;
+ err = write(sk, unplug, sizeof(unplug));
+ }
+ close(sk);
+ return;
+ }
+
+ if ((psm == L2CAP_PSM_HIDP_INTR) && (authorize_device(src, dst) < 0))
+ input_device_close_channels(src, dst);
+
+ return;
+}
+
+static GIOChannel *ctrl_io = NULL;
+static GIOChannel *intr_io = NULL;
+
+int server_start(void)
+{
+ ctrl_io = bt_l2cap_listen(BDADDR_ANY, L2CAP_PSM_HIDP_CTRL, 0, 0,
+ connect_event_cb,
+ GUINT_TO_POINTER(L2CAP_PSM_HIDP_CTRL));
+ if (!ctrl_io) {
+ error("Failed to listen on control channel");
+ return -1;
+ }
+ g_io_channel_set_close_on_unref(ctrl_io, TRUE);
+
+ intr_io = bt_l2cap_listen(BDADDR_ANY, L2CAP_PSM_HIDP_INTR, 0, 0,
+ connect_event_cb,
+ GUINT_TO_POINTER(L2CAP_PSM_HIDP_INTR));
+ if (!intr_io) {
+ error("Failed to listen on interrupt channel");
+ g_io_channel_unref(ctrl_io);
+ ctrl_io = NULL;
+ }
+ g_io_channel_set_close_on_unref(intr_io, TRUE);
+
+ return 0;
+}
+
+void server_stop(void)
+{
+ if (intr_io)
+ g_io_channel_unref(intr_io);
+
+ if (ctrl_io)
+ g_io_channel_unref(ctrl_io);
+}
diff --git a/input/server.h b/input/server.h
new file mode 100644
index 00000000..75cf704c
--- /dev/null
+++ b/input/server.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+int server_start(void);
+void server_stop(void);
diff --git a/input/storage.c b/input/storage.c
new file mode 100644
index 00000000..25894a5b
--- /dev/null
+++ b/input/storage.c
@@ -0,0 +1,364 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hidp.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "storage.h"
+
+static inline int create_filename(char *buf, size_t size,
+ bdaddr_t *bdaddr, const char *name)
+{
+ char addr[18];
+
+ ba2str(bdaddr, addr);
+
+ return create_name(buf, size, STORAGEDIR, addr, name);
+}
+
+int parse_stored_hidd(const char *str, struct hidp_connadd_req *req)
+{
+ char tmp[3];
+ char *desc;
+ unsigned int vendor, product, version, subclass, country, parser, pos;
+ int i;
+
+ desc = malloc(4096);
+ if (!desc)
+ return -ENOMEM;
+
+ memset(desc, 0, 4096);
+
+ sscanf(str, "%04X:%04X:%04X %02X %02X %04X %4095s %08X %n",
+ &vendor, &product, &version, &subclass, &country,
+ &parser, desc, &req->flags, &pos);
+
+ req->vendor = vendor;
+ req->product = product;
+ req->version = version;
+ req->subclass = subclass;
+ req->country = country;
+ req->parser = parser;
+
+ req->rd_size = strlen(desc) / 2;
+ req->rd_data = g_try_malloc0(req->rd_size);
+ if (!req->rd_data) {
+ g_free(desc);
+ return -ENOMEM;
+ }
+
+ memset(tmp, 0, sizeof(tmp));
+ for (i = 0; i < req->rd_size; i++) {
+ memcpy(tmp, desc + (i * 2), 2);
+ req->rd_data[i] = (uint8_t) strtol(tmp, NULL, 16);
+ }
+
+ g_free(desc);
+
+ return 0;
+}
+
+int parse_stored_device_info(const char *str, struct hidp_connadd_req *req)
+{
+ char tmp[3];
+ const char *desc;
+ unsigned int vendor, product, version, subclass, country, parser, pos;
+ size_t len;
+ int i;
+
+ sscanf(str, "%04X:%04X:%04X %02X %02X %04X %08X %n",
+ &vendor, &product, &version, &subclass, &country,
+ &parser, &req->flags, &pos);
+
+ desc = &str[pos];
+ len = strlen(desc);
+ if (len <= 0)
+ return -ENOENT;
+
+ req->vendor = vendor;
+ req->product = product;
+ req->version = version;
+ req->subclass = subclass;
+ req->country = country;
+ req->parser = parser;
+
+ req->rd_size = len / 2;
+ req->rd_data = g_try_malloc0(req->rd_size);
+ if (!req->rd_data) {
+ return -ENOMEM;
+ }
+
+ memset(tmp, 0, sizeof(tmp));
+ for (i = 0; i < req->rd_size; i++) {
+ memcpy(tmp, desc + (i * 2), 2);
+ req->rd_data[i] = (uint8_t) strtol(tmp, NULL, 16);
+ }
+
+ return 0;
+}
+
+int get_stored_device_info(bdaddr_t *src, bdaddr_t *dst,
+ struct hidp_connadd_req *req)
+{
+ char filename[PATH_MAX + 1], *str;
+ char peer[18];
+ int err;
+
+ create_filename(filename, PATH_MAX, src, "input");
+
+ ba2str(dst, peer);
+ str = textfile_get(filename, peer);
+ if (!str)
+ return -ENOENT;
+
+ err = parse_stored_device_info(str, req);
+
+ free(str);
+
+ return err;
+}
+
+int del_stored_device_info(bdaddr_t *src, bdaddr_t *dst)
+{
+ char filename[PATH_MAX + 1];
+ char addr[18];
+
+ ba2str(dst, addr);
+
+ create_filename(filename, PATH_MAX, src, "hidd");
+ textfile_del(filename, addr);
+
+ create_filename(filename, PATH_MAX, src, "input");
+ return textfile_del(filename, addr);
+}
+
+int store_device_info(bdaddr_t *src, bdaddr_t *dst, struct hidp_connadd_req *req)
+{
+ char filename[PATH_MAX + 1], *str, *desc;
+ int i, err, size;
+ char addr[18];
+
+ create_filename(filename, PATH_MAX, src, "input");
+
+ size = 15 + 3 + 3 + 5 + (req->rd_size * 2) + 2 + 9;
+ str = g_try_malloc0(size);
+ if (!str)
+ return -ENOMEM;
+
+ desc = g_try_malloc0((req->rd_size * 2) + 1);
+ if (!desc) {
+ g_free(str);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < req->rd_size; i++)
+ sprintf(desc + (i * 2), "%2.2X", req->rd_data[i]);
+
+ snprintf(str, size - 1, "%04X:%04X:%04X %02X %02X %04X %08X %s",
+ req->vendor, req->product, req->version,
+ req->subclass, req->country, req->parser,
+ req->flags, desc);
+
+ g_free(desc);
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(dst, addr);
+
+ err = textfile_put(filename, addr, str);
+
+ g_free(str);
+
+ return err;
+}
+
+int read_device_name(bdaddr_t *src, bdaddr_t *dst, char **name)
+{
+ char filename[PATH_MAX + 1], addr[18], *str;
+ int len;
+
+ create_filename(filename, PATH_MAX, src, "names");
+
+ ba2str(dst, addr);
+ str = textfile_get(filename, addr);
+ if (!str)
+ return -ENOENT;
+
+ len = strlen(str);
+
+ /* HID max name size is 128 chars */
+ if (len < 128) {
+ *name = str;
+ return 0;
+ }
+
+ *name = g_try_malloc0(128);
+ if (!*name)
+ return -ENOMEM;
+
+ snprintf(*name, 128, "%s", str);
+
+ free(str);
+
+ return 0;
+}
+
+int read_device_class(bdaddr_t *src, bdaddr_t *dst, uint32_t *cls)
+{
+ char filename[PATH_MAX + 1], *str;
+ char addr[18];
+
+ ba2str(src, addr);
+ create_name(filename, PATH_MAX, STORAGEDIR, addr, "classes");
+
+ ba2str(dst, addr);
+ str = textfile_get(filename, addr);
+ if (!str)
+ return -ENOENT;
+
+ if (sscanf(str, "%x", cls) != 1) {
+ g_free(str);
+ return -ENOENT;
+ }
+
+ g_free(str);
+
+ return 0;
+}
+
+int encrypt_link(bdaddr_t *src, bdaddr_t *dst)
+{
+ char filename[PATH_MAX + 1];
+ struct hci_conn_info_req *cr;
+ int dd, err, dev_id;
+ char addr[18], *str;
+
+ create_filename(filename, PATH_MAX, src, "linkkeys");
+
+ ba2str(dst, addr);
+
+ str = textfile_get(filename, addr);
+ if (!str) {
+ error("Encryption link key not found");
+ return -ENOKEY;
+ }
+
+ free(str);
+
+ cr = g_try_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info));
+ if (!cr)
+ return -ENOMEM;
+
+ ba2str(src, addr);
+
+ dev_id = hci_devid(addr);
+ if (dev_id < 0) {
+ g_free(cr);
+ return -errno;
+ }
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ g_free(cr);
+ return -errno;
+ }
+
+ bacpy(&cr->bdaddr, dst);
+ cr->type = ACL_LINK;
+
+ if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0)
+ goto fail;
+
+ if (cr->conn_info->link_mode & HCI_LM_ENCRYPT) {
+ /* Already encrypted */
+ goto done;
+ }
+
+ if (hci_authenticate_link(dd, htobs(cr->conn_info->handle), 1000) < 0) {
+ error("Link authentication failed: %s (%d)",
+ strerror(errno), errno);
+ goto fail;
+ }
+
+ if (hci_encrypt_link(dd, htobs(cr->conn_info->handle), 1, 1000) < 0) {
+ error("Link encryption failed: %s (%d)",
+ strerror(errno), errno);
+ goto fail;
+ }
+
+done:
+ g_free(cr);
+
+ hci_close_dev(dd);
+
+ return 0;
+
+fail:
+ g_free(cr);
+
+ err = errno;
+ hci_close_dev(dd);
+
+ return -err;
+}
+
+gboolean has_bonding(bdaddr_t *src, bdaddr_t *dst)
+{
+ char filename[PATH_MAX + 1];
+ char addr[18], *str;
+
+ create_filename(filename, PATH_MAX, src, "linkkeys");
+
+ ba2str(dst, addr);
+
+ str = textfile_get(filename, addr);
+ if (!str)
+ return FALSE;
+
+ free(str);
+
+ return TRUE;
+}
diff --git a/input/storage.h b/input/storage.h
new file mode 100644
index 00000000..90ba4a87
--- /dev/null
+++ b/input/storage.h
@@ -0,0 +1,41 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+int get_stored_device_info(bdaddr_t *src, bdaddr_t *dst,
+ struct hidp_connadd_req *req);
+
+int del_stored_device_info(bdaddr_t *src, bdaddr_t *dst);
+
+int store_device_info(bdaddr_t *src, bdaddr_t *dst,
+ struct hidp_connadd_req *req);
+
+int parse_stored_hidd(const char *str, struct hidp_connadd_req *req);
+int parse_stored_device_info(const char *str,
+ struct hidp_connadd_req *req);
+
+int read_device_name(bdaddr_t *src, bdaddr_t *dst, char **name);
+int read_device_class(bdaddr_t *src, bdaddr_t *dst, uint32_t *cls);
+
+int encrypt_link(bdaddr_t *src, bdaddr_t *dst);
+
+gboolean has_bonding(bdaddr_t *src, bdaddr_t *dst);
diff --git a/input/test-input b/input/test-input
new file mode 100755
index 00000000..7f41b6f6
--- /dev/null
+++ b/input/test-input
@@ -0,0 +1,45 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'),
+ 'org.bluez.Manager')
+
+conn = manager.ActivateService('input')
+
+input = dbus.Interface(bus.get_object(conn, '/org/bluez/input'),
+ 'org.bluez.input.Manager')
+
+if (len(sys.argv) < 2):
+ print "Usage: %s <command>" % (sys.argv[0])
+ print ""
+ print " list"
+ print " create <address>"
+ print " remove <path>"
+ sys.exit(1)
+
+if (sys.argv[1] == "list"):
+ list = input.ListDevices()
+ print list
+ sys.exit(0)
+
+if (sys.argv[1] == "create"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ device = input.CreateSecureDevice(sys.argv[2])
+ print device
+ sys.exit(0)
+
+if (sys.argv[1] == "remove"):
+ if (len(sys.argv) < 3):
+ print "Need object path parameter"
+ else:
+ input.RemoveDevice(sys.argv[2])
+ sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)