summaryrefslogtreecommitdiff
path: root/input
diff options
context:
space:
mode:
authorAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
committerAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
commit799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch)
treea8c3be85c730de28b012586591b76301033d3d21 /input
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'input')
-rw-r--r--input/device.c1265
-rw-r--r--input/device.h56
-rw-r--r--input/fakehid.c411
-rw-r--r--input/fakehid.h40
-rw-r--r--input/input.conf9
-rw-r--r--input/main.c86
-rw-r--r--input/manager.c207
-rw-r--r--input/manager.h25
-rw-r--r--input/server.c237
-rw-r--r--input/server.h25
10 files changed, 2361 insertions, 0 deletions
diff --git a/input/device.c b/input/device.c
new file mode 100644
index 0000000..554f5ac
--- /dev/null
+++ b/input/device.c
@@ -0,0 +1,1265 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+#include "uinput.h"
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+#include "../src/storage.h"
+#include "../src/manager.h"
+#include "../src/dbus-common.h"
+
+#include "device.h"
+#include "error.h"
+#include "fakehid.h"
+#include "btio.h"
+
+#define INPUT_DEVICE_INTERFACE "org.bluez.Input"
+
+#define BUF_SIZE 16
+
+#define UPDOWN_ENABLED 1
+
+#define FI_FLAG_CONNECTED 1
+
+struct input_conn {
+ struct fake_input *fake;
+ DBusMessage *pending_connect;
+ char *uuid;
+ char *alias;
+ GIOChannel *ctrl_io;
+ GIOChannel *intr_io;
+ guint ctrl_watch;
+ guint intr_watch;
+ int timeout;
+ struct input_device *idev;
+};
+
+struct input_device {
+ DBusConnection *conn;
+ char *path;
+ bdaddr_t src;
+ bdaddr_t dst;
+ uint32_t handle;
+ guint dc_id;
+ char *name;
+ struct btd_device *device;
+ GSList *connections;
+};
+
+GSList *devices = NULL;
+
+static struct input_device *find_device_by_path(GSList *list, const char *path)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct input_device *idev = l->data;
+
+ if (!strcmp(idev->path, path))
+ return idev;
+ }
+
+ return NULL;
+}
+
+static struct input_conn *find_connection(GSList *list, const char *pattern)
+{
+ GSList *l;
+
+ for (l = list; l; l = l->next) {
+ struct input_conn *iconn = l->data;
+
+ if (!strcasecmp(iconn->uuid, pattern))
+ return iconn;
+
+ if (!strcasecmp(iconn->alias, pattern))
+ return iconn;
+ }
+
+ return NULL;
+}
+
+static void input_conn_free(struct input_conn *iconn)
+{
+ if (iconn->pending_connect)
+ dbus_message_unref(iconn->pending_connect);
+
+ if (iconn->ctrl_watch)
+ g_source_remove(iconn->ctrl_watch);
+
+ if (iconn->intr_watch)
+ g_source_remove(iconn->intr_watch);
+
+ if (iconn->intr_io)
+ g_io_channel_unref(iconn->intr_io);
+
+ if (iconn->ctrl_io)
+ g_io_channel_unref(iconn->ctrl_io);
+
+ g_free(iconn->uuid);
+ g_free(iconn->alias);
+ g_free(iconn->fake);
+ g_free(iconn);
+}
+
+static void input_device_free(struct input_device *idev)
+{
+ if (idev->dc_id)
+ device_remove_disconnect_watch(idev->device, idev->dc_id);
+
+ dbus_connection_unref(idev->conn);
+ btd_device_unref(idev->device);
+ g_free(idev->name);
+ g_free(idev->path);
+ 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 - 1);
+
+ 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 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];
+ ssize_t bread = 0, bwritten;
+ uint16_t key;
+ int fd;
+
+ 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;
+ }
+
+ fd = g_io_channel_unix_get_fd(chan);
+
+ memset(buf, 0, BUF_SIZE);
+ bread = read(fd, buf, sizeof(buf) - 1);
+ if (bread < 0) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ DBG("Received: %s", buf);
+
+ bwritten = write(fd, ok, 6);
+ if (bwritten < 0) {
+ 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 void rfcomm_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ struct input_conn *iconn = user_data;
+ struct input_device *idev = iconn->idev;
+ struct fake_input *fake = iconn->fake;
+ DBusMessage *reply;
+
+ if (err) {
+ reply = btd_error_failed(iconn->pending_connect, err->message);
+ goto failed;
+ }
+
+ fake->rfcomm = g_io_channel_unix_get_fd(chan);
+
+ /*
+ * FIXME: Some headsets required a sco connection
+ * first to report volume gain key events
+ */
+ fake->uinput = uinput_create(idev->name);
+ if (fake->uinput < 0) {
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ reply = btd_error_failed(iconn->pending_connect,
+ strerror(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(iconn->pending_connect);
+ g_dbus_send_message(idev->conn, reply);
+
+ dbus_message_unref(iconn->pending_connect);
+ iconn->pending_connect = NULL;
+
+ return;
+
+failed:
+ g_dbus_send_message(idev->conn, reply);
+ dbus_message_unref(iconn->pending_connect);
+ iconn->pending_connect = NULL;
+}
+
+static gboolean rfcomm_connect(struct input_conn *iconn, GError **err)
+{
+ struct input_device *idev = iconn->idev;
+ GIOChannel *io;
+
+ io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, iconn,
+ NULL, err,
+ BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+ BT_IO_OPT_DEST_BDADDR, &idev->dst,
+ BT_IO_OPT_INVALID);
+ if (!io)
+ return FALSE;
+
+ g_io_channel_unref(io);
+
+ return TRUE;
+}
+
+static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct input_conn *iconn = data;
+ struct input_device *idev = iconn->idev;
+ gboolean connected = FALSE;
+
+ /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since
+ * it's likely that ctrl_watch_cb has been queued for dispatching in
+ * this mainloop iteration */
+ if ((cond & (G_IO_HUP | G_IO_ERR)) && iconn->ctrl_watch)
+ g_io_channel_shutdown(chan, TRUE, NULL);
+
+ emit_property_changed(idev->conn, idev->path, INPUT_DEVICE_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+ device_remove_disconnect_watch(idev->device, idev->dc_id);
+ idev->dc_id = 0;
+
+ iconn->intr_watch = 0;
+
+ g_io_channel_unref(iconn->intr_io);
+ iconn->intr_io = NULL;
+
+ /* Close control channel */
+ if (iconn->ctrl_io && !(cond & G_IO_NVAL))
+ g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+ return FALSE;
+}
+
+static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct input_conn *iconn = data;
+
+ /* Checking for intr_watch avoids a double g_io_channel_shutdown since
+ * it's likely that intr_watch_cb has been queued for dispatching in
+ * this mainloop iteration */
+ if ((cond & (G_IO_HUP | G_IO_ERR)) && iconn->intr_watch)
+ g_io_channel_shutdown(chan, TRUE, NULL);
+
+ iconn->ctrl_watch = 0;
+
+ g_io_channel_unref(iconn->ctrl_io);
+ iconn->ctrl_io = NULL;
+
+ /* Close interrupt channel */
+ if (iconn->intr_io && !(cond & G_IO_NVAL))
+ g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+
+ return FALSE;
+}
+
+static gboolean fake_hid_connect(struct input_conn *iconn, GError **err)
+{
+ struct fake_hid *fhid = iconn->fake->priv;
+
+ return fhid->connect(iconn->fake, err);
+}
+
+static int fake_hid_disconnect(struct input_conn *iconn)
+{
+ struct fake_hid *fhid = iconn->fake->priv;
+
+ return fhid->disconnect(iconn->fake);
+}
+
+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 };
+ unsigned 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 int ioctl_connadd(struct hidp_connadd_req *req)
+{
+ int ctl, err = 0;
+
+ ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP);
+ if (ctl < 0)
+ return -errno;
+
+ if (ioctl(ctl, HIDPCONNADD, req) < 0)
+ err = errno;
+
+ close(ctl);
+
+ return -err;
+}
+
+static void encrypt_completed(uint8_t status, gpointer user_data)
+{
+ struct hidp_connadd_req *req = user_data;
+ int err;
+
+ if (status) {
+ error("Encryption failed: %s(0x%x)",
+ strerror(bt_error(status)), status);
+ goto failed;
+ }
+
+ err = ioctl_connadd(req);
+ if (err == 0)
+ goto cleanup;
+
+ error("ioctl_connadd(): %s(%d)", strerror(-err), -err);
+failed:
+ close(req->intr_sock);
+ close(req->ctrl_sock);
+
+cleanup:
+ free(req->rd_data);
+
+ g_free(req);
+}
+
+static int hidp_add_connection(const struct input_device *idev,
+ const struct input_conn *iconn)
+{
+ struct hidp_connadd_req *req;
+ struct fake_hid *fake_hid;
+ struct fake_input *fake;
+ sdp_record_t *rec;
+ char src_addr[18], dst_addr[18];
+ int err;
+
+ req = g_new0(struct hidp_connadd_req, 1);
+ req->ctrl_sock = g_io_channel_unix_get_fd(iconn->ctrl_io);
+ req->intr_sock = g_io_channel_unix_get_fd(iconn->intr_io);
+ req->flags = 0;
+ req->idle_to = iconn->timeout;
+
+ ba2str(&idev->src, src_addr);
+ ba2str(&idev->dst, dst_addr);
+
+ rec = fetch_record(src_addr, dst_addr, idev->handle);
+ if (!rec) {
+ error("Rejected connection from unknown device %s", dst_addr);
+ err = -EPERM;
+ goto cleanup;
+ }
+
+ extract_hid_record(rec, req);
+ sdp_record_free(rec);
+
+ read_device_id(src_addr, dst_addr, NULL,
+ &req->vendor, &req->product, &req->version);
+
+ fake_hid = get_fake_hid(req->vendor, req->product);
+ if (fake_hid) {
+ err = 0;
+ fake = g_new0(struct fake_input, 1);
+ fake->connect = fake_hid_connect;
+ fake->disconnect = fake_hid_disconnect;
+ fake->priv = fake_hid;
+ fake->idev = idev;
+ fake = fake_hid_connadd(fake, iconn->intr_io, fake_hid);
+ if (fake == NULL)
+ err = -ENOMEM;
+ else
+ fake->flags |= FI_FLAG_CONNECTED;
+ goto cleanup;
+ }
+
+ if (idev->name)
+ strncpy(req->name, idev->name, sizeof(req->name) - 1);
+
+ /* Encryption is mandatory for keyboards */
+ if (req->subclass & 0x40) {
+ struct btd_adapter *adapter = device_get_adapter(idev->device);
+
+ err = btd_adapter_encrypt_link(adapter, (bdaddr_t *) &idev->dst,
+ encrypt_completed, req);
+ if (err == 0) {
+ /* Waiting async encryption */
+ return 0;
+ } else if (err != -EALREADY) {
+ error("encrypt_link: %s (%d)", strerror(-err), -err);
+ goto cleanup;
+ }
+ }
+
+ err = ioctl_connadd(req);
+
+cleanup:
+ free(req->rd_data);
+ g_free(req);
+
+ return err;
+}
+
+static int is_connected(struct input_conn *iconn)
+{
+ struct input_device *idev = iconn->idev;
+ struct fake_input *fake = iconn->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;
+}
+
+static int connection_disconnect(struct input_conn *iconn, uint32_t flags)
+{
+ struct input_device *idev = iconn->idev;
+ struct fake_input *fake = iconn->fake;
+ struct hidp_conndel_req req;
+ struct hidp_conninfo ci;
+ int ctl, err;
+
+ /* Fake input disconnect */
+ if (fake) {
+ err = fake->disconnect(iconn);
+ if (err == 0)
+ fake->flags &= ~FI_FLAG_CONNECTED;
+ return err;
+ }
+
+ /* Standard HID disconnect */
+ if (iconn->intr_io)
+ g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+ if (iconn->ctrl_io)
+ g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+ 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 disconnect(struct input_device *idev, uint32_t flags)
+{
+ struct input_conn *iconn = NULL;
+ GSList *l;
+
+ for (l = idev->connections; l; l = l->next) {
+ iconn = l->data;
+
+ if (is_connected(iconn))
+ break;
+ }
+
+ if (!iconn)
+ return -ENOTCONN;
+
+ return connection_disconnect(iconn, flags);
+}
+
+static void disconnect_cb(struct btd_device *device, gboolean removal,
+ void *user_data)
+{
+ struct input_device *idev = user_data;
+ int flags;
+
+ info("Input: disconnect %s", idev->path);
+
+ flags = removal ? (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0;
+
+ disconnect(idev, flags);
+}
+
+static int input_device_connected(struct input_device *idev,
+ struct input_conn *iconn)
+{
+ dbus_bool_t connected;
+ int err;
+
+ if (iconn->intr_io == NULL || iconn->ctrl_io == NULL)
+ return -ENOTCONN;
+
+ err = hidp_add_connection(idev, iconn);
+ if (err < 0)
+ return err;
+
+ iconn->intr_watch = g_io_add_watch(iconn->intr_io,
+ G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ intr_watch_cb, iconn);
+ iconn->ctrl_watch = g_io_add_watch(iconn->ctrl_io,
+ G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ ctrl_watch_cb, iconn);
+
+ connected = TRUE;
+ emit_property_changed(idev->conn, idev->path, INPUT_DEVICE_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+ idev->dc_id = device_add_disconnect_watch(idev->device, disconnect_cb,
+ idev, NULL);
+
+ return 0;
+}
+
+static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err,
+ gpointer user_data)
+{
+ struct input_conn *iconn = user_data;
+ struct input_device *idev = iconn->idev;
+ DBusMessage *reply;
+ int err;
+ const char *err_msg;
+
+ if (conn_err) {
+ err_msg = conn_err->message;
+ goto failed;
+ }
+
+ err = input_device_connected(idev, iconn);
+ if (err < 0) {
+ err_msg = strerror(-err);
+ goto failed;
+ }
+
+ /* Replying to the requestor */
+ g_dbus_send_reply(idev->conn, iconn->pending_connect, DBUS_TYPE_INVALID);
+
+ dbus_message_unref(iconn->pending_connect);
+ iconn->pending_connect = NULL;
+
+ return;
+
+failed:
+ error("%s", err_msg);
+ reply = btd_error_failed(iconn->pending_connect, err_msg);
+ g_dbus_send_message(idev->conn, reply);
+
+ /* So we guarantee the interrupt channel is closed before the
+ * control channel (if we only do unref GLib will close it only
+ * after returning control to the mainloop */
+ if (!conn_err)
+ g_io_channel_shutdown(iconn->intr_io, FALSE, NULL);
+
+ g_io_channel_unref(iconn->intr_io);
+ iconn->intr_io = NULL;
+
+ if (iconn->ctrl_io) {
+ g_io_channel_unref(iconn->ctrl_io);
+ iconn->ctrl_io = NULL;
+ }
+}
+
+static void control_connect_cb(GIOChannel *chan, GError *conn_err,
+ gpointer user_data)
+{
+ struct input_conn *iconn = user_data;
+ struct input_device *idev = iconn->idev;
+ DBusMessage *reply;
+ GIOChannel *io;
+ GError *err = NULL;
+
+ if (conn_err) {
+ error("%s", conn_err->message);
+ reply = btd_error_failed(iconn->pending_connect,
+ conn_err->message);
+ goto failed;
+ }
+
+ /* Connect to the HID interrupt channel */
+ io = bt_io_connect(BT_IO_L2CAP, interrupt_connect_cb, iconn,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+ BT_IO_OPT_DEST_BDADDR, &idev->dst,
+ BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ reply = btd_error_failed(iconn->pending_connect,
+ err->message);
+ g_error_free(err);
+ goto failed;
+ }
+
+ iconn->intr_io = io;
+
+ return;
+
+failed:
+ g_io_channel_unref(iconn->ctrl_io);
+ iconn->ctrl_io = NULL;
+ g_dbus_send_message(idev->conn, reply);
+ dbus_message_unref(iconn->pending_connect);
+ iconn->pending_connect = NULL;
+}
+
+static int fake_disconnect(struct input_conn *iconn)
+{
+ struct fake_input *fake = iconn->fake;
+
+ if (!fake->io)
+ return -ENOTCONN;
+
+ g_io_channel_shutdown(fake->io, TRUE, NULL);
+ 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;
+}
+
+/*
+ * Input Device methods
+ */
+static DBusMessage *input_device_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct input_device *idev = data;
+ struct input_conn *iconn;
+ struct fake_input *fake;
+ DBusMessage *reply;
+ GError *err = NULL;
+
+ iconn = find_connection(idev->connections, "HID");
+ if (!iconn)
+ return btd_error_not_supported(msg);
+
+ if (iconn->pending_connect)
+ return btd_error_in_progress(msg);
+
+ if (is_connected(iconn))
+ return btd_error_already_connected(msg);
+
+ iconn->pending_connect = dbus_message_ref(msg);
+ fake = iconn->fake;
+
+ if (fake) {
+ /* Fake input device */
+ if (fake->connect(iconn, &err))
+ fake->flags |= FI_FLAG_CONNECTED;
+ } else {
+ /* HID devices */
+ GIOChannel *io;
+
+ io = bt_io_connect(BT_IO_L2CAP, control_connect_cb, iconn,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &idev->src,
+ BT_IO_OPT_DEST_BDADDR, &idev->dst,
+ BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ iconn->ctrl_io = io;
+ }
+
+ if (err == NULL)
+ return NULL;
+
+ error("%s", err->message);
+ dbus_message_unref(iconn->pending_connect);
+ iconn->pending_connect = NULL;
+ reply = btd_error_failed(msg, err->message);
+ g_error_free(err);
+ return reply;
+}
+
+static DBusMessage *input_device_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct input_device *idev = data;
+ int err;
+
+ err = disconnect(idev, 0);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void device_unregister(void *data)
+{
+ struct input_device *idev = data;
+
+ DBG("Unregistered interface %s on path %s", INPUT_DEVICE_INTERFACE,
+ idev->path);
+
+ devices = g_slist_remove(devices, idev);
+ input_device_free(idev);
+}
+
+static gint connected_cmp(gpointer a, gpointer b)
+{
+ struct input_conn *iconn = a;
+
+ return !is_connected(iconn);
+}
+
+static DBusMessage *input_device_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct input_device *idev = data;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ dbus_bool_t connected;
+
+ 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_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Connected */
+ connected = !!g_slist_find_custom(idev->connections, NULL,
+ (GCompareFunc) connected_cmp);
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &connected);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static GDBusMethodTable device_methods[] = {
+ { "Connect", "", "", input_device_connect,
+ G_DBUS_METHOD_FLAG_ASYNC },
+ { "Disconnect", "", "", input_device_disconnect },
+ { "GetProperties", "", "a{sv}",input_device_get_properties },
+ { }
+};
+
+static GDBusSignalTable device_signals[] = {
+ { "PropertyChanged", "sv" },
+ { }
+};
+
+static struct input_device *input_device_new(DBusConnection *conn,
+ struct btd_device *device, const char *path,
+ const bdaddr_t *src, const bdaddr_t *dst,
+ const uint32_t handle)
+{
+ struct input_device *idev;
+ char name[249], src_addr[18], dst_addr[18];
+
+ idev = g_new0(struct input_device, 1);
+ bacpy(&idev->src, src);
+ bacpy(&idev->dst, dst);
+ idev->device = btd_device_ref(device);
+ idev->path = g_strdup(path);
+ idev->conn = dbus_connection_ref(conn);
+ idev->handle = handle;
+
+ ba2str(src, src_addr);
+ ba2str(dst, dst_addr);
+ if (read_device_name(src_addr, dst_addr, name) == 0)
+ idev->name = g_strdup(name);
+
+ if (g_dbus_register_interface(conn, idev->path, INPUT_DEVICE_INTERFACE,
+ device_methods, device_signals, NULL,
+ idev, device_unregister) == FALSE) {
+ error("Failed to register interface %s on path %s",
+ INPUT_DEVICE_INTERFACE, path);
+ input_device_free(idev);
+ return NULL;
+ }
+
+ DBG("Registered interface %s on path %s",
+ INPUT_DEVICE_INTERFACE, idev->path);
+
+ return idev;
+}
+
+static struct input_conn *input_conn_new(struct input_device *idev,
+ const char *uuid, const char *alias,
+ int timeout)
+{
+ struct input_conn *iconn;
+
+ iconn = g_new0(struct input_conn, 1);
+ iconn->timeout = timeout;
+ iconn->uuid = g_strdup(uuid);
+ iconn->alias = g_strdup(alias);
+ iconn->idev = idev;
+
+ return iconn;
+}
+
+int input_device_register(DBusConnection *conn, struct btd_device *device,
+ const char *path, const bdaddr_t *src,
+ const bdaddr_t *dst, const char *uuid,
+ uint32_t handle, int timeout)
+{
+ struct input_device *idev;
+ struct input_conn *iconn;
+
+ idev = find_device_by_path(devices, path);
+ if (!idev) {
+ idev = input_device_new(conn, device, path, src, dst, handle);
+ if (!idev)
+ return -EINVAL;
+ devices = g_slist_append(devices, idev);
+ }
+
+ iconn = input_conn_new(idev, uuid, "hid", timeout);
+ if (!iconn)
+ return -EINVAL;
+
+ idev->connections = g_slist_append(idev->connections, iconn);
+
+ return 0;
+}
+
+int fake_input_register(DBusConnection *conn, struct btd_device *device,
+ const char *path, bdaddr_t *src, bdaddr_t *dst,
+ const char *uuid, uint8_t channel)
+{
+ struct input_device *idev;
+ struct input_conn *iconn;
+
+ idev = find_device_by_path(devices, path);
+ if (!idev) {
+ idev = input_device_new(conn, device, path, src, dst, 0);
+ if (!idev)
+ return -EINVAL;
+ devices = g_slist_append(devices, idev);
+ }
+
+ iconn = input_conn_new(idev, uuid, "hsp", 0);
+ if (!iconn)
+ return -EINVAL;
+
+ iconn->fake = g_new0(struct fake_input, 1);
+ iconn->fake->ch = channel;
+ iconn->fake->connect = rfcomm_connect;
+ iconn->fake->disconnect = fake_disconnect;
+
+ idev->connections = g_slist_append(idev->connections, iconn);
+
+ return 0;
+}
+
+static struct input_device *find_device(const bdaddr_t *src,
+ const bdaddr_t *dst)
+{
+ GSList *list;
+
+ for (list = devices; list != NULL; list = list->next) {
+ struct input_device *idev = list->data;
+
+ if (!bacmp(&idev->src, src) && !bacmp(&idev->dst, dst))
+ return idev;
+ }
+
+ return NULL;
+}
+
+int input_device_unregister(const char *path, const char *uuid)
+{
+ struct input_device *idev;
+ struct input_conn *iconn;
+
+ idev = find_device_by_path(devices, path);
+ if (idev == NULL)
+ return -EINVAL;
+
+ iconn = find_connection(idev->connections, uuid);
+ if (iconn == NULL)
+ return -EINVAL;
+
+ if (iconn->pending_connect) {
+ /* Pending connection running */
+ return -EBUSY;
+ }
+
+ idev->connections = g_slist_remove(idev->connections, iconn);
+ input_conn_free(iconn);
+ if (idev->connections)
+ return 0;
+
+ g_dbus_unregister_interface(idev->conn, path, INPUT_DEVICE_INTERFACE);
+
+ return 0;
+}
+
+static int input_device_connadd(struct input_device *idev,
+ struct input_conn *iconn)
+{
+ int err;
+
+ err = input_device_connected(idev, iconn);
+ if (err < 0)
+ goto error;
+
+ return 0;
+
+error:
+ if (iconn->ctrl_io) {
+ g_io_channel_shutdown(iconn->ctrl_io, FALSE, NULL);
+ g_io_channel_unref(iconn->ctrl_io);
+ iconn->ctrl_io = NULL;
+ }
+ if (iconn->intr_io) {
+ g_io_channel_shutdown(iconn->intr_io, FALSE, NULL);
+ g_io_channel_unref(iconn->intr_io);
+ iconn->intr_io = NULL;
+ }
+
+ return err;
+}
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm,
+ GIOChannel *io)
+{
+ struct input_device *idev = find_device(src, dst);
+ struct input_conn *iconn;
+
+ if (!idev)
+ return -ENOENT;
+
+ iconn = find_connection(idev->connections, "hid");
+ if (!iconn)
+ return -ENOENT;
+
+ switch (psm) {
+ case L2CAP_PSM_HIDP_CTRL:
+ if (iconn->ctrl_io)
+ return -EALREADY;
+ iconn->ctrl_io = g_io_channel_ref(io);
+ break;
+ case L2CAP_PSM_HIDP_INTR:
+ if (iconn->intr_io)
+ return -EALREADY;
+ iconn->intr_io = g_io_channel_ref(io);
+ break;
+ }
+
+ if (iconn->intr_io && iconn->ctrl_io)
+ input_device_connadd(idev, iconn);
+
+ return 0;
+}
+
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct input_device *idev = find_device(src, dst);
+ struct input_conn *iconn;
+
+ if (!idev)
+ return -ENOENT;
+
+ iconn = find_connection(idev->connections, "hid");
+ if (!iconn)
+ return -ENOENT;
+
+ if (iconn->intr_io)
+ g_io_channel_shutdown(iconn->intr_io, TRUE, NULL);
+
+ if (iconn->ctrl_io)
+ g_io_channel_shutdown(iconn->ctrl_io, TRUE, NULL);
+
+ return 0;
+}
diff --git a/input/device.h b/input/device.h
new file mode 100644
index 0000000..14c0f97
--- /dev/null
+++ b/input/device.h
@@ -0,0 +1,56 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB"
+#define HID_UUID "00001124-0000-1000-8000-00805f9b34fb"
+
+#define L2CAP_PSM_HIDP_CTRL 0x11
+#define L2CAP_PSM_HIDP_INTR 0x13
+
+struct input_device;
+struct input_conn;
+
+struct fake_input {
+ int flags;
+ GIOChannel *io;
+ int uinput; /* uinput socket */
+ int rfcomm; /* RFCOMM socket */
+ uint8_t ch; /* RFCOMM channel number */
+ gboolean (*connect) (struct input_conn *iconn, GError **err);
+ int (*disconnect) (struct input_conn *iconn);
+ void *priv;
+ const struct input_device *idev;
+};
+
+int fake_input_register(DBusConnection *conn, struct btd_device *device,
+ const char *path, bdaddr_t *src, bdaddr_t *dst,
+ const char *uuid, uint8_t channel);
+int input_device_register(DBusConnection *conn, struct btd_device *device,
+ const char *path, const bdaddr_t *src,
+ const bdaddr_t *dst, const char *uuid,
+ uint32_t handle, int timeout);
+int input_device_unregister(const char *path, const char *uuid);
+
+int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm,
+ GIOChannel *io);
+int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst);
diff --git a/input/fakehid.c b/input/fakehid.c
new file mode 100644
index 0000000..eebca05
--- /dev/null
+++ b/input/fakehid.c
@@ -0,0 +1,411 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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/hidp.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "device.h"
+#include "fakehid.h"
+#include "uinput.h"
+
+#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_1,
+ [0x01] = KEY_2,
+ [0x02] = KEY_3,
+ [0x03] = KEY_4,
+ [0x04] = KEY_5,
+ [0x05] = KEY_6,
+ [0x06] = KEY_7,
+ [0x07] = KEY_8,
+ [0x08] = KEY_9,
+ [0x09] = KEY_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;
+ unsigned int i, mask;
+ int retval;
+ guint8 key;
+
+ 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;
+ ssize_t size;
+ char buff[50];
+ int fd;
+
+ 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;
+ }
+
+ fd = g_io_channel_unix_get_fd(chan);
+
+ memset(buff, 0, sizeof(buff));
+ size = read(fd, buff, sizeof(buff));
+ if (size < 0) {
+ 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, GError **err)
+{
+ 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,
+ .devices = NULL,
+ },
+
+ { },
+};
+
+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;
+}
+
+struct fake_input *fake_hid_connadd(struct fake_input *fake,
+ GIOChannel *intr_io,
+ struct fake_hid *fake_hid)
+{
+ GList *l;
+ struct fake_input *old = NULL;
+
+ /* Look for an already setup device */
+ for (l = fake_hid->devices; l != NULL; l = l->next) {
+ old = l->data;
+ if (old->idev == fake->idev) {
+ g_free(fake);
+ fake = old;
+ fake_hid->connect(fake, NULL);
+ break;
+ }
+ old = NULL;
+ }
+
+ /* New device? Add it to the list of known devices,
+ * and create the uinput necessary */
+ if (old == NULL) {
+ if (fake_hid->setup_uinput(fake, fake_hid)) {
+ error("Error setting up uinput");
+ g_free(fake);
+ return NULL;
+ }
+ fake_hid->devices = g_list_append(fake_hid->devices, fake);
+ }
+
+ fake->io = g_io_channel_ref(intr_io);
+ 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 fake;
+}
diff --git a/input/fakehid.h b/input/fakehid.h
new file mode 100644
index 0000000..3a5d7c4
--- /dev/null
+++ b/input/fakehid.h
@@ -0,0 +1,40 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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, GError **err);
+ 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);
+ GList *devices;
+};
+
+struct fake_hid *get_fake_hid(uint16_t vendor, uint16_t product);
+
+struct fake_input *fake_hid_connadd(struct fake_input *fake, GIOChannel *intr_io,
+ struct fake_hid *fake_hid);
diff --git a/input/input.conf b/input/input.conf
new file mode 100644
index 0000000..abfb64f
--- /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 0000000..e165ab4
--- /dev/null
+++ b/input/main.c
@@ -0,0 +1,86 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 <gdbus.h>
+
+#include "plugin.h"
+#include "log.h"
+#include "manager.h"
+
+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 DBusConnection *connection;
+
+static int input_init(void)
+{
+ GKeyFile *config;
+
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (connection == NULL)
+ return -EIO;
+
+ config = load_config_file(CONFIGDIR "/input.conf");
+
+ if (input_manager_init(connection, config) < 0) {
+ dbus_connection_unref(connection);
+ return -EIO;
+ }
+
+ if (config)
+ g_key_file_free(config);
+
+ return 0;
+}
+
+static void input_exit(void)
+{
+ input_manager_exit();
+
+ dbus_connection_unref(connection);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(input, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, input_init, input_exit)
diff --git a/input/manager.c b/input/manager.c
new file mode 100644
index 0000000..a98a080
--- /dev/null
+++ b/input/manager.c
@@ -0,0 +1,207 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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/hci.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "log.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "server.h"
+#include "manager.h"
+
+static int idle_timeout = 0;
+
+static DBusConnection *connection = NULL;
+static GSList *adapters = NULL;
+
+static void input_remove(struct btd_device *device, const char *uuid)
+{
+ const gchar *path = device_get_path(device);
+
+ DBG("path %s", path);
+
+ input_device_unregister(path, uuid);
+}
+
+static int hid_device_probe(struct btd_device *device, GSList *uuids)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ const gchar *path = device_get_path(device);
+ const sdp_record_t *rec = btd_device_get_record(device, uuids->data);
+ bdaddr_t src, dst;
+
+ DBG("path %s", path);
+
+ if (!rec)
+ return -1;
+
+ adapter_get_address(adapter, &src);
+ device_get_address(device, &dst);
+
+ return input_device_register(connection, device, path, &src, &dst,
+ HID_UUID, rec->handle, idle_timeout * 60);
+}
+
+static void hid_device_remove(struct btd_device *device)
+{
+ input_remove(device, HID_UUID);
+}
+
+static int headset_probe(struct btd_device *device, GSList *uuids)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ const gchar *path = device_get_path(device);
+ const sdp_record_t *record;
+ sdp_list_t *protos;
+ uint8_t ch;
+ bdaddr_t src, dst;
+
+ DBG("path %s", path);
+
+ if (!g_slist_find_custom(uuids, HSP_HS_UUID,
+ (GCompareFunc) strcasecmp))
+ return -EINVAL;
+
+ record = btd_device_get_record(device, uuids->data);
+
+ if (!record || sdp_get_access_protos(record, &protos) < 0) {
+ error("Invalid record");
+ return -EINVAL;
+ }
+
+ 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);
+
+ if (ch <= 0) {
+ error("Invalid RFCOMM channel");
+ return -EINVAL;
+ }
+
+ adapter_get_address(adapter, &src);
+ device_get_address(device, &dst);
+
+ return fake_input_register(connection, device, path, &src, &dst,
+ HSP_HS_UUID, ch);
+}
+
+static void headset_remove(struct btd_device *device)
+{
+ input_remove(device, HSP_HS_UUID);
+}
+
+static int hid_server_probe(struct btd_adapter *adapter)
+{
+ bdaddr_t src;
+ int ret;
+
+ adapter_get_address(adapter, &src);
+
+ ret = server_start(&src);
+ if (ret < 0)
+ return ret;
+
+ adapters = g_slist_append(adapters, btd_adapter_ref(adapter));
+
+ return 0;
+}
+
+static void hid_server_remove(struct btd_adapter *adapter)
+{
+ bdaddr_t src;
+
+ adapter_get_address(adapter, &src);
+
+ server_stop(&src);
+
+ adapters = g_slist_remove(adapters, adapter);
+ btd_adapter_unref(adapter);
+}
+
+static struct btd_device_driver input_hid_driver = {
+ .name = "input-hid",
+ .uuids = BTD_UUIDS(HID_UUID),
+ .probe = hid_device_probe,
+ .remove = hid_device_remove,
+};
+
+static struct btd_device_driver input_headset_driver = {
+ .name = "input-headset",
+ .uuids = BTD_UUIDS(HSP_HS_UUID),
+ .probe = headset_probe,
+ .remove = headset_remove,
+};
+
+static struct btd_adapter_driver input_server_driver = {
+ .name = "input-server",
+ .probe = hid_server_probe,
+ .remove = hid_server_remove,
+};
+
+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) {
+ DBG("input.conf: %s", err->message);
+ g_error_free(err);
+ }
+ }
+
+ connection = dbus_connection_ref(conn);
+
+ btd_register_adapter_driver(&input_server_driver);
+
+ btd_register_device_driver(&input_hid_driver);
+ btd_register_device_driver(&input_headset_driver);
+
+ return 0;
+}
+
+void input_manager_exit(void)
+{
+ btd_unregister_device_driver(&input_hid_driver);
+ btd_unregister_device_driver(&input_headset_driver);
+
+ btd_unregister_adapter_driver(&input_server_driver);
+
+ dbus_connection_unref(connection);
+
+ connection = NULL;
+}
diff --git a/input/manager.h b/input/manager.h
new file mode 100644
index 0000000..7b93c5b
--- /dev/null
+++ b/input/manager.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 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 0000000..d98018b
--- /dev/null
+++ b/input/server.c
@@ -0,0 +1,237 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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 <unistd.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "log.h"
+
+#include "glib-helper.h"
+#include "btio.h"
+#include "adapter.h"
+#include "device.h"
+#include "server.h"
+
+static GSList *servers = NULL;
+struct input_server {
+ bdaddr_t src;
+ GIOChannel *ctrl;
+ GIOChannel *intr;
+ GIOChannel *confirm;
+};
+
+static gint server_cmp(gconstpointer s, gconstpointer user_data)
+{
+ const struct input_server *server = s;
+ const bdaddr_t *src = user_data;
+
+ return bacmp(&server->src, src);
+}
+
+static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+ uint16_t psm;
+ bdaddr_t src, dst;
+ GError *gerr = NULL;
+ int ret;
+
+ if (err) {
+ error("%s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, BT_IO_L2CAP, &gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_PSM, &psm,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ DBG("Incoming connection on PSM %d", psm);
+
+ ret = input_device_set_channel(&src, &dst, psm, chan);
+ if (ret == 0)
+ return;
+
+ /* Send unplug virtual cable to unknown devices */
+ if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) {
+ unsigned char unplug = 0x15;
+ int err, sk = g_io_channel_unix_get_fd(chan);
+ err = write(sk, &unplug, sizeof(unplug));
+ }
+
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void auth_callback(DBusError *derr, void *user_data)
+{
+ struct input_server *server = user_data;
+ bdaddr_t src, dst;
+ GError *err = NULL;
+
+ bt_io_get(server->confirm, BT_IO_L2CAP, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto reject;
+ }
+
+ if (derr) {
+ error("Access denied: %s", derr->message);
+ goto reject;
+ }
+
+ if (!bt_io_accept(server->confirm, connect_event_cb, server,
+ NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ goto reject;
+ }
+
+ g_io_channel_unref(server->confirm);
+ server->confirm = NULL;
+
+ return;
+
+reject:
+ g_io_channel_shutdown(server->confirm, TRUE, NULL);
+ g_io_channel_unref(server->confirm);
+ server->confirm = NULL;
+ input_device_close_channels(&src, &dst);
+}
+
+static void confirm_event_cb(GIOChannel *chan, gpointer user_data)
+{
+ struct input_server *server = user_data;
+ bdaddr_t src, dst;
+ GError *err = NULL;
+ int ret;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ if (server->confirm) {
+ error("Refusing connection: setup in progress");
+ goto drop;
+ }
+
+ server->confirm = g_io_channel_ref(chan);
+
+ ret = btd_request_authorization(&src, &dst, HID_UUID,
+ auth_callback, server);
+ if (ret == 0)
+ return;
+
+ g_io_channel_unref(server->confirm);
+ server->confirm = NULL;
+
+drop:
+ input_device_close_channels(&src, &dst);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+int server_start(const bdaddr_t *src)
+{
+ struct input_server *server;
+ GError *err = NULL;
+
+ server = g_new0(struct input_server, 1);
+ bacpy(&server->src, src);
+
+ server->ctrl = bt_io_listen(BT_IO_L2CAP, connect_event_cb, NULL,
+ server, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, src,
+ BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ if (!server->ctrl) {
+ error("Failed to listen on control channel");
+ g_error_free(err);
+ g_free(server);
+ return -1;
+ }
+
+ server->intr = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event_cb,
+ server, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, src,
+ BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
+ BT_IO_OPT_INVALID);
+ if (!server->intr) {
+ error("Failed to listen on interrupt channel");
+ g_io_channel_unref(server->ctrl);
+ g_error_free(err);
+ g_free(server);
+ return -1;
+ }
+
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+void server_stop(const bdaddr_t *src)
+{
+ struct input_server *server;
+ GSList *l;
+
+ l = g_slist_find_custom(servers, src, server_cmp);
+ if (!l)
+ return;
+
+ server = l->data;
+
+ g_io_channel_shutdown(server->intr, TRUE, NULL);
+ g_io_channel_unref(server->intr);
+
+ g_io_channel_shutdown(server->ctrl, TRUE, NULL);
+ g_io_channel_unref(server->ctrl);
+
+ servers = g_slist_remove(servers, server);
+ g_free(server);
+}
diff --git a/input/server.h b/input/server.h
new file mode 100644
index 0000000..74159bb
--- /dev/null
+++ b/input/server.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2010 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(const bdaddr_t *src);
+void server_stop(const bdaddr_t *src);