This is the plugin to support HID over GATT. It has the uHID module as a
dependency, which is now integrated on the HID maintainer's tree [1]. A merge
of the 'for-next' branch of this tree witht the master branch of bluetooth-next
is necessary to test this plugin, with the uhid module compiled and loaded.
[1] git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid.git
Known limitations:
- No characteristics / descriptors handle storage, we discover everything on
every re-connection;
- Discovery doesn't work when there is a bonded non-connected HoG device. We're
working to improve LE connection and discovery management in general, so this
will be addressed soon.
Claudio Takahasi (7):
hog: Register HID over GATT device driver
hog: Add checking for 'EnableGatt'
hog: Discover descriptors for all characteristics
hog: Use real values for vendor and product IDs
hog: Add read Report Reference descriptor
hog: Add HID Information Characteristic read
hog: Use hardware country code
João Paulo Rechi Vita (12):
hog: Register ATTIO callbacks
hog: Load primary service handle
hog: Discover all characteristics declaration
hog: Discover the "Report Map" characteristic
hog: Enable "Report" characteristic notifications
hog: Add report notification handler
hog: HID I/O driver
hog: Prepend Report ID to the HID report
hog: Add support for uHID events
hog: Handle output reports
hog: Handle output events
hog: Handle feature reports
Makefile.am | 5 +
acinclude.m4 | 8 +
attrib/gatt.h | 1 +
configure.ac | 2 +
input/hog_device.c | 650 ++++++++++++++++++++++++++++++++++++++++++++++++++++
input/hog_device.h | 28 +++
input/main.c | 24 ++
input/manager.c | 38 +++
input/manager.h | 3 +
9 files changed, 759 insertions(+)
create mode 100644 input/hog_device.c
create mode 100644 input/hog_device.h
--
1.7.10.2
From: Jo?o Paulo Rechi Vita <[email protected]>
Date: Fri, 22 Jun 2012 12:08:02 -0300
> This patch adds the GLib GIOChannel watcher to monitor uhid events.
> ---
> input/hog_device.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/input/hog_device.c b/input/hog_device.c
> index f58b16f..cd5d1bf 100644
> --- a/input/hog_device.c
> +++ b/input/hog_device.c
> @@ -53,6 +53,7 @@
>
> #define HOG_REPORT_MAP_UUID 0x2A4B
> #define HOG_REPORT_UUID 0x2A4D
> +
> #define UHID_DEVICE_FILE "/dev/uhid"
>
> #define HOG_REPORT_MAP_MAX_SIZE 512
> @@ -71,6 +72,7 @@ struct hog_device {
> GSList *reports;
> int uhid_fd;
> gboolean prepend_id;
> + guint uhid_watch_id;
> };
>
> struct report {
> @@ -335,6 +337,36 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
> }
> }
>
> +static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
> + gpointer user_data)
> +{
> + struct hog_device *hogdev = user_data;
> + struct uhid_event ev;
> + ssize_t bread;
> + int fd;
> +
> + if (cond & (G_IO_ERR | G_IO_NVAL))
> + goto failed;
> +
> + fd = g_io_channel_unix_get_fd(io);
> + memset(&ev, 0, sizeof(ev));
> +
> + bread = read(fd, &ev, sizeof(ev));
> + if (bread < 0) {
> + int err = errno;
> + DBG("uhid-dev read: %s(%d)", strerror(err), err);
Since this does not seem to be a debug message but instead an error one,
shouldn't you use error() function here ?
Other than that the patch looks good to me.
Paulo
From: Claudio Takahasi <[email protected]>
bCountryCode is a 8-bits integer identifying hardware target country.
The order of the characteristic declarations may be different on each
implementation. Since GATT/ATT requests need to be serialized, HID
information will be returned before report map characteristic value.
---
input/hog_device.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 2472806..dbbea10 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -296,7 +296,7 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
ev.u.create.vendor = vendor;
ev.u.create.product = product;
ev.u.create.version = version;
- ev.u.create.country = 0; /* get this info from the right place */
+ ev.u.create.country = hogdev->bcountrycode;
ev.u.create.bus = BUS_BLUETOOTH;
ev.u.create.rd_data = value;
ev.u.create.rd_size = vlen;
@@ -338,6 +338,7 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
bt_uuid_t report_uuid, report_map_uuid, info_uuid;
struct report *report;
GSList *l;
+ uint16_t map_handle = 0, info_handle = 0;
if (status != 0) {
const char *str = att_ecode2str(status);
@@ -369,12 +370,18 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
report);
discover_descriptor(hogdev->attrib, chr, next, report);
} else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0)
- gatt_read_char(hogdev->attrib, chr->value_handle, 0,
- report_map_read_cb, hogdev);
+ map_handle = chr->value_handle;
else if (bt_uuid_cmp(&uuid, &info_uuid) == 0)
- gatt_read_char(hogdev->attrib, chr->value_handle, 0,
- info_read_cb, hogdev);
+ info_handle = chr->value_handle;
}
+
+ if (info_handle)
+ gatt_read_char(hogdev->attrib, info_handle, 0,
+ info_read_cb, hogdev);
+
+ if (map_handle)
+ gatt_read_char(hogdev->attrib, map_handle, 0,
+ report_map_read_cb, hogdev);
}
static void output_written_cb(guint8 status, const guint8 *pdu,
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
This patch adds the characteristic value read for HID Information
Characteristic. It's information contains HID Device's HID Attributes.
---
input/hog_device.c | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 3876d98..2472806 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -51,6 +51,7 @@
#include "attio.h"
#include "gatt.h"
+#define HOG_INFO_UUID 0x2A4A
#define HOG_REPORT_MAP_UUID 0x2A4B
#define HOG_REPORT_UUID 0x2A4D
@@ -61,6 +62,7 @@
#define UHID_DEVICE_FILE "/dev/uhid"
#define HOG_REPORT_MAP_MAX_SIZE 512
+#define HID_INFO_SIZE 4
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
@@ -77,6 +79,9 @@ struct hog_device {
int uhid_fd;
gboolean prepend_id;
guint uhid_watch_id;
+ uint16_t bcdhid;
+ uint8_t bcountrycode;
+ uint8_t flags;
};
struct report {
@@ -300,10 +305,37 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
error("Failed to create uHID device: %s", strerror(errno));
}
+static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+ uint8_t value[HID_INFO_SIZE];
+ ssize_t vlen;
+
+ if (status != 0) {
+ error("HID Information read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen != 4) {
+ error("ATT protocol error");
+ return;
+ }
+
+ hogdev->bcdhid = att_get_u16(&value[0]);
+ hogdev->bcountrycode = value[2];
+ hogdev->flags = value[3];
+
+ DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X",
+ hogdev->bcdhid, hogdev->bcountrycode, hogdev->flags);
+}
+
static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
{
struct hog_device *hogdev = user_data;
- bt_uuid_t report_uuid, report_map_uuid;
+ bt_uuid_t report_uuid, report_map_uuid, info_uuid;
struct report *report;
GSList *l;
@@ -315,6 +347,7 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
+ bt_uuid16_create(&info_uuid, HOG_INFO_UUID);
for (l = chars; l; l = g_slist_next(l)) {
struct gatt_char *chr, *next;
@@ -338,6 +371,9 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
} else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0)
gatt_read_char(hogdev->attrib, chr->value_handle, 0,
report_map_read_cb, hogdev);
+ else if (bt_uuid_cmp(&uuid, &info_uuid) == 0)
+ gatt_read_char(hogdev->attrib, chr->value_handle, 0,
+ info_read_cb, hogdev);
}
}
--
1.7.10.2
This patch writes the feature reports coming from the HID host on the
device's Feature Report characteristic.
---
input/hog_device.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 374f9fe..3876d98 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -56,6 +56,7 @@
#define HOG_REPORT_TYPE_INPUT 1
#define HOG_REPORT_TYPE_OUTPUT 2
+#define HOG_REPORT_TYPE_FEATURE 3
#define UHID_DEVICE_FILE "/dev/uhid"
@@ -366,10 +367,20 @@ static void forward_report(struct hog_device *hogdev,
int size;
guint type;
- type = HOG_REPORT_TYPE_OUTPUT;
data = ev->u.output.data;
size = ev->u.output.size;
+ switch (ev->type) {
+ case UHID_OUTPUT:
+ type = HOG_REPORT_TYPE_OUTPUT;
+ break;
+ case UHID_FEATURE:
+ type = HOG_REPORT_TYPE_FEATURE;
+ break;
+ default:
+ return;
+ }
+
l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(type),
report_type_cmp);
if (!l)
@@ -413,6 +424,7 @@ static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
switch (ev.type) {
case UHID_OUTPUT:
+ case UHID_FEATURE:
forward_report(hogdev, &ev);
break;
case UHID_OUTPUT_EV:
--
1.7.10.2
This patch prints the output events coming from the HID host for debug
purposes.
---
input/hog_device.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index 50394e7..374f9fe 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -415,6 +415,11 @@ static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
case UHID_OUTPUT:
forward_report(hogdev, &ev);
break;
+ case UHID_OUTPUT_EV:
+ DBG("uHID output event: type %d code %d value %d",
+ ev.u.output_ev.type, ev.u.output_ev.code,
+ ev.u.output_ev.value);
+ break;
default:
warn("unexpected uHID event");
break;
--
1.7.10.2
This patch writes the output reports coming from the HID host on the
device's Output Report characteristic.
---
input/hog_device.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index cd5d1bf..50394e7 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -54,6 +54,9 @@
#define HOG_REPORT_MAP_UUID 0x2A4B
#define HOG_REPORT_UUID 0x2A4D
+#define HOG_REPORT_TYPE_INPUT 1
+#define HOG_REPORT_TYPE_OUTPUT 2
+
#define UHID_DEVICE_FILE "/dev/uhid"
#define HOG_REPORT_MAP_MAX_SIZE 512
@@ -337,6 +340,54 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
}
}
+static void output_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ if (status != 0) {
+ error("Write output report failed: %s", att_ecode2str(status));
+ return;
+ }
+}
+
+static gint report_type_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct report *report = a;
+ uint8_t type = GPOINTER_TO_UINT(b);
+
+ return report->type - type;
+}
+
+static void forward_report(struct hog_device *hogdev,
+ struct uhid_event *ev)
+{
+ struct report *report;
+ GSList *l;
+ void *data;
+ int size;
+ guint type;
+
+ type = HOG_REPORT_TYPE_OUTPUT;
+ data = ev->u.output.data;
+ size = ev->u.output.size;
+
+ l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(type),
+ report_type_cmp);
+ if (!l)
+ return;
+
+ report = l->data;
+
+ DBG("Sending report type %d to device %s handle 0x%X", type,
+ hogdev->path, report->decl->value_handle);
+
+ if (report->decl->properties & ATT_CHAR_PROPER_WRITE)
+ gatt_write_char(hogdev->attrib, report->decl->value_handle,
+ data, size, output_written_cb, hogdev);
+ else if (report->decl->properties & ATT_CHAR_PROPER_WRITE_WITHOUT_RESP)
+ gatt_write_char(hogdev->attrib, report->decl->value_handle,
+ data, size, NULL, NULL);
+}
+
static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
gpointer user_data)
{
@@ -360,6 +411,15 @@ static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
DBG("uHID event type %d received", ev.type);
+ switch (ev.type) {
+ case UHID_OUTPUT:
+ forward_report(hogdev, &ev);
+ break;
+ default:
+ warn("unexpected uHID event");
+ break;
+ }
+
return TRUE;
failed:
--
1.7.10.2
This patch adds the GLib GIOChannel watcher to monitor uhid events.
---
input/hog_device.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index f58b16f..cd5d1bf 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -53,6 +53,7 @@
#define HOG_REPORT_MAP_UUID 0x2A4B
#define HOG_REPORT_UUID 0x2A4D
+
#define UHID_DEVICE_FILE "/dev/uhid"
#define HOG_REPORT_MAP_MAX_SIZE 512
@@ -71,6 +72,7 @@ struct hog_device {
GSList *reports;
int uhid_fd;
gboolean prepend_id;
+ guint uhid_watch_id;
};
struct report {
@@ -335,6 +337,36 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
}
}
+static gboolean uhid_event_cb(GIOChannel *io, GIOCondition cond,
+ gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+ struct uhid_event ev;
+ ssize_t bread;
+ int fd;
+
+ if (cond & (G_IO_ERR | G_IO_NVAL))
+ goto failed;
+
+ fd = g_io_channel_unix_get_fd(io);
+ memset(&ev, 0, sizeof(ev));
+
+ bread = read(fd, &ev, sizeof(ev));
+ if (bread < 0) {
+ int err = errno;
+ DBG("uhid-dev read: %s(%d)", strerror(err), err);
+ goto failed;
+ }
+
+ DBG("uHID event type %d received", ev.type);
+
+ return TRUE;
+
+failed:
+ hogdev->uhid_watch_id = 0;
+ return FALSE;
+}
+
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct hog_device *hogdev = user_data;
@@ -433,6 +465,8 @@ int hog_device_register(struct btd_device *device, const char *path)
{
struct hog_device *hogdev;
struct gatt_primary *prim;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_NVAL;
+ GIOChannel *io;
hogdev = find_device_by_path(devices, path);
if (hogdev)
@@ -453,12 +487,19 @@ int hog_device_register(struct btd_device *device, const char *path)
return -errno;
}
+ io = g_io_channel_unix_new(hogdev->uhid_fd);
+ g_io_channel_set_encoding(io, NULL, NULL);
+ hogdev->uhid_watch_id = g_io_add_watch(io, cond, uhid_event_cb,
+ hogdev);
+ g_io_channel_unref(io);
+
hogdev->hog_primary = g_memdup(prim, sizeof(*prim));
hogdev->attioid = btd_device_add_attio_callback(device,
attio_connected_cb,
attio_disconnected_cb,
hogdev);
+
device_set_auto_connect(device, TRUE);
devices = g_slist_append(devices, hogdev);
@@ -476,6 +517,9 @@ int hog_device_unregister(const char *path)
btd_device_remove_attio_callback(hogdev->device, hogdev->attioid);
+ g_source_remove(hogdev->uhid_watch_id);
+ hogdev->uhid_watch_id = 0;
+
close(hogdev->uhid_fd);
hogdev->uhid_fd = -1;
--
1.7.10.2
If the report descriptor has a Report ID tag it has to be prepended
to the report data to construct the HID report itself.
---
input/hog_device.c | 78 ++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 64 insertions(+), 14 deletions(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 1c12b40..f58b16f 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -61,10 +61,6 @@
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
-struct report {
- struct gatt_char *decl;
-};
-
struct hog_device {
char *path;
struct btd_device *device;
@@ -74,25 +70,64 @@ struct hog_device {
struct gatt_primary *hog_primary;
GSList *reports;
int uhid_fd;
+ gboolean prepend_id;
+};
+
+struct report {
+ uint8_t id;
+ uint8_t type;
+ struct gatt_char *decl;
+ struct hog_device *hogdev;
};
static GSList *devices = NULL;
+static gint report_handle_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct report *report = a;
+ uint16_t handle = GPOINTER_TO_UINT(b);
+
+ return report->decl->value_handle - handle;
+}
+
static void report_value_cb(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
struct hog_device *hogdev = user_data;
struct uhid_event ev;
uint16_t report_size = len - 3;
+ guint handle;
+ GSList *l;
+ struct report *report;
+ uint8_t *buf;
if (len < 3) { /* 1-byte opcode + 2-byte handle */
error("Malformed ATT notification");
return;
}
+ handle = att_get_u16(&pdu[1]);
+
+ l = g_slist_find_custom(hogdev->reports, GUINT_TO_POINTER(handle),
+ report_handle_cmp);
+ if (!l) {
+ error("Invalid report");
+ return;
+ }
+
+ report = l->data;
+
memset(&ev, 0, sizeof(ev));
ev.type = UHID_INPUT;
ev.u.input.size = MIN(report_size, UHID_DATA_MAX);
- memcpy(ev.u.input.data, &pdu[3], MIN(report_size, UHID_DATA_MAX));
+
+ buf = ev.u.input.data;
+ if (hogdev->prepend_id) {
+ *buf = report->id;
+ buf++;
+ ev.u.input.size++;
+ }
+
+ memcpy(buf, &pdu[3], MIN(report_size, UHID_DATA_MAX));
if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
error("uHID write failed: %s", strerror(errno));
@@ -125,6 +160,8 @@ static void write_ccc(uint16_t handle, gpointer user_data)
static void report_reference_cb(guint8 status, const guint8 *pdu,
guint16 plen, gpointer user_data)
{
+ struct report *report = user_data;
+
if (status != 0) {
error("Read Report Reference descriptor failed: %s",
att_ecode2str(status));
@@ -136,13 +173,16 @@ static void report_reference_cb(guint8 status, const guint8 *pdu,
return;
}
+ report->id = pdu[1];
+ report->type = pdu[2];
DBG("Report ID: 0x%02x Report type: 0x%02x", pdu[1], pdu[2]);
}
static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
- struct hog_device *hogdev = user_data;
+ struct report *report = user_data;
+ struct hog_device *hogdev = report->hogdev;
struct att_data_list *list;
uint8_t format;
int i;
@@ -169,10 +209,10 @@ static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
uuid16 = att_get_u16(&value[2]);
if (uuid16 == GATT_CLIENT_CHARAC_CFG_UUID)
- write_ccc(handle, user_data);
+ write_ccc(handle, hogdev);
else if (uuid16 == GATT_REPORT_REFERENCE)
gatt_read_char(hogdev->attrib, handle, 0,
- report_reference_cb, hogdev);
+ report_reference_cb, report);
}
done:
@@ -215,11 +255,20 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
}
DBG("Report MAP:");
- for (i = 0; i < vlen; i += 2) {
- if (i + 1 == vlen)
- DBG("\t %02x", value[i]);
- else
- DBG("\t %02x %02x", value[i], value[i + 1]);
+ for (i = 0; i < vlen; i++) {
+ switch (value[i]) {
+ case 0x85:
+ case 0x86:
+ case 0x87:
+ hogdev->prepend_id = TRUE;
+ }
+
+ if (i % 2 == 0) {
+ if (i + 1 == vlen)
+ DBG("\t %02x", value[i]);
+ else
+ DBG("\t %02x %02x", value[i], value[i + 1]);
+ }
}
vendor_src = btd_device_get_vendor_src(hogdev->device);
@@ -275,10 +324,11 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
if (bt_uuid_cmp(&uuid, &report_uuid) == 0) {
report = g_new0(struct report, 1);
+ report->hogdev = hogdev;
report->decl = g_memdup(chr, sizeof(*chr));
hogdev->reports = g_slist_append(hogdev->reports,
report);
- discover_descriptor(hogdev->attrib, chr, next, hogdev);
+ discover_descriptor(hogdev->attrib, chr, next, report);
} else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0)
gatt_read_char(hogdev->attrib, chr->value_handle, 0,
report_map_read_cb, hogdev);
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
This patch adds the GATT operation to read the value of the Report
Reference descriptor of the Report characteristic.
---
attrib/gatt.h | 1 +
input/hog_device.c | 27 +++++++++++++++++++++++----
2 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/attrib/gatt.h b/attrib/gatt.h
index 9ffe58f..c7e79ab 100644
--- a/attrib/gatt.h
+++ b/attrib/gatt.h
@@ -46,6 +46,7 @@
#define GATT_CHARAC_FMT_UUID 0x2904
#define GATT_CHARAC_AGREG_FMT_UUID 0x2905
#define GATT_CHARAC_VALID_RANGE_UUID 0x2906
+#define GATT_REPORT_REFERENCE 0x2908
/* Client Characteristic Configuration bit field */
#define GATT_CLIENT_CHARAC_CFG_NOTIF_BIT 0x0001
diff --git a/input/hog_device.c b/input/hog_device.c
index 13c0c65..1c12b40 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -122,9 +122,27 @@ static void write_ccc(uint16_t handle, gpointer user_data)
report_ccc_written_cb, hogdev);
}
+static void report_reference_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ if (status != 0) {
+ error("Read Report Reference descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ if (plen != 3) {
+ error("Malformed ATT read response");
+ return;
+ }
+
+ DBG("Report ID: 0x%02x Report type: 0x%02x", pdu[1], pdu[2]);
+}
+
static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
+ struct hog_device *hogdev = user_data;
struct att_data_list *list;
uint8_t format;
int i;
@@ -150,10 +168,11 @@ static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
handle = att_get_u16(value);
uuid16 = att_get_u16(&value[2]);
- if (uuid16 != GATT_CLIENT_CHARAC_CFG_UUID)
- continue;
-
- write_ccc(handle, user_data);
+ if (uuid16 == GATT_CLIENT_CHARAC_CFG_UUID)
+ write_ccc(handle, user_data);
+ else if (uuid16 == GATT_REPORT_REFERENCE)
+ gatt_read_char(hogdev->attrib, handle, 0,
+ report_reference_cb, hogdev);
}
done:
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
This patch replaces the hard-code values for vendor and product IDs
by the values obtained from the device core functions. Vendor and
product IDs are read from the remote's Device Information Service.
---
input/hog_device.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 38c1087..13c0c65 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -180,6 +180,7 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
struct hog_device *hogdev = user_data;
uint8_t value[HOG_REPORT_MAP_MAX_SIZE];
struct uhid_event ev;
+ uint16_t vendor_src, vendor, product, version;
ssize_t vlen;
int i;
@@ -202,15 +203,21 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
DBG("\t %02x %02x", value[i], value[i + 1]);
}
+ vendor_src = btd_device_get_vendor_src(hogdev->device);
+ vendor = btd_device_get_vendor(hogdev->device);
+ product = btd_device_get_product(hogdev->device);
+ version = btd_device_get_version(hogdev->device);
+ DBG("DIS information: vendor_src=0x%X, vendor=0x%X, product=0x%X, "
+ "version=0x%X", vendor_src, vendor, product, version);
+
/* create uHID device */
memset(&ev, 0, sizeof(ev));
ev.type = UHID_CREATE;
- /* TODO: get info from DIS */
strcpy((char *)ev.u.create.name, "bluez-hog-device");
- ev.u.create.vendor = 0xBEBA;
- ev.u.create.product = 0xCAFE;
- ev.u.create.version = 0;
- ev.u.create.country = 0;
+ ev.u.create.vendor = vendor;
+ ev.u.create.product = product;
+ ev.u.create.version = version;
+ ev.u.create.country = 0; /* get this info from the right place */
ev.u.create.bus = BUS_BLUETOOTH;
ev.u.create.rd_data = value;
ev.u.create.rd_size = vlen;
--
1.7.10.2
uHID is HID I/O driver that makes possible to implement HID I/O drivers in
user-space. It works similar to the uinput but it is initialized with a HID
descriptor and deals with raw HID reports.
This commit uses uHID to create a HID device for the remote HoG device and
to tranfers HID reports to HID subsystem.
---
acinclude.m4 | 9 ++++-
configure.ac | 2 ++
input/hog_device.c | 93 +++++++++++++++++++++++++++++++++++++++++-----------
input/main.c | 2 ++
input/manager.c | 2 ++
5 files changed, 88 insertions(+), 20 deletions(-)
diff --git a/acinclude.m4 b/acinclude.m4
index 9b7cc9b..afdbf9f 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -168,6 +168,13 @@ AC_DEFUN([AC_PATH_OUI], [
AC_DEFINE_UNQUOTED(OUIFILE, ["$ac_with_ouifile"], [Define the OUI file path])
])
+AC_DEFUN([AC_PATH_UHID], [
+ AC_CHECK_HEADERS(linux/uhid.h,
+ uhid_found=yes,
+ uhid_found=no
+ )
+])
+
AC_DEFUN([AC_ARG_BLUEZ], [
debug_enable=no
optimization_enable=yes
@@ -398,5 +405,5 @@ AC_DEFUN([AC_ARG_BLUEZ], [
AM_CONDITIONAL(DBUSOOBPLUGIN, test "${dbusoob_enable}" = "yes")
AM_CONDITIONAL(WIIMOTEPLUGIN, test "${wiimote_enable}" = "yes")
AM_CONDITIONAL(GATTMODULES, test "${gatt_enable}" = "yes")
- AM_CONDITIONAL(HOGPLUGIN, test "${gatt_enable}" = "yes" && test "${input_enable}" = "yes")
+ AM_CONDITIONAL(HOGPLUGIN, test "${gatt_enable}" = "yes" && test "${input_enable}" = "yes" && test "${uhid_found}" = "yes")
])
diff --git a/configure.ac b/configure.ac
index 48b181e..e306995 100644
--- a/configure.ac
+++ b/configure.ac
@@ -39,6 +39,7 @@ AC_CHECK_HEADER([sys/inotify.h],
[AC_DEFINE([HAVE_SYS_INOTIFY_H], 1,
[Define to 1 if you have <sys/inotify.h>.])],
[AC_MSG_ERROR(inotify headers are required and missing)])
+
AC_PATH_DBUS
AC_PATH_GLIB
AC_PATH_ALSA
@@ -49,6 +50,7 @@ AC_PATH_SNDFILE
AC_PATH_OUI
AC_PATH_READLINE
AC_PATH_CHECK
+AC_PATH_UHID
AC_ARG_BLUEZ
diff --git a/input/hog_device.c b/input/hog_device.c
index 1dd3e85..38c1087 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -29,6 +29,10 @@
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/uhid.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/uuid.h>
@@ -49,9 +53,14 @@
#define HOG_REPORT_MAP_UUID 0x2A4B
#define HOG_REPORT_UUID 0x2A4D
+#define UHID_DEVICE_FILE "/dev/uhid"
#define HOG_REPORT_MAP_MAX_SIZE 512
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
struct report {
struct gatt_char *decl;
};
@@ -64,21 +73,32 @@ struct hog_device {
guint report_cb_id;
struct gatt_primary *hog_primary;
GSList *reports;
+ int uhid_fd;
};
static GSList *devices = NULL;
static void report_value_cb(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
- uint16_t handle;
+ struct hog_device *hogdev = user_data;
+ struct uhid_event ev;
+ uint16_t report_size = len - 3;
if (len < 3) { /* 1-byte opcode + 2-byte handle */
error("Malformed ATT notification");
return;
}
- handle = att_get_u16(&pdu[1]);
- DBG("Report notification on handle 0x%04x", handle);
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT;
+ ev.u.input.size = MIN(report_size, UHID_DATA_MAX);
+ memcpy(ev.u.input.data, &pdu[3], MIN(report_size, UHID_DATA_MAX));
+
+ if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
+ error("uHID write failed: %s", strerror(errno));
+ else
+ DBG("Report from HoG device %s written to uHID fd %d",
+ hogdev->path, hogdev->uhid_fd);
}
static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
@@ -157,7 +177,9 @@ static void discover_descriptor(GAttrib *attrib, struct gatt_char *chr,
static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
gpointer user_data)
{
+ struct hog_device *hogdev = user_data;
uint8_t value[HOG_REPORT_MAP_MAX_SIZE];
+ struct uhid_event ev;
ssize_t vlen;
int i;
@@ -179,6 +201,22 @@ static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
else
DBG("\t %02x %02x", value[i], value[i + 1]);
}
+
+ /* create uHID device */
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+ /* TODO: get info from DIS */
+ strcpy((char *)ev.u.create.name, "bluez-hog-device");
+ ev.u.create.vendor = 0xBEBA;
+ ev.u.create.product = 0xCAFE;
+ ev.u.create.version = 0;
+ ev.u.create.country = 0;
+ ev.u.create.bus = BUS_BLUETOOTH;
+ ev.u.create.rd_data = value;
+ ev.u.create.rd_size = vlen;
+
+ if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
+ error("Failed to create uHID device: %s", strerror(errno));
}
static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
@@ -239,6 +277,12 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
static void attio_disconnected_cb(gpointer user_data)
{
struct hog_device *hogdev = user_data;
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_DESTROY;
+ if (write(hogdev->uhid_fd, &ev, sizeof(ev)) < 0)
+ error("Failed to destroy uHID device: %s", strerror(errno));
g_attrib_unregister(hogdev->attrib, hogdev->report_cb_id);
hogdev->report_cb_id = 0;
@@ -293,6 +337,22 @@ static struct gatt_primary *load_hog_primary(struct btd_device *device)
return (l ? l->data : NULL);
}
+static void report_free(void *data)
+{
+ struct report *report = data;
+ g_free(report->decl);
+ g_free(report);
+}
+
+static void hog_device_free(struct hog_device *hogdev)
+{
+ btd_device_unref(hogdev->device);
+ g_slist_free_full(hogdev->reports, report_free);
+ g_free(hogdev->path);
+ g_free(hogdev->hog_primary);
+ g_free(hogdev);
+}
+
int hog_device_register(struct btd_device *device, const char *path)
{
struct hog_device *hogdev;
@@ -310,6 +370,13 @@ int hog_device_register(struct btd_device *device, const char *path)
if (!hogdev)
return -ENOMEM;
+ hogdev->uhid_fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC);
+ if (hogdev->uhid_fd < 0) {
+ error("Failed to open uHID device: %s", strerror(errno));
+ hog_device_free(hogdev);
+ return -errno;
+ }
+
hogdev->hog_primary = g_memdup(prim, sizeof(*prim));
hogdev->attioid = btd_device_add_attio_callback(device,
@@ -323,22 +390,6 @@ int hog_device_register(struct btd_device *device, const char *path)
return 0;
}
-static void report_free(void *data)
-{
- struct report *report = data;
- g_free(report->decl);
- g_free(report);
-}
-
-static void hog_device_free(struct hog_device *hogdev)
-{
- btd_device_unref(hogdev->device);
- g_slist_free_full(hogdev->reports, report_free);
- g_free(hogdev->path);
- g_free(hogdev->hog_primary);
- g_free(hogdev);
-}
-
int hog_device_unregister(const char *path)
{
struct hog_device *hogdev;
@@ -348,6 +399,10 @@ int hog_device_unregister(const char *path)
return -EINVAL;
btd_device_remove_attio_callback(hogdev->device, hogdev->attioid);
+
+ close(hogdev->uhid_fd);
+ hogdev->uhid_fd = -1;
+
devices = g_slist_remove(devices, hogdev);
hog_device_free(hogdev);
diff --git a/input/main.c b/input/main.c
index 722bc49..d1623ec 100644
--- a/input/main.c
+++ b/input/main.c
@@ -86,6 +86,7 @@ static void input_exit(void)
BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
input_init, input_exit)
+#ifdef HAVE_LINUX_UHID_H
static int hog_init(void)
{
if (!main_opts.gatt_enabled) {
@@ -106,3 +107,4 @@ static void hog_exit(void)
BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
hog_init, hog_exit)
+#endif
diff --git a/input/manager.c b/input/manager.c
index 01f83ce..28f3f81 100644
--- a/input/manager.c
+++ b/input/manager.c
@@ -197,6 +197,7 @@ void input_manager_exit(void)
connection = NULL;
}
+#ifdef HAVE_LINUX_UHID_H
static int hog_device_probe(struct btd_device *device, GSList *uuids)
{
const char *path = device_get_path(device);
@@ -231,3 +232,4 @@ void hog_manager_exit(void)
{
btd_unregister_device_driver(&hog_driver);
}
+#endif
--
1.7.10.2
---
input/hog_device.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index d1b8bb8..1dd3e85 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -61,12 +61,26 @@ struct hog_device {
struct btd_device *device;
GAttrib *attrib;
guint attioid;
+ guint report_cb_id;
struct gatt_primary *hog_primary;
GSList *reports;
};
static GSList *devices = NULL;
+static void report_value_cb(const uint8_t *pdu, uint16_t len, gpointer user_data)
+{
+ uint16_t handle;
+
+ if (len < 3) { /* 1-byte opcode + 2-byte handle */
+ error("Malformed ATT notification");
+ return;
+ }
+
+ handle = att_get_u16(&pdu[1]);
+ DBG("Report notification on handle 0x%04x", handle);
+}
+
static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
guint16 plen, gpointer user_data)
{
@@ -216,12 +230,19 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
gatt_discover_char(hogdev->attrib, prim->range.start, prim->range.end,
NULL, char_discovered_cb, hogdev);
+
+ hogdev->report_cb_id = g_attrib_register(hogdev->attrib,
+ ATT_OP_HANDLE_NOTIFY, report_value_cb,
+ hogdev, NULL);
}
static void attio_disconnected_cb(gpointer user_data)
{
struct hog_device *hogdev = user_data;
+ g_attrib_unregister(hogdev->attrib, hogdev->report_cb_id);
+ hogdev->report_cb_id = 0;
+
g_attrib_unref(hogdev->attrib);
hogdev->attrib = NULL;
}
--
1.7.10.2
---
input/hog_device.c | 30 +++++++++++++++++++++++++++---
1 file changed, 27 insertions(+), 3 deletions(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 574e6f3..d1b8bb8 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -67,10 +67,30 @@ struct hog_device {
static GSList *devices = NULL;
+static void report_ccc_written_cb(guint8 status, const guint8 *pdu,
+ guint16 plen, gpointer user_data)
+{
+ if (status != 0) {
+ error("Write report characteristic descriptor failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ DBG("Report characteristic descriptor written: notifications enabled");
+}
+
+static void write_ccc(uint16_t handle, gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+ uint8_t value[] = { 0x01, 0x00 };
+
+ gatt_write_char(hogdev->attrib, handle, value, sizeof(value),
+ report_ccc_written_cb, hogdev);
+}
+
static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data)
{
- struct report *report = user_data;
struct att_data_list *list;
uint8_t format;
int i;
@@ -89,13 +109,17 @@ static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
goto done;
for (i = 0; i < list->num; i++) {
- uint16_t uuid16;
+ uint16_t uuid16, handle;
uint8_t *value;
value = list->data[i];
+ handle = att_get_u16(value);
uuid16 = att_get_u16(&value[2]);
- DBG("%s descriptor: 0x%04x", report->decl->uuid, uuid16);
+ if (uuid16 != GATT_CLIENT_CHARAC_CFG_UUID)
+ continue;
+
+ write_ccc(handle, user_data);
}
done:
--
1.7.10.2
This characteristic contains the HID descriptor.
---
input/hog_device.c | 50 +++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 41 insertions(+), 9 deletions(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index cac66e2..574e6f3 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -47,8 +47,11 @@
#include "attio.h"
#include "gatt.h"
+#define HOG_REPORT_MAP_UUID 0x2A4B
#define HOG_REPORT_UUID 0x2A4D
+#define HOG_REPORT_MAP_MAX_SIZE 512
+
struct report {
struct gatt_char *decl;
};
@@ -113,10 +116,37 @@ static void discover_descriptor(GAttrib *attrib, struct gatt_char *chr,
gatt_find_info(attrib, start, end, discover_descriptor_cb, user_data);
}
+static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ uint8_t value[HOG_REPORT_MAP_MAX_SIZE];
+ ssize_t vlen;
+ int i;
+
+ if (status != 0) {
+ error("Report Map read failed: %s", att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ error("ATT protocol error");
+ return;
+ }
+
+ DBG("Report MAP:");
+ for (i = 0; i < vlen; i += 2) {
+ if (i + 1 == vlen)
+ DBG("\t %02x", value[i]);
+ else
+ DBG("\t %02x %02x", value[i], value[i + 1]);
+ }
+}
+
static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
{
struct hog_device *hogdev = user_data;
- bt_uuid_t report_uuid;
+ bt_uuid_t report_uuid, report_map_uuid;
struct report *report;
GSList *l;
@@ -127,6 +157,7 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
}
bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
+ bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID);
for (l = chars; l; l = g_slist_next(l)) {
struct gatt_char *chr, *next;
@@ -140,14 +171,15 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
bt_string_to_uuid(&uuid, chr->uuid);
- if (bt_uuid_cmp(&uuid, &report_uuid) != 0)
- continue;
-
- report = g_new0(struct report, 1);
- report->decl = g_memdup(chr, sizeof(*chr));
- hogdev->reports = g_slist_append(hogdev->reports, report);
-
- discover_descriptor(hogdev->attrib, chr, next, hogdev);
+ if (bt_uuid_cmp(&uuid, &report_uuid) == 0) {
+ report = g_new0(struct report, 1);
+ report->decl = g_memdup(chr, sizeof(*chr));
+ hogdev->reports = g_slist_append(hogdev->reports,
+ report);
+ discover_descriptor(hogdev->attrib, chr, next, hogdev);
+ } else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0)
+ gatt_read_char(hogdev->attrib, chr->value_handle, 0,
+ report_map_read_cb, hogdev);
}
}
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
"Report" characteristic has "Report Reference Characteristic" descriptor and
"Client Characteristic Configuration" descriptor.
---
input/hog_device.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/input/hog_device.c b/input/hog_device.c
index 791550a..cac66e2 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -64,6 +64,55 @@ struct hog_device {
static GSList *devices = NULL;
+static void discover_descriptor_cb(guint8 status, const guint8 *pdu,
+ guint16 len, gpointer user_data)
+{
+ struct report *report = user_data;
+ struct att_data_list *list;
+ uint8_t format;
+ int i;
+
+ if (status != 0) {
+ error("Discover all characteristic descriptors failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ list = dec_find_info_resp(pdu, len, &format);
+ if (list == NULL)
+ return;
+
+ if (format != 0x01)
+ goto done;
+
+ for (i = 0; i < list->num; i++) {
+ uint16_t uuid16;
+ uint8_t *value;
+
+ value = list->data[i];
+ uuid16 = att_get_u16(&value[2]);
+
+ DBG("%s descriptor: 0x%04x", report->decl->uuid, uuid16);
+ }
+
+done:
+ att_data_list_free(list);
+}
+
+static void discover_descriptor(GAttrib *attrib, struct gatt_char *chr,
+ struct gatt_char *next, gpointer user_data)
+{
+ uint16_t start, end;
+
+ start = chr->value_handle + 1;
+ end = (next ? next->handle - 1 : 0xffff);
+
+ if (start > end)
+ return;
+
+ gatt_find_info(attrib, start, end, discover_descriptor_cb, user_data);
+}
+
static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
{
struct hog_device *hogdev = user_data;
@@ -80,9 +129,12 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
for (l = chars; l; l = g_slist_next(l)) {
- struct gatt_char *chr = l->data;
+ struct gatt_char *chr, *next;
bt_uuid_t uuid;
+ chr = l->data;
+ next = l->next ? l->next->data : NULL;
+
DBG("0x%04x UUID: %s properties: %02x",
chr->handle, chr->uuid, chr->properties);
@@ -94,6 +146,8 @@ static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
report = g_new0(struct report, 1);
report->decl = g_memdup(chr, sizeof(*chr));
hogdev->reports = g_slist_append(hogdev->reports, report);
+
+ discover_descriptor(hogdev->attrib, chr, next, hogdev);
}
}
--
1.7.10.2
HID service supports multiple report characteristic. Each report
characteristic has a reference descriptor containing ID and type.
---
input/hog_device.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index 6ce05a9..791550a 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -47,21 +47,65 @@
#include "attio.h"
#include "gatt.h"
+#define HOG_REPORT_UUID 0x2A4D
+
+struct report {
+ struct gatt_char *decl;
+};
+
struct hog_device {
char *path;
struct btd_device *device;
GAttrib *attrib;
guint attioid;
struct gatt_primary *hog_primary;
+ GSList *reports;
};
static GSList *devices = NULL;
+static void char_discovered_cb(GSList *chars, guint8 status, gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+ bt_uuid_t report_uuid;
+ struct report *report;
+ GSList *l;
+
+ if (status != 0) {
+ const char *str = att_ecode2str(status);
+ DBG("Discover all characteristics failed: %s", str);
+ return;
+ }
+
+ bt_uuid16_create(&report_uuid, HOG_REPORT_UUID);
+
+ for (l = chars; l; l = g_slist_next(l)) {
+ struct gatt_char *chr = l->data;
+ bt_uuid_t uuid;
+
+ DBG("0x%04x UUID: %s properties: %02x",
+ chr->handle, chr->uuid, chr->properties);
+
+ bt_string_to_uuid(&uuid, chr->uuid);
+
+ if (bt_uuid_cmp(&uuid, &report_uuid) != 0)
+ continue;
+
+ report = g_new0(struct report, 1);
+ report->decl = g_memdup(chr, sizeof(*chr));
+ hogdev->reports = g_slist_append(hogdev->reports, report);
+ }
+}
+
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct hog_device *hogdev = user_data;
+ struct gatt_primary *prim = hogdev->hog_primary;
hogdev->attrib = g_attrib_ref(attrib);
+
+ gatt_discover_char(hogdev->attrib, prim->range.start, prim->range.end,
+ NULL, char_discovered_cb, hogdev);
}
static void attio_disconnected_cb(gpointer user_data)
@@ -148,9 +192,17 @@ int hog_device_register(struct btd_device *device, const char *path)
return 0;
}
+static void report_free(void *data)
+{
+ struct report *report = data;
+ g_free(report->decl);
+ g_free(report);
+}
+
static void hog_device_free(struct hog_device *hogdev)
{
btd_device_unref(hogdev->device);
+ g_slist_free_full(hogdev->reports, report_free);
g_free(hogdev->path);
g_free(hogdev->hog_primary);
g_free(hogdev);
--
1.7.10.2
---
input/hog_device.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/input/hog_device.c b/input/hog_device.c
index 687dc95..6ce05a9 100644
--- a/input/hog_device.c
+++ b/input/hog_device.c
@@ -31,6 +31,7 @@
#include <unistd.h>
#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
#include <glib.h>
@@ -41,14 +42,17 @@
#include "hog_device.h"
+#include "att.h"
#include "gattrib.h"
#include "attio.h"
+#include "gatt.h"
struct hog_device {
char *path;
struct btd_device *device;
GAttrib *attrib;
guint attioid;
+ struct gatt_primary *hog_primary;
};
static GSList *devices = NULL;
@@ -95,18 +99,44 @@ static struct hog_device *hog_device_new(struct btd_device *device,
return hogdev;
}
+static gint primary_uuid_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct gatt_primary *prim = a;
+ const char *uuid = b;
+
+ return g_strcmp0(prim->uuid, uuid);
+}
+
+static struct gatt_primary *load_hog_primary(struct btd_device *device)
+{
+ GSList *primaries, *l;
+
+ primaries = btd_device_get_primaries(device);
+
+ l = g_slist_find_custom(primaries, HOG_UUID, primary_uuid_cmp);
+
+ return (l ? l->data : NULL);
+}
+
int hog_device_register(struct btd_device *device, const char *path)
{
struct hog_device *hogdev;
+ struct gatt_primary *prim;
hogdev = find_device_by_path(devices, path);
if (hogdev)
return -EALREADY;
+ prim = load_hog_primary(device);
+ if (!prim)
+ return -EINVAL;
+
hogdev = hog_device_new(device, path);
if (!hogdev)
return -ENOMEM;
+ hogdev->hog_primary = g_memdup(prim, sizeof(*prim));
+
hogdev->attioid = btd_device_add_attio_callback(device,
attio_connected_cb,
attio_disconnected_cb,
@@ -122,6 +152,7 @@ static void hog_device_free(struct hog_device *hogdev)
{
btd_device_unref(hogdev->device);
g_free(hogdev->path);
+ g_free(hogdev->hog_primary);
g_free(hogdev);
}
--
1.7.10.2
This way the LE connection is kept up. Also set device to autoconnect.
---
Makefile.am | 2 +-
input/hog_device.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++
input/hog_device.h | 3 ++
input/manager.c | 4 +-
4 files changed, 148 insertions(+), 2 deletions(-)
create mode 100644 input/hog_device.c
diff --git a/Makefile.am b/Makefile.am
index 9a5d81d..2fb5a2a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -190,7 +190,7 @@ endif
if HOGPLUGIN
builtin_modules += hog
-builtin_sources += input/hog_device.h
+builtin_sources += input/hog_device.h input/hog_device.c
endif
if SERIALPLUGIN
diff --git a/input/hog_device.c b/input/hog_device.c
new file mode 100644
index 0000000..687dc95
--- /dev/null
+++ b/input/hog_device.c
@@ -0,0 +1,141 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Marcel Holtmann <[email protected]>
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ * 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 <unistd.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include "log.h"
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "hog_device.h"
+
+#include "gattrib.h"
+#include "attio.h"
+
+struct hog_device {
+ char *path;
+ struct btd_device *device;
+ GAttrib *attrib;
+ guint attioid;
+};
+
+static GSList *devices = NULL;
+
+static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+
+ hogdev->attrib = g_attrib_ref(attrib);
+}
+
+static void attio_disconnected_cb(gpointer user_data)
+{
+ struct hog_device *hogdev = user_data;
+
+ g_attrib_unref(hogdev->attrib);
+ hogdev->attrib = NULL;
+}
+
+static struct hog_device *find_device_by_path(GSList *list, const char *path)
+{
+ for (; list; list = list->next) {
+ struct hog_device *hogdev = list->data;
+
+ if (!strcmp(hogdev->path, path))
+ return hogdev;
+ }
+
+ return NULL;
+}
+
+static struct hog_device *hog_device_new(struct btd_device *device,
+ const char *path)
+{
+ struct hog_device *hogdev;
+
+ hogdev = g_new0(struct hog_device, 1);
+ if (!hogdev)
+ return NULL;
+
+ hogdev->path = g_strdup(path);
+ hogdev->device = btd_device_ref(device);
+
+ return hogdev;
+}
+
+int hog_device_register(struct btd_device *device, const char *path)
+{
+ struct hog_device *hogdev;
+
+ hogdev = find_device_by_path(devices, path);
+ if (hogdev)
+ return -EALREADY;
+
+ hogdev = hog_device_new(device, path);
+ if (!hogdev)
+ return -ENOMEM;
+
+ hogdev->attioid = btd_device_add_attio_callback(device,
+ attio_connected_cb,
+ attio_disconnected_cb,
+ hogdev);
+ device_set_auto_connect(device, TRUE);
+
+ devices = g_slist_append(devices, hogdev);
+
+ return 0;
+}
+
+static void hog_device_free(struct hog_device *hogdev)
+{
+ btd_device_unref(hogdev->device);
+ g_free(hogdev->path);
+ g_free(hogdev);
+}
+
+int hog_device_unregister(const char *path)
+{
+ struct hog_device *hogdev;
+
+ hogdev = find_device_by_path(devices, path);
+ if (hogdev == NULL)
+ return -EINVAL;
+
+ btd_device_remove_attio_callback(hogdev->device, hogdev->attioid);
+ devices = g_slist_remove(devices, hogdev);
+ hog_device_free(hogdev);
+
+ return 0;
+}
diff --git a/input/hog_device.h b/input/hog_device.h
index a0158ea..ce6a79e 100644
--- a/input/hog_device.h
+++ b/input/hog_device.h
@@ -23,3 +23,6 @@
*/
#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb"
+
+int hog_device_register(struct btd_device *device, const char *path);
+int hog_device_unregister(const char *path);
diff --git a/input/manager.c b/input/manager.c
index 8da9f64..01f83ce 100644
--- a/input/manager.c
+++ b/input/manager.c
@@ -203,7 +203,7 @@ static int hog_device_probe(struct btd_device *device, GSList *uuids)
DBG("path %s", path);
- return 0;
+ return hog_device_register(device, path);
}
static void hog_device_remove(struct btd_device *device)
@@ -211,6 +211,8 @@ static void hog_device_remove(struct btd_device *device)
const gchar *path = device_get_path(device);
DBG("path %s", path);
+
+ hog_device_unregister(path);
}
static struct btd_device_driver hog_driver = {
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
---
Makefile.am | 5 +++++
acinclude.m4 | 1 +
input/hog_device.h | 25 +++++++++++++++++++++++++
input/main.c | 13 +++++++++++++
input/manager.c | 34 ++++++++++++++++++++++++++++++++++
input/manager.h | 3 +++
6 files changed, 81 insertions(+)
create mode 100644 input/hog_device.h
diff --git a/Makefile.am b/Makefile.am
index 1c214c6..9a5d81d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -188,6 +188,11 @@ builtin_sources += input/main.c \
input/fakehid.c input/fakehid.h
endif
+if HOGPLUGIN
+builtin_modules += hog
+builtin_sources += input/hog_device.h
+endif
+
if SERIALPLUGIN
builtin_modules += serial
builtin_sources += serial/main.c \
diff --git a/acinclude.m4 b/acinclude.m4
index 6505ad3..9b7cc9b 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -398,4 +398,5 @@ AC_DEFUN([AC_ARG_BLUEZ], [
AM_CONDITIONAL(DBUSOOBPLUGIN, test "${dbusoob_enable}" = "yes")
AM_CONDITIONAL(WIIMOTEPLUGIN, test "${wiimote_enable}" = "yes")
AM_CONDITIONAL(GATTMODULES, test "${gatt_enable}" = "yes")
+ AM_CONDITIONAL(HOGPLUGIN, test "${gatt_enable}" = "yes" && test "${input_enable}" = "yes")
])
diff --git a/input/hog_device.h b/input/hog_device.h
new file mode 100644
index 0000000..a0158ea
--- /dev/null
+++ b/input/hog_device.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Marcel Holtmann <[email protected]>
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ *
+ * 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 HOG_UUID "00001812-0000-1000-8000-00805f9b34fb"
diff --git a/input/main.c b/input/main.c
index da09b86..2aac3db 100644
--- a/input/main.c
+++ b/input/main.c
@@ -84,3 +84,16 @@ static void input_exit(void)
BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
input_init, input_exit)
+
+static int hog_init(void)
+{
+ return hog_manager_init();
+}
+
+static void hog_exit(void)
+{
+ hog_manager_exit();
+}
+
+BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ hog_init, hog_exit)
diff --git a/input/manager.c b/input/manager.c
index 5cc552b..8da9f64 100644
--- a/input/manager.c
+++ b/input/manager.c
@@ -40,6 +40,7 @@
#include "../src/device.h"
#include "device.h"
+#include "hog_device.h"
#include "server.h"
#include "manager.h"
@@ -195,3 +196,36 @@ void input_manager_exit(void)
connection = NULL;
}
+
+static int hog_device_probe(struct btd_device *device, GSList *uuids)
+{
+ const char *path = device_get_path(device);
+
+ DBG("path %s", path);
+
+ return 0;
+}
+
+static void hog_device_remove(struct btd_device *device)
+{
+ const gchar *path = device_get_path(device);
+
+ DBG("path %s", path);
+}
+
+static struct btd_device_driver hog_driver = {
+ .name = "input-hog",
+ .uuids = BTD_UUIDS(HOG_UUID),
+ .probe = hog_device_probe,
+ .remove = hog_device_remove,
+};
+
+int hog_manager_init(void)
+{
+ return btd_register_device_driver(&hog_driver);
+}
+
+void hog_manager_exit(void)
+{
+ btd_unregister_device_driver(&hog_driver);
+}
diff --git a/input/manager.h b/input/manager.h
index 7b93c5b..468de64 100644
--- a/input/manager.h
+++ b/input/manager.h
@@ -23,3 +23,6 @@
int input_manager_init(DBusConnection *conn, GKeyFile *config);
void input_manager_exit(void);
+
+int hog_manager_init(void);
+void hog_manager_exit(void);
--
1.7.10.2
From: Claudio Takahasi <[email protected]>
This patch adds the verification for 'EnableGatt' config option on HoG
plugin. HoG should not be enabled if EnableGatt is disabled.
---
input/main.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/input/main.c b/input/main.c
index 2aac3db..722bc49 100644
--- a/input/main.c
+++ b/input/main.c
@@ -32,6 +32,7 @@
#include <gdbus.h>
#include "plugin.h"
+#include "hcid.h"
#include "log.h"
#include "manager.h"
@@ -87,11 +88,19 @@ BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
static int hog_init(void)
{
+ if (!main_opts.gatt_enabled) {
+ DBG("GATT is disabled");
+ return -ENOTSUP;
+ }
+
return hog_manager_init();
}
static void hog_exit(void)
{
+ if (!main_opts.gatt_enabled)
+ return;
+
hog_manager_exit();
}
--
1.7.10.2