Skip to content

Commit 8a4de63

Browse files
authored
hidraw: report only Top-Level Usage_page/Usage pairs as 'unique' devices (#601)
- unique Top-Level Usage_page/Usage: to be consistent with Windows/macOS (OS-level implementations). - support for single Usage Page item for several UsageIDs; - support for 32bit Usage items; Fixes: #298 Fixes: #454
1 parent 09ab35f commit 8a4de63

2 files changed

Lines changed: 101 additions & 36 deletions

File tree

libusb/hid.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,16 @@ static int get_usage(uint8_t *report_descriptor, size_t size,
250250
//printf("Usage Page: %x\n", (uint32_t)*usage_page);
251251
}
252252
if (key_cmd == 0x8) {
253-
*usage = get_bytes(report_descriptor, size, data_len, i);
254-
usage_found = 1;
253+
if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */
254+
*usage_page = get_bytes(report_descriptor, size, 2, i + 2);
255+
usage_page_found = 1;
256+
*usage = get_bytes(report_descriptor, size, 2, i);
257+
usage_found = 1;
258+
}
259+
else {
260+
*usage = get_bytes(report_descriptor, size, data_len, i);
261+
usage_found = 1;
262+
}
255263
//printf("Usage: %x\n", (uint32_t)*usage);
256264
}
257265

linux/hid.c

Lines changed: 91 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
192192
* Returns 1 if successful, 0 if an invalid key
193193
* Sets data_len and key_size when successful
194194
*/
195-
static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size)
195+
static int get_hid_item_size(const __u8 *report_descriptor, __u32 size, unsigned int pos, int *data_len, int *key_size)
196196
{
197197
int key = report_descriptor[pos];
198198
int size_code;
@@ -248,7 +248,7 @@ static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 si
248248
* Get bytes from a HID Report Descriptor.
249249
* Only call with a num_bytes of 0, 1, 2, or 4.
250250
*/
251-
static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur)
251+
static __u32 get_hid_report_bytes(const __u8 *rpt, size_t len, size_t num_bytes, size_t cur)
252252
{
253253
/* Return if there aren't enough bytes. */
254254
if (cur + num_bytes >= len)
@@ -271,6 +271,60 @@ static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_
271271
return 0;
272272
}
273273

274+
/*
275+
* Iterates until the end of a Collection.
276+
* Assumes that *pos is exactly at the beginning of a Collection.
277+
* Skips all nested Collection, i.e. iterates until the end of current level Collection.
278+
*
279+
* The return value is non-0 when an end of current Collection is found,
280+
* 0 when error is occured (broken Descriptor, end of a Collection is found before its begin,
281+
* or no Collection is found at all).
282+
*/
283+
static int hid_iterate_over_collection(const __u8 *report_descriptor, __u32 size, unsigned int *pos, int *data_len, int *key_size)
284+
{
285+
int collection_level = 0;
286+
287+
while (*pos < size) {
288+
int key = report_descriptor[*pos];
289+
int key_cmd = key & 0xfc;
290+
291+
/* Determine data_len and key_size */
292+
if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size))
293+
return 0; /* malformed report */
294+
295+
switch (key_cmd) {
296+
case 0xa0: /* Collection 6.2.2.4 (Main) */
297+
collection_level++;
298+
break;
299+
case 0xc0: /* End Collection 6.2.2.4 (Main) */
300+
collection_level--;
301+
break;
302+
}
303+
304+
if (collection_level < 0) {
305+
/* Broken descriptor or someone is using this function wrong,
306+
* i.e. should be called exactly at the collection start */
307+
return 0;
308+
}
309+
310+
if (collection_level == 0) {
311+
/* Found it!
312+
* Also possible when called not at the collection start, but should not happen if used correctly */
313+
return 1;
314+
}
315+
316+
*pos += *data_len + *key_size;
317+
}
318+
319+
return 0; /* Did not find the end of a Collection */
320+
}
321+
322+
struct hid_usage_iterator {
323+
unsigned int pos;
324+
int usage_page_found;
325+
unsigned short usage_page;
326+
};
327+
274328
/*
275329
* Retrieves the device's Usage Page and Usage from the report descriptor.
276330
* The algorithm returns the current Usage Page/Usage pair whenever a new
@@ -288,63 +342,64 @@ static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_
288342
* 1 when finished processing descriptor.
289343
* -1 on a malformed report.
290344
*/
291-
static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage)
345+
static int get_next_hid_usage(const __u8 *report_descriptor, __u32 size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage)
292346
{
293347
int data_len, key_size;
294-
int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */
295-
int usage_pair_ready = 0;
348+
int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */
296349

297-
/* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */
298350
int usage_found = 0;
299351

300-
while (*pos < size) {
301-
int key = report_descriptor[*pos];
352+
while (ctx->pos < size) {
353+
int key = report_descriptor[ctx->pos];
302354
int key_cmd = key & 0xfc;
303355

304356
/* Determine data_len and key_size */
305-
if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size))
357+
if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size))
306358
return -1; /* malformed report */
307359

