Skip to content

Commit 48cacfa

Browse files
authored
Usage Page and Usage on Linux with hidraw (#139)
NOTE: This commit does not handle composite HID descriptors I am interested in adding support for composite descriptors though I still need to find a device with a composite descriptor to test it correctly. The implementation idea is similar, as in #125 for macOS.
1 parent 8ed3685 commit 48cacfa

File tree

2 files changed

+267
-40
lines changed

2 files changed

+267
-40
lines changed

hidapi/hidapi.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ extern "C" {
9696
/** Product string */
9797
wchar_t *product_string;
9898
/** Usage Page for this Device/Interface
99-
(Windows/Mac only). */
99+
(Windows/Mac/hidraw only) */
100100
unsigned short usage_page;
101101
/** Usage for this Device/Interface
102-
(Windows/Mac only).*/
102+
(Windows/Mac/hidraw only) */
103103
unsigned short usage;
104104
/** The USB interface which this logical device
105105
represents.
@@ -124,7 +124,7 @@ extern "C" {
124124
needed. This function should be called at the beginning of
125125
execution however, if there is a chance of HIDAPI handles
126126
being opened by different threads simultaneously.
127-
127+
128128
@ingroup API
129129
130130
@returns

linux/hid.c

Lines changed: 264 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ static void register_global_error(const char *msg)
123123
last_global_error_str = utf8_to_wchar_t(msg);
124124
}
125125

126+
/* See register_global_error, but you can pass a format string into this function. */
127+
static void register_global_error_format(const char *format, ...)
128+
{
129+
va_list args;
130+
va_start(args, format);
131+
132+
char msg[100];
133+
vsnprintf(msg, sizeof(msg), format, args);
134+
135+
va_end(args);
136+
137+
register_global_error(msg);
138+
}
126139

127140
/* Set the last error for a device to be reported by hid_error(device).
128141
* The given error message will be copied (and decoded according to the
@@ -158,11 +171,67 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
158171
return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name));
159172
}
160173

174+
/*
175+
* Gets the size of the HID item at the given position
176+
* Returns 1 if successful, 0 if an invalid key
177+
* Sets data_len and key_size when successful
178+
*/
179+
static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size)
180+
{
181+
int key = report_descriptor[pos];
182+
int size_code;
183+
184+
/*
185+
* This is a Long Item. The next byte contains the
186+
* length of the data section (value) for this key.
187+
* See the HID specification, version 1.11, section
188+
* 6.2.2.3, titled "Long Items."
189+
*/
190+
if ((key & 0xf0) == 0xf0) {
191+
if (pos + 1 < size)
192+
{
193+
*data_len = report_descriptor[pos + 1];
194+
*key_size = 3;
195+
return 1;
196+
}
197+
*data_len = 0; /* malformed report */
198+
*key_size = 0;
199+
}
200+
201+
/*
202+
* This is a Short Item. The bottom two bits of the
203+
* key contain the size code for the data section
204+
* (value) for this key. Refer to the HID
205+
* specification, version 1.11, section 6.2.2.2,
206+
* titled "Short Items."
207+
*/
208+
size_code = key & 0x3;
209+
switch (size_code) {
210+
case 0:
211+
case 1:
212+
case 2:
213+
*data_len = size_code;
214+
*key_size = 1;
215+
return 1;
216+
case 3:
217+
*data_len = 4;
218+
*key_size = 1;
219+
return 1;
220+
default:
221+
/* Can't ever happen since size_code is & 0x3 */
222+
*data_len = 0;
223+
*key_size = 0;
224+
break;
225+
};
226+
227+
/* malformed report */
228+
return 0;
229+
}
230+
161231
/* uses_numbered_reports() returns 1 if report_descriptor describes a device
162232
which contains numbered reports. */
163233
static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
164234
unsigned int i = 0;
165-
int size_code;
166235
int data_len, key_size;
167236

168237
while (i < size) {
@@ -175,42 +244,9 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
175244
return 1;
176245
}
177246

178-
//printf("key: %02hhx\n", key);
179-
180-
if ((key & 0xf0) == 0xf0) {
181-
/* This is a Long Item. The next byte contains the
182-
length of the data section (value) for this key.
183-
See the HID specification, version 1.11, section
184-
6.2.2.3, titled "Long Items." */
185-
if (i+1 < size)
186-
data_len = report_descriptor[i+1];
187-
else
188-
data_len = 0; /* malformed report */
189-
key_size = 3;
190-
}
191-
else {
192-
/* This is a Short Item. The bottom two bits of the
193-
key contain the size code for the data section
194-
(value) for this key. Refer to the HID
195-
specification, version 1.11, section 6.2.2.2,
196-
titled "Short Items." */
197-
size_code = key & 0x3;
198-
switch (size_code) {
199-
case 0:
200-
case 1:
201-
case 2:
202-
data_len = size_code;
203-
break;
204-
case 3:
205-
data_len = 4;
206-
break;
207-
default:
208-
/* Can't ever happen since size_code is & 0x3 */
209-
data_len = 0;
210-
break;
211-
};
212-
key_size = 1;
213-
}
247+
/* Determine data_len and key_size */
248+
if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size))
249+
return 0; /* malformed report */
214250

