summaryrefslogtreecommitdiffstats
path: root/input/device.c
diff options
context:
space:
mode:
Diffstat (limited to 'input/device.c')
-rw-r--r--input/device.c1171
1 files changed, 1171 insertions, 0 deletions
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;
+}