308360
switch (key_cmd) {
309361
case 0x4: /* Usage Page 6.2.2.7 (Global) */
310-
*usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
362+
ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos);
363+
ctx->usage_page_found = 1;
311364
break;
312365

313366
case 0x8: /* Usage 6.2.2.8 (Local) */
314-
*usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
315-
usage_found = 1;
367+
if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */
368+
ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2);
369+
ctx->usage_page_found = 1;
370+
*usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos);
371+
usage_found = 1;
372+
}
373+
else {
374+
*usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos);
375+
usage_found = 1;
376+
}
316377
break;
317378

318379
case 0xa0: /* Collection 6.2.2.4 (Main) */
319-
/* A Usage Item (Local) must be found for the pair to be valid */
320-
if (usage_found)
321-
usage_pair_ready = 1;
380+
if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) {
381+
return -1;
382+
}
322383

323-
/* Usage is a Local Item, unset it */
324-
usage_found = 0;
325-
break;
384+
/* A pair is valid - to be reported when Collection is found */
385+
if (usage_found && ctx->usage_page_found) {
386+
*usage_page = ctx->usage_page;
387+
return 0;
388+
}
326389

327-
case 0x80: /* Input 6.2.2.4 (Main) */
328-
case 0x90: /* Output 6.2.2.4 (Main) */
329-
case 0xb0: /* Feature 6.2.2.4 (Main) */
330-
case 0xc0: /* End Collection 6.2.2.4 (Main) */
331-
/* Usage is a Local Item, unset it */
332-
usage_found = 0;
333390
break;
334391
}
335392

336393
/* Skip over this key and its associated data */
337-
*pos += data_len + key_size;
338-
339-
/* Return usage pair */
340-
if (usage_pair_ready)
341-
return 0;
394+
ctx->pos += data_len + key_size;
342395
}
343396

344397
/* If no top-level application collection is found and usage page/usage pair is found, pair is valid
345398
https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */
346-
if (initial && usage_found)
347-
return 0; /* success */
399+
if (initial && usage_found && ctx->usage_page_found) {
400+
*usage_page = ctx->usage_page;
401+
return 0; /* success */
402+
}
348403

349404
return 1; /* finished processing */
350405
}
@@ -727,12 +782,14 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device
727782
result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc);
728783
if (result >= 0) {
729784
unsigned short page = 0, usage = 0;
730-
unsigned int pos = 0;
785+
struct hid_usage_iterator usage_iterator;
786+
memset(&usage_iterator, 0, sizeof(usage_iterator));
787+
731788
/*
732789
* Parse the first usage and usage page
733790
* out of the report descriptor.
734791
*/
735-
if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
792+
if (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) {
736793
cur_dev->usage_page = page;
737794
cur_dev->usage = usage;
738795
}
@@ -741,7 +798,7 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device
741798
* Parse any additional usage and usage pages
742799
* out of the report descriptor.
743800
*/
744-
while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
801+
while (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) {
745802
/* Create new record for additional usage pairs */
746803
struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
747804
struct hid_device_info *prev_dev = cur_dev;

0 commit comments

Comments
 (0)