Skip to content

Commit

Permalink
Usage Page and Usage on Linux with hidraw and whitespace cleanup
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
* Added get_hid_item_size() for hid parsing, used in both uses_numbered_reports() and get_usage()

- hidtest-hidraw test -

Device Found
  type: 308f 0015
  path: /dev/hidraw10
  serial_number: 53373100323943353230353139363032 - sam4s2
  Manufacturer: Kiibohd
  Product:      Keyboard - None PartialMap USBxUART
  Release:      4ac
  Interface:    0
  Usage (page): 0x6 (0x1)

Device Found
  type: 308f 0015
  path: /dev/hidraw11
  serial_number: 53373100323943353230353139363032 - sam4s2
  Manufacturer: Kiibohd
  Product:      Keyboard - None PartialMap USBxUART
  Release:      4ac
  Interface:    1
  Usage (page): 0x6 (0x1)

Device Found
  type: 308f 0015
  path: /dev/hidraw12
  serial_number: 53373100323943353230353139363032 - sam4s2
  Manufacturer: Kiibohd
  Product:      Keyboard - None PartialMap USBxUART
  Release:      4ac
  Interface:    2
  Usage (page): 0x1 (0xc)

Device Found
  type: 308f 0015
  path: /dev/hidraw13
  serial_number: 53373100323943353230353139363032 - sam4s2
  Manufacturer: Kiibohd
  Product:      Keyboard - None PartialMap USBxUART
  Release:      4ac
  Interface:    3
  Usage (page): 0x2 (0x1)

Device Found
  type: 308f 0015
  path: /dev/hidraw14
  serial_number: 53373100323943353230353139363032 - sam4s2
  Manufacturer: Kiibohd
  Product:      Keyboard - None PartialMap USBxUART
  Release:      4ac
  Interface:    4
  Usage (page): 0x1100 (0xff1c)
  • Loading branch information
haata committed Jan 22, 2020
1 parent 51bdec7 commit fdb411c
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 78 deletions.
6 changes: 3 additions & 3 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions hidtest/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
8/22/2009
Copyright 2009
This contents of this file may be used by anyone
for any reason without any conditions and may be
used as a starting point for your own applications
Expand Down Expand Up @@ -42,12 +42,12 @@ int main(int argc, char* argv[])
#endif

struct hid_device_info *devs, *cur_dev;

if (hid_init())
return -1;

devs = hid_enumerate(0x0, 0x0);
cur_dev = devs;
cur_dev = devs;
while (cur_dev) {
printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
printf("\n");
Expand All @@ -65,7 +65,7 @@ int main(int argc, char* argv[])
memset(buf,0x00,sizeof(buf));
buf[0] = 0x01;
buf[1] = 0x81;


// Open the device using the VID, PID,
// and optionally the Serial number.
Expand Down Expand Up @@ -107,7 +107,7 @@ int main(int argc, char* argv[])

// Set the hid_read() function to be non-blocking.
hid_set_nonblocking(handle, 1);

// Try to read from the device. There should be no
// data here, but execution should not block.
res = hid_read(handle, buf, 17);
Expand Down Expand Up @@ -150,7 +150,7 @@ int main(int argc, char* argv[])
printf("Unable to write()\n");
printf("Error: %ls\n", hid_error(handle));
}


// Request state (cmd 0x81). The first byte is the report number (0x1).
buf[0] = 0x1;
Expand Down
240 changes: 189 additions & 51 deletions linux/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ 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
Expand Down Expand Up @@ -152,11 +166,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) {
Expand All @@ -169,42 +239,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;
Expand All @@ -214,6 +251,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_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, __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_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 */
}

/*
* Retrieves the hidraw report descriptor
*/
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 @@ -538,6 +668,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 @@ -657,22 +806,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
Loading

0 comments on commit fdb411c

Please sign in to comment.