215251
/* Skip over this key and it's associated data */
216252
i += data_len + key_size;
@@ -220,6 +256,157 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
220256
return 0;
221257
}
222258

259+
/*
260+
* Get bytes from a HID Report Descriptor.
261+
* Only call with a num_bytes of 0, 1, 2, or 4.
262+
*/
263+
static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur)
264+
{
265+
/* Return if there aren't enough bytes. */
266+
if (cur + num_bytes >= len)
267+
return 0;
268+
269+
if (num_bytes == 0)
270+
return 0;
271+
else if (num_bytes == 1)
272+
return rpt[cur + 1];
273+
else if (num_bytes == 2)
274+
return (rpt[cur + 2] * 256 + rpt[cur + 1]);
275+
else if (num_bytes == 4)
276+
return (
277+
rpt[cur + 4] * 0x01000000 +
278+
rpt[cur + 3] * 0x00010000 +
279+
rpt[cur + 2] * 0x00000100 +
280+
rpt[cur + 1] * 0x00000001
281+
);
282+
else
283+
return 0;
284+
}
285+
286+
/*
287+
* Retrieves the device's Usage Page and Usage from the report descriptor.
288+
* The algorithm returns the current Usage Page/Usage pair whenever a new
289+
* Collection is found and a Usage Local Item is currently in scope.
290+
* Usage Local Items are consumed by each Main Item (See. 6.2.2.8).
291+
* The algorithm should give similar results as Apple's:
292+
* https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc
293+
* Physical Collections are also matched (macOS does the same).
294+
*
295+
* This function can be called repeatedly until it returns non-0
296+
* Usage is found. pos is the starting point (initially 0) and will be updated
297+
* to the next search position.
298+
*
299+
* The return value is 0 when a pair is found.
300+
* 1 when finished processing descriptor.
301+
* -1 on a malformed report.
302+
*/
303+
static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage)
304+
{
305+
int data_len, key_size;
306+
int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */
307+
int usage_pair_ready = 0;
308+
309+
/* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */
310+
int usage_found = 0;
311+
312+
while (*pos < size) {
313+
int key = report_descriptor[*pos];
314+
int key_cmd = key & 0xfc;
315+
316+
/* Determine data_len and key_size */
317+
if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size))
318+
return -1; /* malformed report */
319+
320+
switch (key_cmd) {
321+
case 0x4: /* Usage Page 6.2.2.7 (Global) */
322+
*usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
323+
break;
324+
325+
case 0x8: /* Usage 6.2.2.8 (Local) */
326+
*usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos);
327+
usage_found = 1;
328+
break;
329+
330+
case 0xa0: /* Collection 6.2.2.4 (Main) */
331+
/* A Usage Item (Local) must be found for the pair to be valid */
332+
if (usage_found)
333+
usage_pair_ready = 1;
334+
335+
/* Usage is a Local Item, unset it */
336+
usage_found = 0;
337+
break;
338+
339+
case 0x80: /* Input 6.2.2.4 (Main) */
340+
case 0x90: /* Output 6.2.2.4 (Main) */
341+
case 0xb0: /* Feature 6.2.2.4 (Main) */
342+
case 0xc0: /* End Collection 6.2.2.4 (Main) */
343+
/* Usage is a Local Item, unset it */
344+
usage_found = 0;
345+
break;
346+
}
347+
348+
/* Skip over this key and it's associated data */
349+
*pos += data_len + key_size;
350+
351+
/* Return usage pair */
352+
if (usage_pair_ready)
353+
return 0;
354+
}
355+
356+
/* If no top-level application collection is found and usage page/usage pair is found, pair is valid
357+
https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */
358+
if (initial && usage_found)
359+
return 0; /* success */
360+
361+
return 1; /* finished processing */
362+
}
363+
364+
/*
365+
* Retrieves the hidraw report descriptor from a file.
366+
* When using this form, <sysfs_path>/device/report_descriptor, elevated priviledges are not required.
367+
*/
368+
static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc)
369+
{
370+
int rpt_handle;
371+
ssize_t res;
372+
373+
rpt_handle = open(rpt_path, O_RDONLY);
374+
if (rpt_handle < 0) {
375+
register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno));
376+
return -1;
377+
}
378+
379+
/*
380+
* Read in the Report Descriptor
381+
* The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always
382+
* be ok when reading the descriptor.
383+
* In practice if the HID descriptor is any larger I suspect many other things will break.
384+
*/
385+
memset(rpt_desc, 0x0, sizeof(*rpt_desc));
386+
res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE);
387+
if (res < 0) {
388+
register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno));
389+
}
390+
rpt_desc->size = (__u32) res;
391+
392+
close(rpt_handle);
393+
return (int) res;
394+
}
395+
396+
static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc)
397+
{
398+
int res = -1;
399+
/* Construct <sysfs_path>/device/report_descriptor */
400+
size_t rpt_path_len = strlen(sysfs_path) + 25 + 1;
401+
char* rpt_path = (char*) calloc(1, rpt_path_len);
402+
snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path);
403+
404+
res = get_hid_report_descriptor(rpt_path, rpt_desc);
405+
free(rpt_path);
406+
407+
return res;
408+
}
409+
223410
/*
224411
* The caller is responsible for free()ing the (newly-allocated) character
225412
* strings pointed to by serial_number_utf8 and product_name_utf8 after use.
@@ -451,6 +638,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
451638
char *product_name_utf8 = NULL;
452639
int bus_type;
453640
int result;
641+
struct hidraw_report_descriptor report_desc;
454642

455643
/* Get the filename of the /sys entry for the device
456644
and create a udev_device object (dev) representing it */
@@ -582,6 +770,45 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id,
582770
* check for USB and Bluetooth devices above */
583771
break;
584772
}
773+
774+
/* Usage Page and Usage */
775+
result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc);
776+
if (result >= 0) {
777+
unsigned short page = 0, usage = 0;
778+
unsigned int pos = 0;
779+
/*
780+
* Parse the first usage and usage page
781+
* out of the report descriptor.
782+
*/
783+
if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
784+
cur_dev->usage_page = page;
785+
cur_dev->usage = usage;
786+
}
787+
788+
/*
789+
* Parse any additional usage and usage pages
790+
* out of the report descriptor.
791+
*/
792+
while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) {
793+
/* Create new record for additional usage pairs */
794+
tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
795+
cur_dev->next = tmp;
796+
prev_dev = cur_dev;
797+
cur_dev = tmp;
798+
799+
/* Update fields */
800+
cur_dev->path = strdup(dev_path);
801+
cur_dev->vendor_id = dev_vid;
802+
cur_dev->product_id = dev_pid;
803+
cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL;
804+
cur_dev->release_number = prev_dev->release_number;
805+
cur_dev->interface_number = prev_dev->interface_number;
806+
cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL;
807+
cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL;
808+
cur_dev->usage_page = page;
809+
cur_dev->usage = usage;
810+
}
811+
}
585812
}
586813

587814
next:

0 commit comments

Comments
 (0)