Skip to content

Commit

Permalink
Fill in the Usage Page and Usage on Linux with hidraw
Browse files Browse the repository at this point in the history
Adapted from ApeironTuska's PR to PastaJ36/hidapi (PastaJ36/hidapi#1)
Which was adapted from djpnewton's PR to signal11/hidapi (signal11/hidapi#6)

Also addresses some of the issues mentioned in (signal11/hidapi#6)
* hid_open_path and hid_enumerate both need to retrieve the usage page
as the user may call hid_open_path directly without using hid_enumerate
* Now using uses_numbered_reports() instead of duplicated code

Includes additional interface fields that are useful in Linux when it's
not possible (undesireable) to unbind the HID interface during a scan.
* iInterface (only some devices use this, but can be very useful)
* bInterfaceClass
* bInterfaceSubClass
* bInterfaceProtocol
* bNumEndpoints

TODO - macOS and Windows
TODO - Test hid and libusb usage and usage page
  • Loading branch information
haata committed Jan 19, 2020
1 parent 7d9b4dc commit a27a6c2
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 17 deletions.
6 changes: 4 additions & 2 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ extern "C" {
/** Product string */
wchar_t *product_string;
/** Usage Page for this Device/Interface
(Windows/Mac only). */
(Windows/Mac only)
(Linux if unbound)*/
unsigned short usage_page;
/** Usage for this Device/Interface
(Windows/Mac only).*/
(Windows/Mac only)
(Linux if unbound)*/
unsigned short usage;
/** The USB interface which this logical device
represents.
Expand Down
164 changes: 149 additions & 15 deletions linux/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
return 1;
}

//printf("key: %02hhx\n", key);
printf("key: %02hhx\n", key);

if ((key & 0xf0) == 0xf0) {
/* This is a Long Item. The next byte contains the
Expand Down Expand Up @@ -228,6 +228,132 @@ 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_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_usage(__u8 *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;
printf("GETTT");

while (i < size) {
int key = report_descriptor[i];
int key_cmd = key & 0xfc;
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;
}

if (key_cmd == 0x4) {
*usage_page = get_bytes(report_descriptor, size, data_len, i);
usage_page_found = 1;
}
if (key_cmd == 0x8) {
*usage = get_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 */
}

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) {
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.
Expand Down Expand Up @@ -567,6 +693,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_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;
Expand Down Expand Up @@ -736,22 +881,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_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,
Expand Down

0 comments on commit a27a6c2

Please sign in to comment.