diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 09c980158..2410ff723 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -470,6 +470,21 @@ extern "C" { */ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + /** @brief Get The struct #hid_device_info from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev); + /** @brief Get a string from a HID device, based on its string index. @ingroup API diff --git a/hidtest/test.c b/hidtest/test.c index 157ce6f90..2fe62ea00 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -51,6 +51,24 @@ #endif // +void print_device(struct hid_device_info *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"); + printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string); + printf(" Product: %ls\n", cur_dev->product_string); + printf(" Release: %hx\n", cur_dev->release_number); + printf(" Interface: %d\n", cur_dev->interface_number); + printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page); + printf("\n"); +} + +void print_devices(struct hid_device_info *cur_dev) { + while (cur_dev) { + print_device(cur_dev); + cur_dev = cur_dev->next; + } +} + int main(int argc, char* argv[]) { (void)argc; @@ -63,7 +81,7 @@ int main(int argc, char* argv[]) hid_device *handle; int i; - struct hid_device_info *devs, *cur_dev; + struct hid_device_info *devs; printf("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n", HID_API_VERSION_STR, hid_version_str()); if (HID_API_VERSION == HID_API_MAKE_VERSION(hid_version()->major, hid_version()->minor, hid_version()->patch)) { @@ -83,18 +101,7 @@ int main(int argc, char* argv[]) #endif devs = hid_enumerate(0x0, 0x0); - 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"); - printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string); - printf(" Product: %ls\n", cur_dev->product_string); - printf(" Release: %hx\n", cur_dev->release_number); - printf(" Interface: %d\n", cur_dev->interface_number); - printf(" Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page); - printf("\n"); - cur_dev = cur_dev->next; - } + print_devices(devs); hid_free_enumeration(devs); // Set up the command buffer. @@ -134,6 +141,13 @@ int main(int argc, char* argv[]) printf("Serial Number String: (%d) %ls", wstr[0], wstr); printf("\n"); + struct hid_device_info* info = hid_get_device_info(handle); + if (info == NULL) { + printf("Unable to get device info\n"); + } else { + print_devices(info); + } + // Read Indexed String 1 wstr[0] = 0x0000; res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); diff --git a/libusb/hid.c b/libusb/hid.c index 3103f037d..bb007d5b5 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -146,18 +146,21 @@ struct hid_device_ { /* Handle to the actual device. */ libusb_device_handle *device_handle; + /* USB Configuration Number of the device */ + int config_number; + /* The interface number of the HID */ + int interface; + /* Endpoint information */ int input_endpoint; int output_endpoint; int input_ep_max_packet_size; - /* The interface number of the HID */ - int interface; - /* Indexes of Strings */ int manufacturer_index; int product_index; int serial_index; + struct hid_device_info* device_info; /* Whether blocking reads are used */ int blocking; /* boolean */ @@ -210,6 +213,8 @@ static void free_hid_device(hid_device *dev) pthread_cond_destroy(&dev->condition); pthread_mutex_destroy(&dev->mutex); + hid_free_enumeration(dev->device_info); + /* Free the device itself */ free(dev); } @@ -222,7 +227,6 @@ static void register_error(hid_device *dev, const char *op) } #endif -#ifdef INVASIVE_GET_USAGE /* 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) @@ -322,7 +326,6 @@ static int get_usage(uint8_t *report_descriptor, size_t size, return -1; /* failure */ } -#endif /* INVASIVE_GET_USAGE */ #if defined(__FreeBSD__) && __FreeBSD__ < 10 /* The libusb version included in FreeBSD < 10 doesn't have this function. In @@ -484,9 +487,14 @@ static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) return str; } -static char *make_path(libusb_device *dev, int interface_number, int config_number) +/** + Max length of the result: "000-000.000.000.000.000.000.000:000.000" (39 chars). + 64 is used for simplicity/alignment. +*/ +static void get_path(char (*result)[64], libusb_device *dev, int config_number, int interface_number) { - char str[64]; /* max length "000-000.000.000.000.000.000.000:000.000" */ + char *str = *result; + /* Note that USB3 port count limit is 7; use 8 here for alignment */ uint8_t port_numbers[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int num_ports = libusb_get_port_numbers(dev, port_numbers, 8); @@ -499,7 +507,7 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb n += snprintf(&str[n], sizeof(":000.000"), ":%u.%u", (uint8_t)config_number, (uint8_t)interface_number); str[n] = '\0'; } else { - /* USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ + /* Likely impossible, but check: USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ if (num_ports == LIBUSB_ERROR_OVERFLOW) { LOG("make_path() failed. buffer overflow error\n"); } else { @@ -507,6 +515,12 @@ static char *make_path(libusb_device *dev, int interface_number, int config_numb } str[0] = '\0'; } +} + +static char *make_path(libusb_device *dev, int config_number, int interface_number) +{ + char str[64]; + get_path(&str, dev, config_number, interface_number); return strdup(str); } @@ -548,11 +562,111 @@ int HID_API_EXPORT hid_exit(void) return 0; } +/** + * Requires an opened device with *claimed interface*. + */ +static void fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num) +{ + unsigned char data[4096]; + unsigned short page = 0, usage = 0; + + /* Get the HID Report Descriptor. */ + int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); + if (res >= 0) { + /* Parse the usage and usage page + out of the report descriptor. */ + get_usage(data, res, &page, &usage); + } + else + LOG("libusb_control_transfer() for getting the HID report descriptor failed with %d: %s\n", res, libusb_error_name(res)); + + cur_dev->usage_page = page; + cur_dev->usage = usage; +} + +#ifdef INVASIVE_GET_USAGE +static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num) +{ + int res = 0; + +#ifdef DETACH_KERNEL_DRIVER + int detached = 0; + /* Usage Page and Usage */ + res = libusb_kernel_driver_active(handle, interface_num); + if (res == 1) { + res = libusb_detach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); + else + detached = 1; + } +#endif + + res = libusb_claim_interface(handle, interface_num); + if (res >= 0) { + fill_device_info_usage(cur_dev, handle, interface_num); + + /* Release the interface */ + res = libusb_release_interface(handle, interface_num); + if (res < 0) + LOG("Can't release the interface.\n"); + } + else + LOG("Can't claim interface %d\n", res); + +#ifdef DETACH_KERNEL_DRIVER + /* Re-attach kernel driver if necessary. */ + if (detached) { + res = libusb_attach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't re-attach kernel driver.\n"); + } +#endif +} +#endif /* INVASIVE_GET_USAGE */ + +/** + * Create and fill up most of hid_device_info fields. + * usage_page/usage is not filled up. + */ +static struct hid_device_info * create_device_info_for_device(libusb_device_handle *handle, struct libusb_device_descriptor *desc, int config_number, int interface_num) +{ + struct hid_device_info *cur_dev = calloc(1, sizeof(struct hid_device_info)); + if (cur_dev == NULL) { + return NULL; + } + + /* VID/PID */ + cur_dev->vendor_id = desc->idVendor; + cur_dev->product_id = desc->idProduct; + + cur_dev->release_number = desc->bcdDevice; + + cur_dev->interface_number = interface_num; + + if (!handle) { + return cur_dev; + } + + cur_dev->path = make_path(libusb_get_device(handle), config_number, interface_num); + + if (desc->iSerialNumber > 0) + cur_dev->serial_number = get_usb_string(handle, desc->iSerialNumber); + + /* Manufacturer and Product strings */ + if (desc->iManufacturer > 0) + cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer); + if (desc->iProduct > 0) + cur_dev->product_string = get_usb_string(handle, desc->iProduct); + + return cur_dev; +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; libusb_device *dev; - libusb_device_handle *handle; + libusb_device_handle *handle = NULL; ssize_t num_devs; int i = 0; @@ -589,27 +703,12 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc; intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - int interface_num = intf_desc->bInterfaceNumber; struct hid_device_info *tmp; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); - res = libusb_open(dev, &handle); - if (res >= 0) { #ifdef __ANDROID__ + if (handle) { /* There is (a potential) libusb Android backend, in which device descriptor is not accurate up until the device is opened. https://github.com/libusb/libusb/pull/874#discussion_r632801373 @@ -618,95 +717,45 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, having it here won't do any harm, since reading the device descriptor is as cheap as copy 18 bytes of data. */ libusb_get_device_descriptor(dev, &desc); + } #endif - /* Serial Number */ - if (desc.iSerialNumber > 0) - cur_dev->serial_number = - get_usb_string(handle, desc.iSerialNumber); - - /* Manufacturer and Product strings */ - if (desc.iManufacturer > 0) - cur_dev->manufacturer_string = - get_usb_string(handle, desc.iManufacturer); - if (desc.iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc.iProduct); - + tmp = create_device_info_for_device(handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); + if (tmp) { #ifdef INVASIVE_GET_USAGE -{ - /* - This section is removed because it is too - invasive on the system. Getting a Usage Page - and Usage requires parsing the HID Report - descriptor. Getting a HID Report descriptor - involves claiming the interface. Claiming the - interface involves detaching the kernel driver. - Detaching the kernel driver is hard on the system - because it will unclaim interfaces (if another - app has them claimed) and the re-attachment of - the driver will sometimes change /dev entry names. - It is for these reasons that this section is - #if 0. For composite devices, use the interface - field in the hid_device_info struct to distinguish - between interfaces. */ - unsigned char data[256]; -#ifdef DETACH_KERNEL_DRIVER - int detached = 0; - /* Usage Page and Usage */ - res = libusb_kernel_driver_active(handle, interface_num); - if (res == 1) { - res = libusb_detach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); - else - detached = 1; - } -#endif - res = libusb_claim_interface(handle, interface_num); - if (res >= 0) { - /* Get the HID Report Descriptor. */ - res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); - if (res >= 0) { - unsigned short page=0, usage=0; - /* Parse the usage and usage page - out of the report descriptor. */ - get_usage(data, res, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - else - LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); - - /* Release the interface */ - res = libusb_release_interface(handle, interface_num); - if (res < 0) - LOG("Can't release the interface.\n"); - } - else - LOG("Can't claim interface %d\n", res); -#ifdef DETACH_KERNEL_DRIVER - /* Re-attach kernel driver if necessary. */ - if (detached) { - res = libusb_attach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't re-attach kernel driver.\n"); + /* TODO: have a runtime check for this section. */ + + /* + This section is removed because it is too + invasive on the system. Getting a Usage Page + and Usage requires parsing the HID Report + descriptor. Getting a HID Report descriptor + involves claiming the interface. Claiming the + interface involves detaching the kernel driver. + Detaching the kernel driver is hard on the system + because it will unclaim interfaces (if another + app has them claimed) and the re-attachment of + the driver will sometimes change /dev entry names. + It is for these reasons that this section is + optional. For composite devices, use the interface + field in the hid_device_info struct to distinguish + between interfaces. */ + if (handle) { + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber); } -#endif -} #endif /* INVASIVE_GET_USAGE */ - libusb_close(handle); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; } - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - /* Release Number */ - cur_dev->release_number = desc.bcdDevice; - - /* Interface Number */ - cur_dev->interface_number = interface_num; + if (res >= 0) + libusb_close(handle); } } /* altsettings */ } /* interfaces */ @@ -909,7 +958,7 @@ static void *read_thread(void *param) } -static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc) +static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc) { int i =0; int res = 0; @@ -943,7 +992,8 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa dev->product_index = desc.iProduct; dev->serial_index = desc.iSerialNumber; - /* Store off the interface number */ + /* Store off the USB information */ + dev->config_number = config_number; dev->interface = intf_desc->bInterfaceNumber; dev->input_endpoint = 0; @@ -1017,7 +1067,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) for (k = 0; k < intf->num_altsetting && !good_open; k++) { const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); + char dev_path[64]; + get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); if (!strcmp(dev_path, path)) { /* Matched Paths. Open this device */ @@ -1025,14 +1076,12 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) res = libusb_open(usb_dev, &dev->device_handle); if (res < 0) { LOG("can't open device\n"); - free(dev_path); break; } - good_open = hidapi_initialize_device(dev, intf_desc); + good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc); if (!good_open) libusb_close(dev->device_handle); } - free(dev_path); } } } @@ -1107,7 +1156,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys goto err; } - if (!hidapi_initialize_device(dev, selected_intf_desc)) + if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc)) goto err; return dev; @@ -1455,6 +1504,22 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); } +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); + + dev->device_info = create_device_info_for_device(dev->device_handle, &desc, dev->config_number, dev->interface); + // device error already set by create_device_info_for_device, if any + + if (dev->device_info) { + fill_device_info_usage(dev->device_info, dev->device_handle, dev->interface); + } + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { wchar_t *str; diff --git a/linux/hid.c b/linux/hid.c index d5adc1caf..ed1637dce 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -68,27 +68,12 @@ #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) #endif -/* USB HID device property names */ -const char *device_string_names[] = { - "manufacturer", - "product", - "serial", -}; - -/* Symbolic names for the properties above */ -enum device_string_id { - DEVICE_STRING_MANUFACTURER, - DEVICE_STRING_PRODUCT, - DEVICE_STRING_SERIAL, - - DEVICE_STRING_COUNT, -}; - struct hid_device_ { int device_handle; int blocking; int uses_numbered_reports; wchar_t *last_error_str; + struct hid_device_info* device_info; }; static struct hid_api_version api_version = { @@ -107,6 +92,7 @@ static hid_device *new_hid_device(void) dev->blocking = 1; dev->uses_numbered_reports = 0; dev->last_error_str = NULL; + dev->device_info = NULL; return dev; } @@ -421,6 +407,7 @@ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_ return (int) res; } +/* return size of the descriptor, or -1 on failure */ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) { int res = -1; @@ -435,16 +422,106 @@ static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct h return res; } +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, sizeof(tmp)); + tmp[uevent_len] = '\0'; + + char *saveptr = NULL; + char *line; + char *key; + char *value; + + line = strtok_r(tmp, "\n", &saveptr); + while (line != NULL) { + /* line: "KEY=value" */ + key = line; + value = strchr(line, '='); + if (!value) { + goto next_line; + } + *value = '\0'; + value++; + + if (strcmp(key, "HID_ID") == 0) { + /** + * type vendor product + * HID_ID=0003:000005AC:00008242 + **/ + int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); + if (ret == 3) { + return 1; + } + } + +next_line: + line = strtok_r(NULL, "\n", &saveptr); + } + + register_global_error("Couldn't find/parse HID_ID"); + return 0; +} + +/* return non-zero if successfully parsed */ +static int parse_hid_vid_pid_from_uevent_path(const char *uevent_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int handle; + ssize_t res; + + handle = open(uevent_path, O_RDONLY); + if (handle < 0) { + register_global_error_format("open failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + char buf[1024]; + res = read(handle, buf, sizeof(buf)); + close(handle); + + if (res < 0) { + register_global_error_format("read failed (%s): %s", uevent_path, strerror(errno)); + return 0; + } + + buf[res] = '\0'; + return parse_hid_vid_pid_from_uevent(buf, bus_type, vendor_id, product_id); +} + +/* return non-zero if successfully read/parsed */ +static int parse_hid_vid_pid_from_sysfs(const char *sysfs_path, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id) +{ + int res = 0; + /* Construct /device/uevent */ + size_t uevent_path_len = strlen(sysfs_path) + 14 + 1; + char* uevent_path = (char*) calloc(1, uevent_path_len); + snprintf(uevent_path, uevent_path_len, "%s/device/uevent", sysfs_path); + + res = parse_hid_vid_pid_from_uevent_path(uevent_path, bus_type, vendor_id, product_id); + free(uevent_path); + + 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. */ -static int -parse_uevent_info(const char *uevent, unsigned *bus_type, +static int parse_uevent_info(const char *uevent, unsigned *bus_type, unsigned short *vendor_id, unsigned short *product_id, char **serial_number_utf8, char **product_name_utf8) { - char *tmp = strdup(uevent); + char tmp[1024]; + size_t uevent_len = strlen(uevent); + if (uevent_len > sizeof(tmp) - 1) + uevent_len = sizeof(tmp) - 1; + memcpy(tmp, uevent, sizeof(tmp)); + tmp[uevent_len] = '\0'; + char *saveptr = NULL; char *line; char *key; @@ -488,132 +565,234 @@ parse_uevent_info(const char *uevent, unsigned *bus_type, line = strtok_r(NULL, "\n", &saveptr); } - free(tmp); return (found_id && found_name && found_serial); } -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) { - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; + struct hid_device_info *root = NULL; + struct hid_device_info *cur_dev = NULL; + + const char *sysfs_path; + const char *dev_path; + const char *str; + struct udev_device *hid_dev; /* The device's HID udev node. */ + struct udev_device *usb_dev; /* The device's USB udev node. */ + struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ + unsigned short dev_vid; + unsigned short dev_pid; char *serial_number_utf8 = NULL; char *product_name_utf8 = NULL; + unsigned bus_type; + int result; + struct hidraw_report_descriptor report_desc; - if (!string || !maxlen) { - register_device_error(dev, "Zero buffer/length"); - return -1; + sysfs_path = udev_device_get_syspath(raw_dev); + dev_path = udev_device_get_devnode(raw_dev); + + hid_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "hid", + NULL); + + if (!hid_dev) { + /* Unable to find parent hid device. */ + goto end; + } + + result = parse_uevent_info( + udev_device_get_sysattr_value(hid_dev, "uevent"), + &bus_type, + &dev_vid, + &dev_pid, + &serial_number_utf8, + &product_name_utf8); + + if (!result) { + /* parse_uevent_info() failed for at least one field. */ + goto end; + } + + /* Filter out unhandled devices right away */ + switch (bus_type) { + case BUS_BLUETOOTH: + case BUS_I2C: + case BUS_USB: + break; + + default: + goto end; + } + + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; + + cur_dev = root; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = dev_path? strdup(dev_path): NULL; + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Serial Number */ + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + + /* Release Number */ + cur_dev->release_number = 0x0; + + /* Interface Number */ + cur_dev->interface_number = -1; + + switch (bus_type) { + case BUS_USB: + /* The device pointed to by raw_dev contains information about + the hidraw device. In order to get information about the + USB device, get the parent device with the + subsystem/devtype pair of "usb"/"usb_device". This will + be several levels up the tree, but the function will find + it. */ + usb_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_device"); + + /* uhid USB devices + * Since this is a virtual hid interface, no USB information will + * be available. */ + if (!usb_dev) { + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + break; + } + + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = copy_udev_string(usb_dev, "manufacturer"); + cur_dev->product_string = copy_udev_string(usb_dev, "product"); + + /* Release Number */ + str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); + cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; + + /* Get a handle to the interface's udev node. */ + intf_dev = udev_device_get_parent_with_subsystem_devtype( + raw_dev, + "usb", + "usb_interface"); + if (intf_dev) { + str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); + cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; + } + + break; + + case BUS_BLUETOOTH: + case BUS_I2C: + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string = wcsdup(L""); + cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); + + break; + + default: + /* Unknown device type - this should never happen, as we + * check for USB and Bluetooth devices above */ + break; + } + + /* Usage Page and Usage */ + result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); + if (result >= 0) { + unsigned short page = 0, usage = 0; + unsigned int pos = 0; + /* + * Parse the first usage and usage page + * out of the report descriptor. + */ + if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { + /* Create new record for additional usage pairs */ + struct hid_device_info *tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *prev_dev = cur_dev; + + if (!tmp) + continue; + cur_dev->next = tmp; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = dev_path? strdup(dev_path): NULL; + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; + } } +end: + free(serial_number_utf8); + free(product_name_utf8); + + return root; +} + +static struct hid_device_info * create_device_info_for_hid_device(hid_device *dev) { + struct udev *udev; + struct udev_device *udev_dev; + struct stat s; + int ret = -1; + struct hid_device_info *root = NULL; + register_device_error(dev, NULL); /* Get the dev_t (major/minor numbers) from the file handle. */ ret = fstat(dev->device_handle, &s); if (-1 == ret) { register_device_error(dev, "Failed to stat device handle"); - return ret; + return NULL; } /* Create the udev object */ udev = udev_new(); if (!udev) { register_device_error(dev, "Couldn't create udev context"); - return -1; + return NULL; } /* Open a udev device from the dev_t. 'c' means character device. */ udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); if (udev_dev) { - hid_dev = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "hid", - NULL); - if (hid_dev) { - unsigned short dev_vid; - unsigned short dev_pid; - unsigned bus_type; - size_t retm; - - ret = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - /* Standard USB device */ - if (bus_type == BUS_USB) { - /* This is a USB device. Find its parent USB Device node. */ - parent = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "usb", - "usb_device"); - if (parent) { - const char *str; - const char *key_str = NULL; - - if (key >= 0 && key < DEVICE_STRING_COUNT) { - key_str = device_string_names[key]; - } else { - ret = -1; - goto end; - } - - str = udev_device_get_sysattr_value(parent, key_str); - if (str) { - /* Convert the string from UTF-8 to wchar_t */ - retm = mbstowcs(string, str, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - } - - /* USB information parsed */ - goto end; - } - else { - /* Correctly handled below */ - } - } - - /* USB information not available (uhid) or another type of HID bus */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - switch (key) { - case DEVICE_STRING_MANUFACTURER: - wcsncpy(string, L"", maxlen); - ret = 0; - break; - case DEVICE_STRING_PRODUCT: - retm = mbstowcs(string, product_name_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_SERIAL: - retm = mbstowcs(string, serial_number_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_COUNT: - default: - ret = -1; - break; - } - } - } + root = create_device_info_for_device(udev_dev); } -end: - free(serial_number_utf8); - free(product_name_utf8); + if (!root) { + /* TODO: have a better error reporting via create_device_info_for_device */ + register_device_error(dev, "Couldn't create hid_device_info"); + } udev_device_unref(udev_dev); - /* parent and hid_dev don't need to be (and can't be) unref'd. - I'm not sure why, but they'll throw double-free() errors. */ udev_unref(udev); - return ret; + return root; } HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() @@ -649,7 +828,6 @@ int HID_API_EXPORT hid_exit(void) return 0; } - struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct udev *udev; @@ -658,7 +836,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ hid_init(); /* register_global_error: global error is reset by hid_init */ @@ -679,197 +856,49 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, create a udev_device record for it */ udev_list_entry_foreach(dev_list_entry, devices) { const char *sysfs_path; - const char *dev_path; - const char *str; + unsigned short dev_vid = 0; + unsigned short dev_pid = 0; + unsigned bus_type = 0; struct udev_device *raw_dev; /* The device's hidraw udev node. */ - struct udev_device *hid_dev; /* The device's HID udev node. */ - struct udev_device *usb_dev; /* The device's USB udev node. */ - struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ - unsigned short dev_vid; - unsigned short dev_pid; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - unsigned bus_type; - int result; - struct hidraw_report_descriptor report_desc; + struct hid_device_info * tmp; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ sysfs_path = udev_list_entry_get_name(dev_list_entry); - raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - dev_path = udev_device_get_devnode(raw_dev); + if (!sysfs_path) + continue; - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); + if (vendor_id != 0 || product_id != 0) { + if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) + continue; - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; + if (vendor_id != 0 && vendor_id != dev_vid) + continue; + if (product_id != 0 && product_id != dev_pid) + continue; } - result = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - if (!result) { - /* parse_uevent_info() failed for at least one field. */ - goto next; - } - - /* Filter out unhandled devices right away */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - break; - - default: - goto next; - } - - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; + raw_dev = udev_device_new_from_syspath(udev, sysfs_path); + if (!raw_dev) + continue; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + tmp = create_device_info_for_device(raw_dev); + if (tmp) { if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } - prev_dev = cur_dev; cur_dev = tmp; - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = dev_path? strdup(dev_path): NULL; - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Serial Number */ - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - - /* Release Number */ - cur_dev->release_number = 0x0; - - /* Interface Number */ - cur_dev->interface_number = -1; - - switch (bus_type) { - case BUS_USB: - /* The device pointed to by raw_dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it. */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_device"); - - /* uhid USB devices - Since this is a virtual hid interface, no USB information will - be available. */ - if (!usb_dev) { - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - break; - } - - /* Manufacturer and Product strings */ - 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]); - - /* Release Number */ - str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); - cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; - - /* Get a handle to the interface's udev node. */ - intf_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_interface"); - if (intf_dev) { - str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); - cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; - } - - break; - - case BUS_BLUETOOTH: - case BUS_I2C: - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - - break; - - default: - /* Unknown device type - this should never happen, as we - * check for USB and Bluetooth devices above */ - break; - } - - /* Usage Page and Usage */ - result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); - if (result >= 0) { - unsigned short page = 0, usage = 0; - unsigned int pos = 0; - /* - * Parse the first usage and usage page - * out of the report descriptor. - */ - if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - - /* - * Parse any additional usage and usage pages - * out of the report descriptor. - */ - while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - /* Create new record for additional usage pairs */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev->next = tmp; - prev_dev = cur_dev; - cur_dev = tmp; - - /* Update fields */ - cur_dev->path = strdup(dev_path); - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; - cur_dev->release_number = prev_dev->release_number; - cur_dev->interface_number = prev_dev->interface_number; - cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; - cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; - cur_dev->usage_page = page; - cur_dev->usage = usage; - } + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } } - next: - free(serial_number_utf8); - free(product_name_utf8); udev_device_unref(raw_dev); - /* hid_dev, usb_dev and intf_dev don't need to be (and can't be) - unref()d. It will cause a double-free() error. I'm not - sure why. */ } /* Free the enumerator and udev objects. */ udev_enumerate_unref(enumerate); @@ -1125,23 +1154,93 @@ void HID_API_EXPORT hid_close(hid_device *dev) /* Free the device error message */ register_device_error(dev, NULL); + hid_free_enumeration(dev->device_info); + free(dev); } int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->manufacturer_string) { + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->product_string) { + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); + if (!string || !maxlen) { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { + // hid_get_device_info will have set an error already + return -1; + } + + if (info->serial_number) { + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + } + else { + string[0] = L'\0'; + } + + return 0; +} + + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) { + // Lazy initialize device_info + dev->device_info = create_device_info_for_hid_device(dev); + } + + // create_device_info_for_hid_device will set an error if needed + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) diff --git a/mac/hid.c b/mac/hid.c index d78ac9bee..678c7bded 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -132,6 +132,7 @@ struct hid_device_ { uint8_t *input_report_buf; CFIndex max_input_report_len; struct input_report *input_reports; + struct hid_device_info* device_info; pthread_t thread; pthread_mutex_t mutex; /* Protects input_reports */ @@ -154,6 +155,7 @@ static hid_device *new_hid_device(void) dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; + dev->device_info = NULL; dev->shutdown_thread = 0; /* Thread objects */ @@ -187,6 +189,7 @@ static void free_hid_device(hid_device *dev) if (dev->source) CFRelease(dev->source); free(dev->input_report_buf); + hid_free_enumeration(dev->device_info); /* Clean up the thread objects */ pthread_barrier_destroy(&dev->shutdown_barrier); @@ -1161,17 +1164,74 @@ void HID_API_EXPORT hid_close(hid_device *dev) int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_manufacturer_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->manufacturer_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_product_string(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->product_string, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; } int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - return get_serial_number(dev->device_handle, string, maxlen); + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) + { + // hid_get_device_info will have set an error already + return -1; + } + + wcsncpy(string, info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + dev->device_info = create_device_info(dev->device_handle); + } + + return dev->device_info; } int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) diff --git a/windows/hid.c b/windows/hid.c index 3b9fbac73..dbde2fa0f 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1141,13 +1141,13 @@ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) { - register_string_error(dev, L"NULL device info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } @@ -1161,13 +1161,13 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) { - register_string_error(dev, L"NULL device info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } @@ -1181,13 +1181,13 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wch int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) { - register_string_error(dev, L"NULL device info"); + if (!string || !maxlen) { + register_string_error(dev, L"Zero buffer/length"); return -1; } - if (!string || !maxlen) { - register_string_error(dev, L"Zero buffer/length"); + if (!dev->device_info) { + register_string_error(dev, L"NULL device info"); return -1; } @@ -1199,6 +1199,16 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de return 0; } +HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + register_string_error(dev, L"NULL device info"); + return NULL; + } + + return dev->device_info; +} + int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) { BOOL res;