diff --git a/linux/hid.c b/linux/hid.c index 07ab3e17a..0daff3625 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -27,6 +27,7 @@ #include #include #include +#include /* Unix */ #include @@ -169,8 +170,6 @@ 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. @@ -214,6 +213,126 @@ 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 uint32_t get_bytes(uint8_t *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_usage(uint8_t *report_descriptor, size_t size, + unsigned short *usage_page, unsigned short *usage) +{ + int i = 0; + int size_code; + 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; + + 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; + } + + if (key_cmd == 0x4) { + *usage_page = get_bytes(report_descriptor, size, data_len, i); + usage_page_found = 1; + //printf("Usage Page: %x\n", (uint32_t)*usage_page); + } + if (key_cmd == 0x8) { + *usage = get_bytes(report_descriptor, size, data_len, i); + usage_found = 1; + //printf("Usage: %x\n", (uint32_t)*usage); + } + + 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 */ +} + +static int get_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) { + perror("HIDIOCGRDESCSIZE"); + return res; + } + + /* Get Report Descriptor */ + rpt_desc->size = desc_size; + res = ioctl(device_handle, HIDIOCGRDESC, rpt_desc); + if (res < 0) + perror("HIDIOCGRDESC"); + + 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 +657,23 @@ 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_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_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,26 +793,13 @@ 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_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, - rpt_desc.size); + uses_numbered_reports(rpt_desc.value, rpt_desc.size); } return dev;