diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 4102e6c17..76ca74da1 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -63,10 +63,10 @@ extern "C" { /** Product string */ wchar_t *product_string; /** Usage Page for this Device/Interface - (Windows/Mac only). */ + (Windows/Mac/hidraw only) */ unsigned short usage_page; /** Usage for this Device/Interface - (Windows/Mac only).*/ + (Windows/Mac/hidraw only) */ unsigned short usage; /** The USB interface which this logical device represents. @@ -91,7 +91,7 @@ extern "C" { needed. This function should be called at the beginning of execution however, if there is a chance of HIDAPI handles being opened by different threads simultaneously. - + @ingroup API @returns diff --git a/linux/hid.c b/linux/hid.c index 07ab3e17a..b390fa5c5 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -117,6 +117,19 @@ static void register_global_error(const char *msg) last_global_error_str = utf8_to_wchar_t(msg); } +/* See register_global_error, but you can pass a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + + char msg[100]; + vsnprintf(msg, sizeof(msg), format, args); + + va_end(args); + + register_global_error(msg); +} /* Set the last error for a device to be reported by hid_error(device). * The given error message will be copied (and decoded according to the @@ -152,11 +165,67 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); } +/* + * Gets the size of the HID item at the given position + * Returns 1 if successful, 0 if an invalid key + * Sets data_len and key_size when successful + */ +static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) +{ + int key = report_descriptor[pos]; + int size_code; + + /* + * This is a Long Item. The next byte contains the + * length of the data section (value) for this key. + * See the HID specification, version 1.11, section + * 6.2.2.3, titled "Long Items." + */ + if ((key & 0xf0) == 0xf0) { + if (pos + 1 < size) + { + *data_len = report_descriptor[pos + 1]; + *key_size = 3; + return 1; + } + *data_len = 0; /* malformed report */ + *key_size = 0; + } + + /* + * This is a Short Item. The bottom two bits of the + * key contain the size code for the data section + * (value) for this key. Refer to the HID + * specification, version 1.11, section 6.2.2.2, + * titled "Short Items." + */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + *data_len = size_code; + *key_size = 1; + return 1; + case 3: + *data_len = 4; + *key_size = 1; + return 1; + default: + /* Can't ever happen since size_code is & 0x3 */ + *data_len = 0; + *key_size = 0; + break; + }; + + /* malformed report */ + return 0; +} + /* uses_numbered_reports() returns 1 if report_descriptor describes a device which contains numbered reports. */ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { unsigned int i = 0; - int size_code; int data_len, key_size; while (i < size) { @@ -169,42 +238,9 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { return 1; } - //printf("key: %02hhx\n", key); - - if ((key & 0xf0) == 0xf0) { - /* This is a Long Item. The next byte contains the - length of the data section (value) for this key. - See the HID specification, version 1.11, section - 6.2.2.3, titled "Long Items." */ - if (i+1 < size) - data_len = report_descriptor[i+1]; - else - data_len = 0; /* malformed report */ - key_size = 3; - } - else { - /* This is a Short Item. The bottom two bits of the - key contain the size code for the data section - (value) for this key. Refer to the HID - specification, version 1.11, section 6.2.2.2, - titled "Short Items." */ - size_code = key & 0x3; - switch (size_code) { - case 0: - case 1: - case 2: - data_len = size_code; - break; - case 3: - data_len = 4; - break; - default: - /* Can't ever happen since size_code is & 0x3 */ - data_len = 0; - break; - }; - key_size = 1; - } + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) + return 0; /* malformed report */ /* Skip over this key and it's associated data */ i += data_len + key_size; @@ -214,6 +250,99 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { return 0; } +/* + * Get bytes from a HID Report Descriptor. + * Only call with a num_bytes of 0, 1, 2, or 4. + */ +static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur) +{ + /* Return if there aren't enough bytes. */ + if (cur + num_bytes >= len) + return 0; + + if (num_bytes == 0) + return 0; + else if (num_bytes == 1) + return rpt[cur + 1]; + else if (num_bytes == 2) + return (rpt[cur + 2] * 256 + rpt[cur + 1]); + else if (num_bytes == 4) + return ( + rpt[cur + 4] * 0x01000000 + + rpt[cur + 3] * 0x00010000 + + rpt[cur + 2] * 0x00000100 + + rpt[cur + 1] * 0x00000001 + ); + else + return 0; +} + +/* + * Retrieves the device's Usage Page and Usage from the report + * descriptor. The algorithm is simple, as it just returns the first + * Usage and Usage Page that it finds in the descriptor. + * The return value is 0 on success and -1 on failure. + */ +static int get_hid_usage(__u8 *report_descriptor, __u32 size, unsigned short *usage_page, unsigned short *usage) +{ + unsigned int i = 0; + int data_len, key_size; + int usage_found = 0, usage_page_found = 0; + + while (i < size) { + int key = report_descriptor[i]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) + return -1; /* malformed report */ + + if (key_cmd == 0x4) { + *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, i); + usage_page_found = 1; + } + if (key_cmd == 0x8) { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, i); + usage_found = 1; + } + + if (usage_page_found && usage_found) + return 0; /* success */ + + /* Skip over this key and it's associated data */ + i += data_len + key_size; + } + + return -1; /* failure */ +} + +/* + * Retrieves the hidraw report descriptor + */ +static int get_hid_report_descriptor(int device_handle, struct hidraw_report_descriptor *rpt_desc) +{ + int res, desc_size = 0; + + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + + /* Get Report Descriptor Size */ + res = ioctl(device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + register_global_error_format("ioctl (GRDESCSIZE): %s", strerror(errno)); + return res; + } + + /* Get Report Descriptor */ + rpt_desc->size = desc_size; + res = ioctl(device_handle, HIDIOCGRDESC, rpt_desc); + if (res < 0) { + register_global_error_format("ioctl (GRDESC): %s", strerror(errno)); + return res; + } + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. @@ -538,6 +667,25 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); + /* Usage Page and Usage */ + int res; + struct hidraw_report_descriptor rpt_desc; + int device_handle = open(dev_path, O_RDWR); + if (device_handle > 0) { + res = get_hid_report_descriptor(device_handle, &rpt_desc); + if (res >= 0) { + unsigned short page = 0, usage = 0; + /* + * Parse the usage and usage page + * out of the report descriptor. + */ + get_hid_usage(rpt_desc.value, rpt_desc.size, &page, &usage); + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + close(device_handle); + } + /* Release Number */ str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; @@ -657,22 +805,11 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) register_device_error(dev, NULL); /* Get the report descriptor */ - int res, desc_size = 0; + int res; struct hidraw_report_descriptor rpt_desc; - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - - /* Get Report Descriptor Size */ - res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); - if (res < 0) { - register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); - } else { + res = get_hid_report_descriptor(dev->device_handle, &rpt_desc); + if (res >= 0) { /* Determine if this device uses numbered reports. */ dev->uses_numbered_reports = uses_numbered_reports(rpt_desc.value,