From 4155be772186a1511fbd3f6f3077cbb0f0246a8c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 20 Jun 2022 23:46:07 +0100 Subject: [PATCH 01/28] feat: Add hid_get_device_info --- hidapi/hidapi.h | 12 ++ hidtest/test.c | 27 ++- libusb/hid.c | 228 +++++++++++--------- linux/hid.c | 542 ++++++++++++++++++++++++------------------------ mac/hid.c | 66 +++++- windows/hid.c | 10 + 6 files changed, 502 insertions(+), 383 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 09c980158..d6ab16ae4 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -470,6 +470,18 @@ extern "C" { */ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + /** @brief Get The stuct #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. + This struct is valid until the device is closed with hid_close() + */ + struct hid_device_info HID_API_EXPORT *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..2acfe2965 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -51,6 +51,17 @@ #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"); +} + int main(int argc, char* argv[]) { (void)argc; @@ -85,14 +96,7 @@ int main(int argc, char* argv[]) 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"); + print_device(cur_dev); cur_dev = cur_dev->next; } hid_free_enumeration(devs); @@ -134,6 +138,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_device(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..31bdbe3e8 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -158,6 +158,7 @@ struct hid_device_ { 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 +211,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); } @@ -548,6 +551,95 @@ int HID_API_EXPORT hid_exit(void) return 0; } + +static void fill_device_info_for_device(libusb_device_handle *handle, struct hid_device_info * cur_dev, struct libusb_device_descriptor *desc) +{ + /* VID/PID */ + cur_dev->vendor_id = desc->idVendor; + cur_dev->product_id = desc->idProduct; + + /* Release Number */ + cur_dev->release_number = desc->bcdDevice; + + if (handle) { + /* 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); + + #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"); + } +#endif +} +#endif /* INVASIVE_GET_USAGE */ + } +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { libusb_device **devs; @@ -608,102 +700,23 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, res = libusb_open(dev, &handle); - if (res >= 0) { -#ifdef __ANDROID__ - /* 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 - A workaround is to re-read the descriptor again. - Even if it is not going to be accepted into libusb master, - 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); - -#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; + #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 + A workaround is to re-read the descriptor again. + Even if it is not going to be accepted into libusb master, + 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 - 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); + #endif - /* 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 */ + fill_device_info_for_device(handle, cur_dev, &desc); + if (res >= 0) libusb_close(handle); - } - /* 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; @@ -909,12 +922,17 @@ 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, char* path, const struct libusb_interface_descriptor *intf_desc) { int i =0; int res = 0; struct libusb_device_descriptor desc; - libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); + libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); + + dev->device_info = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + dev->device_info->next = NULL; + dev->device_info->path = path; + fill_device_info_for_device(dev->device_handle, dev->device_info, &desc); #ifdef DETACH_KERNEL_DRIVER /* Detach the kernel driver, but only if the @@ -1028,11 +1046,14 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) free(dev_path); break; } - good_open = hidapi_initialize_device(dev, intf_desc); + good_open = hidapi_initialize_device(dev, dev_path, intf_desc); if (!good_open) libusb_close(dev->device_handle); + + // dev_path is owned by dev now + } else { + free(dev_path); } - free(dev_path); } } } @@ -1062,6 +1083,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys const struct libusb_interface_descriptor *selected_intf_desc = NULL; int res = 0; int j = 0, k = 0; + char* path; if(hid_init() < 0) return NULL; @@ -1107,7 +1129,8 @@ 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)) + path = make_path(libusb_get_device(dev->device_handle), selected_intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); + if (!hidapi_initialize_device(dev, path, selected_intf_desc)) goto err; return dev; @@ -1118,6 +1141,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys if (dev->device_handle) libusb_close(dev->device_handle); free_hid_device(dev); + // If path is set it, ownership was given to hidapi_initialize_device #else (void)sys_dev; (void)interface_num; @@ -1455,6 +1479,16 @@ 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); } +struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + // register_device_error(dev, "NULL device/info"); + return NULL; + } + + 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..34e56de6f 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -89,6 +89,7 @@ struct hid_device_ { 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 +108,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; } @@ -493,127 +495,229 @@ parse_uevent_info(const char *uevent, unsigned *bus_type, } -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) +static void get_device_info_for_device(struct udev_device *raw_dev, unsigned short vendor_id, unsigned short product_id, struct hid_device_info ** first_dev, struct hid_device_info ** last_dev) { - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; + struct hid_device_info *cur_dev = NULL; + struct hid_device_info *prev_dev = NULL; /* previous device */ + + 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 next; } + 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; + + /* VID/PID match. Create the record. */ + *first_dev = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + cur_dev = *first_dev; + + /* 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; + } + } + } + + next: + free(serial_number_utf8); + free(product_name_utf8); + /* 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. */ + + *last_dev = cur_dev; +} + +struct hid_device_info* get_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; + 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; - } - } - } + struct hid_device_info * tmp; + get_device_info_for_device(udev_dev, 0, 0, &root, &tmp); } -end: - free(serial_number_utf8); - free(product_name_utf8); - 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 +753,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 +761,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 +781,27 @@ 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; 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; /* 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); - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); - - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; - } - - 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; - - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info * first_dev; + struct hid_device_info * last_dev; + get_device_info_for_device(raw_dev, vendor_id, product_id, &first_dev, &last_dev); + if (first_dev) { if (cur_dev) { - cur_dev->next = tmp; + cur_dev->next = first_dev; } 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; - } + root = first_dev; } + cur_dev = last_dev; } - 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); @@ -956,6 +888,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* If we have a good handle, return it. */ if (dev->device_handle >= 0) { + dev->device_info = get_device_info_for_hid_device(dev); + /* Get the report descriptor */ int res, desc_size = 0; struct hidraw_report_descriptor rpt_desc; @@ -1125,23 +1059,81 @@ 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 (!dev->device_info) + { + register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_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_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); + if (!dev->device_info) + { + register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_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_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); + if (!dev->device_info) + { + register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + + +struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { + if (!dev->device_info) + { + register_device_error(dev, "NULL device/info"); + return NULL; + } + + 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..64dcb02dc 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 */ @@ -155,6 +156,7 @@ static hid_device *new_hid_device(void) dev->input_report_buf = NULL; dev->input_reports = NULL; dev->shutdown_thread = 0; + dev->device_info = NULL; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); @@ -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); @@ -827,6 +830,8 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) goto return_error; } + dev->device_info = create_device_info(dev->device_handle); + /* Open the IOHIDDevice */ ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); if (ret == kIOReturnSuccess) { @@ -1161,17 +1166,72 @@ 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 (!dev->device_info) + { + // register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_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 (!dev->device_info) + { + // register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_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 (!dev->device_info) + { + // register_device_error(dev, "NULL device/info"); + return -1; + } + + if (!string || !maxlen) + { + // register_device_error(dev, "Zero buffer/length"); + return -1; + } + + wcsncpy(string, dev->device_info->serial_number, maxlen); + string[maxlen - 1] = L'\0'; + + return 0; +} + +struct hid_device_info *HID_API_EXPORT_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_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..72f48abd5 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1199,6 +1199,16 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de return 0; } +struct hid_device_info *HID_API_EXPORT_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; From 1818b598c8d720d1cabd44e51a4f2ba9214b999c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 20 Jun 2022 23:49:10 +0100 Subject: [PATCH 02/28] chore: formatting --- libusb/hid.c | 114 +++++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 31bdbe3e8..d839ff978 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -575,65 +575,65 @@ static void fill_device_info_for_device(libusb_device_handle *handle, struct hid cur_dev->product_string = get_usb_string(handle, desc->iProduct); - #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; - } +#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); + 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"); - } + /* 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 */ From 19e03fb52b2dd1b76b81bfa94384c109169138d1 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 23 Jun 2022 19:16:18 +0100 Subject: [PATCH 03/28] Apply suggestions from code review Co-authored-by: Ihor Dutchak --- libusb/hid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index d839ff978..25ec9e3cf 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -700,7 +700,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, res = libusb_open(dev, &handle); - #ifdef __ANDROID__ +#ifdef __ANDROID__ if (handle) { /* There is (a potential) libusb Android backend, in which device descriptor is not accurate up until the device is opened. @@ -711,7 +711,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, is as cheap as copy 18 bytes of data. */ libusb_get_device_descriptor(dev, &desc); } - #endif +#endif fill_device_info_for_device(handle, cur_dev, &desc); From 5497de13e19c6dc6f9fb05b4902343b2ee538d5e Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 23 Jun 2022 19:16:34 +0100 Subject: [PATCH 04/28] Update hidapi/hidapi.h Co-authored-by: Ihor Dutchak --- hidapi/hidapi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index d6ab16ae4..5625c3c61 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -478,7 +478,7 @@ extern "C" { @returns This function returns a pointer to the struct #hid_device_info for this hid_device, or NULL in the case of failure. - This struct is valid until the device is closed with hid_close() + This struct is valid until the device is closed with hid_close(). */ struct hid_device_info HID_API_EXPORT *HID_API_CALL hid_get_device_info(hid_device *dev); From f88c36cf80990953d0aaa01b8c41559fd3d497bc Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 23 Jun 2022 19:41:23 +0100 Subject: [PATCH 05/28] fix: segfaults --- libusb/hid.c | 22 +++++++++++----------- linux/hid.c | 4 ++-- windows/hid.c | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 25ec9e3cf..18fb255fc 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -644,7 +644,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, { libusb_device **devs; libusb_device *dev; - libusb_device_handle *handle; + libusb_device_handle *handle = NULL; ssize_t num_devs; int i = 0; @@ -701,16 +701,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, res = libusb_open(dev, &handle); #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 - A workaround is to re-read the descriptor again. - Even if it is not going to be accepted into libusb master, - 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); - } + 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 + A workaround is to re-read the descriptor again. + Even if it is not going to be accepted into libusb master, + 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 fill_device_info_for_device(handle, cur_dev, &desc); diff --git a/linux/hid.c b/linux/hid.c index 34e56de6f..549bbbbe3 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -788,8 +788,8 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, sysfs_path = udev_list_entry_get_name(dev_list_entry); raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - struct hid_device_info * first_dev; - struct hid_device_info * last_dev; + struct hid_device_info * first_dev = NULL; + struct hid_device_info * last_dev = NULL; get_device_info_for_device(raw_dev, vendor_id, product_id, &first_dev, &last_dev); if (first_dev) { if (cur_dev) { diff --git a/windows/hid.c b/windows/hid.c index 72f48abd5..2e62c2237 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1199,7 +1199,7 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de return 0; } -struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { +struct hid_device_info *HID_API_EXPORT_CALL HID_API_CALL hid_get_device_info(hid_device *dev) { if (!dev->device_info) { register_string_error(dev, L"NULL device/info"); From 575b133a856002e966e5543547558e81c4a9b5dd Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 23 Jun 2022 19:55:53 +0100 Subject: [PATCH 06/28] fix: windows hopefully --- hidapi/hidapi.h | 2 +- libusb/hid.c | 2 +- linux/hid.c | 2 +- mac/hid.c | 2 +- windows/hid.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 5625c3c61..56f31e211 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -480,7 +480,7 @@ extern "C" { for this hid_device, or NULL in the case of failure. This struct is valid until the device is closed with hid_close(). */ - struct hid_device_info HID_API_EXPORT *HID_API_CALL hid_get_device_info(hid_device *dev); + 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. diff --git a/libusb/hid.c b/libusb/hid.c index 18fb255fc..1633aacf5 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1479,7 +1479,7 @@ 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); } -struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { if (!dev->device_info) { // register_device_error(dev, "NULL device/info"); diff --git a/linux/hid.c b/linux/hid.c index 549bbbbe3..5245b2f72 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1126,7 +1126,7 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s } -struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { +HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { if (!dev->device_info) { register_device_error(dev, "NULL device/info"); diff --git a/mac/hid.c b/mac/hid.c index 64dcb02dc..5285b7699 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -1224,7 +1224,7 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return 0; } -struct hid_device_info *HID_API_EXPORT_CALL hid_get_device_info(hid_device *dev) { +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"); diff --git a/windows/hid.c b/windows/hid.c index 2e62c2237..68ee8c583 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1199,7 +1199,7 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de return 0; } -struct hid_device_info *HID_API_EXPORT_CALL HID_API_CALL hid_get_device_info(hid_device *dev) { +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"); From 372c89b2b41183419b6ed60ad4bc6fd712e1691a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 17 Jul 2022 12:19:58 +0100 Subject: [PATCH 07/28] fix: code review suggestions --- hidapi/hidapi.h | 2 +- libusb/hid.c | 24 ++++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 56f31e211..e666296d8 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -470,7 +470,7 @@ extern "C" { */ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); - /** @brief Get The stuct #hid_device_info from a HID device. + /** @brief Get The struct #hid_device_info from a HID device. @ingroup API @param dev A device handle returned from hid_open(). diff --git a/libusb/hid.c b/libusb/hid.c index 1633aacf5..fa12ebe4a 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -922,7 +922,7 @@ static void *read_thread(void *param) } -static int hidapi_initialize_device(hid_device *dev, char* path, const struct libusb_interface_descriptor *intf_desc) +static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc) { int i =0; int res = 0; @@ -931,7 +931,7 @@ static int hidapi_initialize_device(hid_device *dev, char* path, const struct li dev->device_info = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); dev->device_info->next = NULL; - dev->device_info->path = path; + // dev->device_info->path is set by the caller fill_device_info_for_device(dev->device_handle, dev->device_info, &desc); #ifdef DETACH_KERNEL_DRIVER @@ -1046,14 +1046,17 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) free(dev_path); break; } - good_open = hidapi_initialize_device(dev, dev_path, intf_desc); + good_open = hidapi_initialize_device(dev, intf_desc); if (!good_open) libusb_close(dev->device_handle); - // dev_path is owned by dev now - } else { - free(dev_path); + if (dev->device_info) { + dev->device_info->path = dev_path; + dev_path = NULL; + } + } + free(dev_path); } } } @@ -1083,7 +1086,6 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys const struct libusb_interface_descriptor *selected_intf_desc = NULL; int res = 0; int j = 0, k = 0; - char* path; if(hid_init() < 0) return NULL; @@ -1129,10 +1131,13 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys goto err; } - path = make_path(libusb_get_device(dev->device_handle), selected_intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); - if (!hidapi_initialize_device(dev, path, selected_intf_desc)) + if (!hidapi_initialize_device(dev, selected_intf_desc)) goto err; + if (dev->device_info) { + dev->device_info->path = make_path(libusb_get_device(dev->device_handle), selected_intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); + } + return dev; err: @@ -1141,7 +1146,6 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys if (dev->device_handle) libusb_close(dev->device_handle); free_hid_device(dev); - // If path is set it, ownership was given to hidapi_initialize_device #else (void)sys_dev; (void)interface_num; From c42b4b89437f4afd50a09cdcbd7245cf99abad3c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 26 Jul 2022 19:32:34 +0100 Subject: [PATCH 08/28] fix: lazy load hid_get_device_info result on some platforms --- linux/hid.c | 28 +++++++++++++++------------- mac/hid.c | 26 +++++++++++++------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 5245b2f72..e6d22ab9a 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -888,8 +888,6 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* If we have a good handle, return it. */ if (dev->device_handle >= 0) { - dev->device_info = get_device_info_for_hid_device(dev); - /* Get the report descriptor */ int res, desc_size = 0; struct hidraw_report_descriptor rpt_desc; @@ -1067,9 +1065,10 @@ 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) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1079,7 +1078,7 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st return -1; } - wcsncpy(string, dev->device_info->manufacturer_string, maxlen); + wcsncpy(string, info->manufacturer_string, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1087,9 +1086,10 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1099,7 +1099,7 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, return -1; } - wcsncpy(string, dev->device_info->product_string, maxlen); + wcsncpy(string, info->product_string, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1107,9 +1107,10 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1119,7 +1120,7 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return -1; } - wcsncpy(string, dev->device_info->serial_number, maxlen); + wcsncpy(string, info->serial_number, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1129,10 +1130,11 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { if (!dev->device_info) { - register_device_error(dev, "NULL device/info"); - return NULL; + // Lazy initialize device_info + dev->device_info = get_device_info_for_hid_device(dev); } + // get_device_info_for_hid_device will set an error if needed return dev->device_info; } diff --git a/mac/hid.c b/mac/hid.c index 5285b7699..a0cef9073 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -830,8 +830,6 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) goto return_error; } - dev->device_info = create_device_info(dev->device_handle); - /* Open the IOHIDDevice */ ret = IOHIDDeviceOpen(dev->device_handle, dev->open_options); if (ret == kIOReturnSuccess) { @@ -1166,9 +1164,10 @@ 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) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1178,7 +1177,7 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st return -1; } - wcsncpy(string, dev->device_info->manufacturer_string, maxlen); + wcsncpy(string, info->manufacturer_string, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1186,9 +1185,10 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1198,7 +1198,7 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, return -1; } - wcsncpy(string, dev->device_info->product_string, maxlen); + wcsncpy(string, info->product_string, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1206,9 +1206,10 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - if (!dev->device_info) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "NULL device/info"); + // hid_get_device_info will have set an error already return -1; } @@ -1218,7 +1219,7 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s return -1; } - wcsncpy(string, dev->device_info->serial_number, maxlen); + wcsncpy(string, info->serial_number, maxlen); string[maxlen - 1] = L'\0'; return 0; @@ -1227,8 +1228,7 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s 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; + dev->device_info = create_device_info(dev->device_handle); } return dev->device_info; From 3d992cddf37ff39ffff9f4c35bc52b06b6db1888 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 1 Aug 2022 13:42:59 +0300 Subject: [PATCH 09/28] Update libusb/hid.c --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index fa12ebe4a..fb4e30b9b 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -927,7 +927,7 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa int i =0; int res = 0; struct libusb_device_descriptor desc; - libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); + libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); dev->device_info = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); dev->device_info->next = NULL; From 8d2954c04506fddbf4089d63e7289db950808c3c Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 1 Aug 2022 13:43:37 +0300 Subject: [PATCH 10/28] Update libusb/hid.c --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index fb4e30b9b..4cb077f6d 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -612,7 +612,7 @@ static void fill_device_info_for_device(libusb_device_handle *handle, struct hid if (res >= 0) { unsigned short page=0, usage=0; /* Parse the usage and usage page - out of the report descriptor. */ + out of the report descriptor. */ get_usage(data, res, &page, &usage); cur_dev->usage_page = page; cur_dev->usage = usage; From 29f3826c98b8b1bef074640078ea58bc5ff43daa Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 1 Aug 2022 13:56:41 +0300 Subject: [PATCH 11/28] Update libusb/hid.c --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 4cb077f6d..c36ebf678 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1131,7 +1131,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, selected_intf_desc)) goto err; if (dev->device_info) { From 5c75cd06396f59d1b52e70ebb0f8c74dc8705b56 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Mon, 1 Aug 2022 14:14:29 +0300 Subject: [PATCH 12/28] Update hidapi/hidapi.h --- hidapi/hidapi.h | 1 + 1 file changed, 1 insertion(+) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index e666296d8..4e9f24096 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -478,6 +478,7 @@ extern "C" { @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(). */ HID_API_EXPORT struct hid_device_info * HID_API_CALL hid_get_device_info(hid_device *dev); From 938b9120f64254526a0089e50ef53bfe8e682db1 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 1 Aug 2022 19:36:29 +0100 Subject: [PATCH 13/28] Apply suggestions from code review Co-authored-by: Ihor Dutchak --- mac/hid.c | 2 +- windows/hid.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mac/hid.c b/mac/hid.c index a0cef9073..760424c2c 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -155,8 +155,8 @@ static hid_device *new_hid_device(void) dev->source = NULL; dev->input_report_buf = NULL; dev->input_reports = NULL; - dev->shutdown_thread = 0; dev->device_info = NULL; + dev->shutdown_thread = 0; /* Thread objects */ pthread_mutex_init(&dev->mutex, NULL); diff --git a/windows/hid.c b/windows/hid.c index 68ee8c583..fda4ddef8 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1202,7 +1202,7 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *de 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"); + register_string_error(dev, L"NULL device info"); return NULL; } From 183267429217dec8b9f5f52576e7ad4bd75b9b94 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 1 Aug 2022 19:39:50 +0100 Subject: [PATCH 14/28] code review comments --- libusb/hid.c | 12 ++++++------ linux/hid.c | 30 +++++++++++++++--------------- mac/hid.c | 30 +++++++++++++++--------------- windows/hid.c | 24 ++++++++++++------------ 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index c36ebf678..324a3581c 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -703,12 +703,12 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, #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 - A workaround is to re-read the descriptor again. - Even if it is not going to be accepted into libusb master, - having it here won't do any harm, since reading the device descriptor - is as cheap as copy 18 bytes of data. */ + device descriptor is not accurate up until the device is opened. + https://github.com/libusb/libusb/pull/874#discussion_r632801373 + A workaround is to re-read the descriptor again. + Even if it is not going to be accepted into libusb master, + 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 diff --git a/linux/hid.c b/linux/hid.c index e6d22ab9a..e44443b22 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1065,16 +1065,16 @@ 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) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } @@ -1086,16 +1086,16 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } @@ -1107,16 +1107,16 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } diff --git a/mac/hid.c b/mac/hid.c index 760424c2c..678c7bded 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -1164,16 +1164,16 @@ 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) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + // register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } @@ -1185,16 +1185,16 @@ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *st int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + // register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } @@ -1206,16 +1206,16 @@ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - struct hid_device_info *info = hid_get_device_info(dev); - if (!info) + if (!string || !maxlen) { - // hid_get_device_info will have set an error already + // register_device_error(dev, "Zero buffer/length"); return -1; } - if (!string || !maxlen) + struct hid_device_info *info = hid_get_device_info(dev); + if (!info) { - // register_device_error(dev, "Zero buffer/length"); + // hid_get_device_info will have set an error already return -1; } diff --git a/windows/hid.c b/windows/hid.c index fda4ddef8..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; } From fdddfc646ffc22c741d7d5f8f35ff3cfa3ae7deb Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 1 Aug 2022 19:47:08 +0100 Subject: [PATCH 15/28] refactor libusb fill_device_info_for_device into create_device_info_for_device --- libusb/hid.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 324a3581c..3ffaad34f 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -552,8 +552,10 @@ int HID_API_EXPORT hid_exit(void) } -static void fill_device_info_for_device(libusb_device_handle *handle, struct hid_device_info * cur_dev, struct libusb_device_descriptor *desc) +static struct hid_device_info* create_device_info_for_device(libusb_device_handle *handle, struct libusb_device_descriptor *desc) { + struct hid_device_info* cur_dev = calloc(1, sizeof(struct hid_device_info)); + /* VID/PID */ cur_dev->vendor_id = desc->idVendor; cur_dev->product_id = desc->idProduct; @@ -638,6 +640,8 @@ static void fill_device_info_for_device(libusb_device_handle *handle, struct hid } #endif /* INVASIVE_GET_USAGE */ } + + return cur_dev; } struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) @@ -682,22 +686,8 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, 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); #ifdef __ANDROID__ @@ -713,7 +703,18 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } #endif - fill_device_info_for_device(handle, cur_dev, &desc); + struct hid_device_info *tmp = create_device_info_for_device(handle, &desc); + 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); if (res >= 0) libusb_close(handle); @@ -929,10 +930,9 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa struct libusb_device_descriptor desc; libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); - dev->device_info = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - dev->device_info->next = NULL; + dev->device_info = create_device_info_for_device(dev->device_handle, &desc); // dev->device_info->path is set by the caller - fill_device_info_for_device(dev->device_handle, dev->device_info, &desc); + dev->device_info->next = NULL; #ifdef DETACH_KERNEL_DRIVER /* Detach the kernel driver, but only if the From cd7349ea8581a3c3a7fe13f9060fca0d4b0e2110 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 1 Aug 2022 20:10:58 +0100 Subject: [PATCH 16/28] review comments --- libusb/hid.c | 134 ++++++++++++++++++++++++++------------------------- linux/hid.c | 12 ++--- 2 files changed, 74 insertions(+), 72 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 3ffaad34f..2a6b6fd77 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -563,83 +563,85 @@ static struct hid_device_info* create_device_info_for_device(libusb_device_handl /* Release Number */ cur_dev->release_number = desc->bcdDevice; - if (handle) { - /* 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); + if (!handle) { + return cur_dev; + } + + /* 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); #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]; + /* + 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; - } + 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); + 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) { - /* 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"); + 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("Can't claim interface %d\n", res); + 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"); - } + /* 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 */ } +#endif /* INVASIVE_GET_USAGE */ return cur_dev; } diff --git a/linux/hid.c b/linux/hid.c index e44443b22..ef443cfd9 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -495,7 +495,7 @@ parse_uevent_info(const char *uevent, unsigned *bus_type, } -static void get_device_info_for_device(struct udev_device *raw_dev, unsigned short vendor_id, unsigned short product_id, struct hid_device_info ** first_dev, struct hid_device_info ** last_dev) +static void create_device_info_for_device(struct udev_device *raw_dev, unsigned short vendor_id, unsigned short product_id, struct hid_device_info ** first_dev, struct hid_device_info ** last_dev) { struct hid_device_info *cur_dev = NULL; struct hid_device_info *prev_dev = NULL; /* previous device */ @@ -684,7 +684,7 @@ static void get_device_info_for_device(struct udev_device *raw_dev, unsigned sho *last_dev = cur_dev; } -struct hid_device_info* get_device_info_for_hid_device(hid_device *dev) { +struct hid_device_info* create_device_info_for_hid_device(hid_device *dev) { struct udev *udev; struct udev_device *udev_dev; struct stat s; @@ -711,7 +711,7 @@ struct hid_device_info* get_device_info_for_hid_device(hid_device *dev) { udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); if (udev_dev) { struct hid_device_info * tmp; - get_device_info_for_device(udev_dev, 0, 0, &root, &tmp); + create_device_info_for_device(udev_dev, 0, 0, &root, &tmp); } udev_device_unref(udev_dev); @@ -790,7 +790,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct hid_device_info * first_dev = NULL; struct hid_device_info * last_dev = NULL; - get_device_info_for_device(raw_dev, vendor_id, product_id, &first_dev, &last_dev); + create_device_info_for_device(raw_dev, vendor_id, product_id, &first_dev, &last_dev); if (first_dev) { if (cur_dev) { cur_dev->next = first_dev; @@ -1131,10 +1131,10 @@ HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_devi if (!dev->device_info) { // Lazy initialize device_info - dev->device_info = get_device_info_for_hid_device(dev); + dev->device_info = create_device_info_for_hid_device(dev); } - // get_device_info_for_hid_device will set an error if needed + // create_device_info_for_hid_device will set an error if needed return dev->device_info; } From e6241f940fe0bc5d6cae504f8bfcb07c1bb8dc0d Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sat, 6 Aug 2022 19:19:04 +0300 Subject: [PATCH 17/28] Explicit documentation NOTE --- hidapi/hidapi.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 4e9f24096..2410ff723 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -480,6 +480,8 @@ extern "C" { 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); From 79a77a719502f0b4e45451e5b2538b861251ef89 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sat, 6 Aug 2022 19:45:59 +0300 Subject: [PATCH 18/28] libusb: lazy initialisation for get_device_info --- libusb/hid.c | 134 +++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 2a6b6fd77..5eb3efa8c 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -146,14 +146,16 @@ 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; @@ -487,9 +489,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); @@ -502,7 +509,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 { @@ -510,6 +517,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); } @@ -552,51 +565,54 @@ int HID_API_EXPORT hid_exit(void) } -static struct hid_device_info* create_device_info_for_device(libusb_device_handle *handle, struct libusb_device_descriptor *desc) +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)); + 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; - /* Release Number */ cur_dev->release_number = desc->bcdDevice; + cur_dev->interface_number = interface_num; + if (!handle) { return cur_dev; } - - /* Serial Number */ + + 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); + 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); + cur_dev->manufacturer_string = get_usb_string(handle, desc->iManufacturer); if (desc->iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc->iProduct); + cur_dev->product_string = get_usb_string(handle, desc->iProduct); #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. */ + { + /* + 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; @@ -617,7 +633,7 @@ static struct hid_device_info* create_device_info_for_device(libusb_device_handl if (res >= 0) { unsigned short page=0, usage=0; /* Parse the usage and usage page - out of the report descriptor. */ + out of the report descriptor. */ get_usage(data, res, &page, &usage); cur_dev->usage_page = page; cur_dev->usage = usage; @@ -687,9 +703,8 @@ 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. */ res = libusb_open(dev, &handle); #ifdef __ANDROID__ @@ -705,7 +720,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } #endif - struct hid_device_info *tmp = create_device_info_for_device(handle, &desc); + tmp = create_device_info_for_device(handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); if (cur_dev) { cur_dev->next = tmp; } @@ -714,15 +729,8 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } cur_dev = tmp; - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); - if (res >= 0) libusb_close(handle); - - /* Interface Number */ - cur_dev->interface_number = interface_num; } } /* altsettings */ } /* interfaces */ @@ -925,17 +933,13 @@ 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; 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->device_info->path is set by the caller - dev->device_info->next = NULL; - #ifdef DETACH_KERNEL_DRIVER /* Detach the kernel driver, but only if the device is managed by the kernel */ @@ -963,7 +967,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; @@ -1037,7 +1042,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 */ @@ -1045,20 +1051,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); - - if (dev->device_info) { - dev->device_info->path = dev_path; - dev_path = NULL; - } - } - free(dev_path); } } } @@ -1133,13 +1131,9 @@ 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; - if (dev->device_info) { - dev->device_info->path = make_path(libusb_get_device(dev->device_handle), selected_intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); - } - return dev; err: @@ -1486,10 +1480,12 @@ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *s } HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_device *dev) { - if (!dev->device_info) - { - // register_device_error(dev, "NULL device/info"); - return NULL; + 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 } return dev->device_info; From acd478888a4d513f33185529db2b240e84a6fe6d Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sat, 6 Aug 2022 20:06:51 +0300 Subject: [PATCH 19/28] fix invasive detach build --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 5eb3efa8c..956dbae91 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -617,7 +617,7 @@ static struct hid_device_info * create_device_info_for_device(libusb_device_hand #ifdef DETACH_KERNEL_DRIVER int detached = 0; /* Usage Page and Usage */ - res = libusb_kernel_driver_active(handle, interface_num); + int res = libusb_kernel_driver_active(handle, interface_num); if (res == 1) { res = libusb_detach_kernel_driver(handle, interface_num); if (res < 0) From e48a44923cec9edc87d0fc7dc738a49904fd79cd Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 12:36:21 +0300 Subject: [PATCH 20/28] fix usage_page/usage fill --- libusb/hid.c | 173 +++++++++++++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 73 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 956dbae91..482a6ff92 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -227,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) @@ -327,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 @@ -564,7 +562,73 @@ 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[256]; + unsigned short page = 0, usage = 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) { + /* 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 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)); @@ -595,70 +659,6 @@ static struct hid_device_info * create_device_info_for_device(libusb_device_hand if (desc->iProduct > 0) cur_dev->product_string = get_usb_string(handle, desc->iProduct); -#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 */ - int 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"); - } -#endif - } -#endif /* INVASIVE_GET_USAGE */ - return cur_dev; } @@ -721,15 +721,38 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, #endif tmp = create_device_info_for_device(handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; + if (tmp) { +#ifdef INVASIVE_GET_USAGE + /* 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. */ + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber); +#endif /* INVASIVE_GET_USAGE */ + + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; } - cur_dev = tmp; - if (res >= 0) + if (res >= 0) libusb_close(handle); } } /* altsettings */ @@ -1486,6 +1509,10 @@ HID_API_EXPORT struct hid_device_info *HID_API_CALL hid_get_device_info(hid_devi 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; From 4471fcecd7dda8bdb9c07550e575e3a5c57e9323 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 12:37:32 +0300 Subject: [PATCH 21/28] compilation fix --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 482a6ff92..ada03c7af 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -571,7 +571,7 @@ static void fill_device_info_usage(struct hid_device_info * cur_dev, libusb_devi unsigned short page = 0, usage = 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); + 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. */ From e22cc65e97974db171e4a37beb5de0dabf26e93a Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 12:43:52 +0300 Subject: [PATCH 22/28] typo --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index ada03c7af..6194c848f 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -604,7 +604,7 @@ static void invasive_fill_device_info_usage(struct hid_device_info * cur_dev, li res = libusb_claim_interface(handle, interface_num); if (res >= 0) { - fill_device_info_usage(cur_dev, handle, interface_num) + fill_device_info_usage(cur_dev, handle, interface_num); /* Release the interface */ res = libusb_release_interface(handle, interface_num); From d5bfa6e1a4fc8c8f1ca3d9afd5105de4341cb14c Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 13:10:36 +0300 Subject: [PATCH 23/28] use MAX_REPORT_DESCRIPTOR_SIZE constant --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 6194c848f..5008fd251 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -567,7 +567,7 @@ int HID_API_EXPORT hid_exit(void) */ static void fill_device_info_usage(struct hid_device_info * cur_dev, libusb_device_handle *handle, int interface_num) { - unsigned char data[256]; + unsigned char data[4096]; unsigned short page = 0, usage = 0; /* Get the HID Report Descriptor. */ From 5bb05a9d1051ef2a88355862c034b8e2d01a9d06 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 13:11:30 +0300 Subject: [PATCH 24/28] correct woding --- libusb/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 5008fd251..6f94f29f0 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -578,7 +578,7 @@ static void fill_device_info_usage(struct hid_device_info * cur_dev, libusb_devi get_usage(data, res, &page, &usage); } else - LOG("libusb_control_transfer() for getting the HID descriptor failed with %d: %s\n", res, libusb_error_name(res)); + 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; From c7a8a71819bc581eb83bc097e158233a3126d79e Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 13:15:49 +0300 Subject: [PATCH 25/28] check for handle --- libusb/hid.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libusb/hid.c b/libusb/hid.c index 6f94f29f0..b26b415be 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -740,7 +740,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, optional. For composite devices, use the interface field in the hid_device_info struct to distinguish between interfaces. */ - invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber); + if (handle) { + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber); + } #endif /* INVASIVE_GET_USAGE */ if (cur_dev) { From 8d11ec51542be9a03c36ccf15f54507a79bf568b Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 16:09:47 +0300 Subject: [PATCH 26/28] code style --- libusb/hid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index b26b415be..bb007d5b5 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -565,7 +565,7 @@ int HID_API_EXPORT hid_exit(void) /** * 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) +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; @@ -585,7 +585,7 @@ static void fill_device_info_usage(struct hid_device_info * cur_dev, libusb_devi } #ifdef INVASIVE_GET_USAGE -static void invasive_fill_device_info_usage(struct hid_device_info * cur_dev, libusb_device_handle *handle, int interface_num) +static void invasive_fill_device_info_usage(struct hid_device_info *cur_dev, libusb_device_handle *handle, int interface_num) { int res = 0; From 02962507bf4e0bbe9f19247e3129be06a2dfa1c3 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 19:27:14 +0300 Subject: [PATCH 27/28] hidtest: print all of hid_get_device_info --- hidtest/test.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/hidtest/test.c b/hidtest/test.c index 2acfe2965..2fe62ea00 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -62,6 +62,13 @@ void print_device(struct hid_device_info *cur_dev) { 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; @@ -74,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)) { @@ -94,11 +101,7 @@ int main(int argc, char* argv[]) #endif devs = hid_enumerate(0x0, 0x0); - cur_dev = devs; - while (cur_dev) { - print_device(cur_dev); - cur_dev = cur_dev->next; - } + print_devices(devs); hid_free_enumeration(devs); // Set up the command buffer. @@ -142,7 +145,7 @@ int main(int argc, char* argv[]) if (info == NULL) { printf("Unable to get device info\n"); } else { - print_device(info); + print_devices(info); } // Read Indexed String 1 From 4779d63d87609e31b97457fdb18d05207871891c Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Sun, 7 Aug 2022 19:28:37 +0300 Subject: [PATCH 28/28] refactor hidraw create_device_info_for_hid_device --- linux/hid.c | 451 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 278 insertions(+), 173 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index ef443cfd9..ed1637dce 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -68,22 +68,6 @@ #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; @@ -423,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; @@ -437,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; @@ -490,15 +565,14 @@ 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 void create_device_info_for_device(struct udev_device *raw_dev, unsigned short vendor_id, unsigned short product_id, struct hid_device_info ** first_dev, struct hid_device_info ** last_dev) +static struct hid_device_info * create_device_info_for_device(struct udev_device *raw_dev) { + struct hid_device_info *root = NULL; struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ const char *sysfs_path; const char *dev_path; @@ -524,7 +598,7 @@ static void create_device_info_for_device(struct udev_device *raw_dev, unsigned if (!hid_dev) { /* Unable to find parent hid device. */ - goto next; + goto end; } result = parse_uevent_info( @@ -537,7 +611,7 @@ static void create_device_info_for_device(struct udev_device *raw_dev, unsigned if (!result) { /* parse_uevent_info() failed for at least one field. */ - goto next; + goto end; } /* Filter out unhandled devices right away */ @@ -548,148 +622,145 @@ static void create_device_info_for_device(struct udev_device *raw_dev, unsigned break; default: - goto next; + goto end; } - /* 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; - - /* VID/PID match. Create the record. */ - *first_dev = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev = *first_dev; - - /* 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; - } + /* Create the record. */ + root = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (!root) + goto end; - /* 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; - } + cur_dev = root; - break; + /* 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); - case BUS_BLUETOOTH: - case BUS_I2C: + /* 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; - - 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; + /* 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; } } - next: - free(serial_number_utf8); - free(product_name_utf8); - /* 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. */ +end: + free(serial_number_utf8); + free(product_name_utf8); - *last_dev = cur_dev; + return root; } -struct hid_device_info* create_device_info_for_hid_device(hid_device *dev) { +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; + struct hid_device_info *root = NULL; register_device_error(dev, NULL); @@ -710,8 +781,12 @@ struct hid_device_info* create_device_info_for_hid_device(hid_device *dev) { /* 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) { - struct hid_device_info * tmp; - create_device_info_for_device(udev_dev, 0, 0, &root, &tmp); + root = create_device_info_for_device(udev_dev); + } + + 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); @@ -781,24 +856,46 @@ 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; + 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 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); + if (!sysfs_path) + continue; + + if (vendor_id != 0 || product_id != 0) { + if (!parse_hid_vid_pid_from_sysfs(sysfs_path, &bus_type, &dev_vid, &dev_pid)) + continue; + + if (vendor_id != 0 && vendor_id != dev_vid) + continue; + if (product_id != 0 && product_id != dev_pid) + continue; + } + raw_dev = udev_device_new_from_syspath(udev, sysfs_path); + if (!raw_dev) + continue; - struct hid_device_info * first_dev = NULL; - struct hid_device_info * last_dev = NULL; - create_device_info_for_device(raw_dev, vendor_id, product_id, &first_dev, &last_dev); - if (first_dev) { + tmp = create_device_info_for_device(raw_dev); + if (tmp) { if (cur_dev) { - cur_dev->next = first_dev; + cur_dev->next = tmp; } else { - root = first_dev; + root = tmp; + } + cur_dev = tmp; + + /* move the pointer to the tail of returnd list */ + while (cur_dev->next != NULL) { + cur_dev = cur_dev->next; } - cur_dev = last_dev; } udev_device_unref(raw_dev); @@ -1065,71 +1162,79 @@ 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) { - if (!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) - { + 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'; + 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) { - if (!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) - { + 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'; + 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) { - if (!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) - { + 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'; + 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) - { + if (!dev->device_info) { // Lazy initialize device_info dev->device_info = create_device_info_for_hid_device(dev); }