diff --git a/hidapi/hidapi/hidapi.h b/hidapi/hidapi/hidapi.h index efba318..5b8d0ab 100644 --- a/hidapi/hidapi/hidapi.h +++ b/hidapi/hidapi/hidapi.h @@ -48,12 +48,12 @@ @ingroup API */ -#define HID_API_VERSION_MINOR 10 +#define HID_API_VERSION_MINOR 11 /** @brief Static/compile-time patch version of the library. @ingroup API */ -#define HID_API_VERSION_PATCH 1 +#define HID_API_VERSION_PATCH 0 /* Helper macros */ #define HID_API_AS_STR_IMPL(x) #x @@ -495,4 +495,3 @@ extern "C" { #endif #endif - diff --git a/hidapi/hidapi/hidapi_libusb.h b/hidapi/hidapi/hidapi_libusb.h new file mode 100644 index 0000000..1f56c2c --- /dev/null +++ b/hidapi/hidapi/hidapi_libusb.h @@ -0,0 +1,54 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2021, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_LIBUSB_H__ +#define HIDAPI_LIBUSB_H__ + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Open a HID device using libusb_wrap_sys_device. + See https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga98f783e115ceff4eaf88a60e6439563c, + for details on libusb_wrap_sys_device. + + @ingroup API + @param sys_dev Platform-specific file descriptor that can be recognised by libusb. + @param interface_num USB interface number of the device to be used as HID interface. + Pass -1 to select first HID interface of the device. + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hidapi/libusb/hid.c b/hidapi/libusb/hid.c index 0b8aa1e..756a591 100644 --- a/hidapi/libusb/hid.c +++ b/hidapi/libusb/hid.c @@ -49,7 +49,7 @@ #include #endif -#include "hidapi.h" +#include "hidapi_libusb.h" #if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ @@ -484,15 +484,29 @@ 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) +static char *make_path(libusb_device *dev, int interface_number, int config_number) { - char str[64]; - snprintf(str, sizeof(str), "%04x:%04x:%02x", - libusb_get_bus_number(dev), - libusb_get_device_address(dev), - interface_number); - str[sizeof(str)-1] = '\0'; - + char str[64]; /* max length "000-000.000.000.000.000.000.000:000.000" */ + /* 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); + + if (num_ports > 0) { + int n = snprintf(str, sizeof("000-000"), "%u-%u", libusb_get_bus_number(dev), port_numbers[0]); + for (uint8_t i = 1; i < num_ports; i++) { + n += snprintf(&str[n], sizeof(".000"), ".%u", port_numbers[i]); + } + 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 */ + if (num_ports == LIBUSB_ERROR_OVERFLOW) { + LOG("make_path() failed. buffer overflow error\n"); + } else { + LOG("make_path() failed. unknown error\n"); + } + str[0] = '\0'; + } return strdup(str); } @@ -555,12 +569,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, struct libusb_device_descriptor desc; struct libusb_config_descriptor *conf_desc = NULL; int j, k; - int interface_num = 0; int res = libusb_get_device_descriptor(dev, &desc); unsigned short dev_vid = desc.idVendor; unsigned short dev_pid = desc.idProduct; + if ((vendor_id != 0x0 && vendor_id != dev_vid) || + (product_id != 0x0 && product_id != dev_pid)) { + continue; + } + res = libusb_get_active_config_descriptor(dev, &conf_desc); if (res < 0) libusb_get_config_descriptor(dev, 0, &conf_desc); @@ -571,118 +589,124 @@ 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) { - interface_num = intf_desc->bInterfaceNumber; - - /* 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; + 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); + /* VID/PID match. Create the record. */ + tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); + + res = libusb_open(dev, &handle); + + if (res >= 0) { +#ifdef __ANDROID__ + /* 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_open(dev, &handle); + /* Serial Number */ + if (desc.iSerialNumber > 0) + cur_dev->serial_number = + get_usb_string(handle, desc.iSerialNumber); - if (res >= 0) { - /* 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); + /* 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."); - 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 */ - libusb_close(handle); - } - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; + 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; + /* Release Number */ + cur_dev->release_number = desc.bcdDevice; - /* Interface Number */ - cur_dev->interface_number = interface_num; - } + /* Interface Number */ + cur_dev->interface_number = interface_num; } } /* altsettings */ } /* interfaces */ @@ -885,13 +909,95 @@ static void *read_thread(void *param) } +static int hidapi_initialize_device(hid_device *dev, 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); + +#ifdef DETACH_KERNEL_DRIVER + /* Detach the kernel driver, but only if the + device is managed by the kernel */ + dev->is_driver_detached = 0; + if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { + res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + libusb_close(dev->device_handle); + LOG("Unable to detach Kernel Driver\n"); + return 0; + } + else { + dev->is_driver_detached = 1; + LOG("Driver successfully detached from kernel.\n"); + } + } +#endif + res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + return 0; + } + + /* Store off the string descriptor indexes */ + dev->manufacturer_index = desc.iManufacturer; + dev->product_index = desc.iProduct; + dev->serial_index = desc.iSerialNumber; + + /* Store off the interface number */ + dev->interface = intf_desc->bInterfaceNumber; + + dev->input_endpoint = 0; + dev->input_ep_max_packet_size = 0; + dev->output_endpoint = 0; + + /* Find the INPUT and OUTPUT endpoints. An + OUTPUT endpoint is not required. */ + for (i = 0; i < intf_desc->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *ep + = &intf_desc->endpoint[i]; + + /* Determine the type and direction of this + endpoint. */ + int is_interrupt = + (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT; + int is_output = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_OUT; + int is_input = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN; + + /* Decide whether to use it for input or output. */ + if (dev->input_endpoint == 0 && + is_interrupt && is_input) { + /* Use this endpoint for INPUT */ + dev->input_endpoint = ep->bEndpointAddress; + dev->input_ep_max_packet_size = ep->wMaxPacketSize; + } + if (dev->output_endpoint == 0 && + is_interrupt && is_output) { + /* Use this endpoint for OUTPUT */ + dev->output_endpoint = ep->bEndpointAddress; + } + } + + pthread_create(&dev->thread, NULL, read_thread, dev); + + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); + return 1; +} + + hid_device * HID_API_EXPORT hid_open_path(const char *path) { hid_device *dev = NULL; - libusb_device **devs; - libusb_device *usb_dev; - int res; + libusb_device **devs = NULL; + libusb_device *usb_dev = NULL; + int res = 0; int d = 0; int good_open = 0; @@ -901,21 +1007,18 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) dev = new_hid_device(); libusb_get_device_list(usb_context, &devs); - while ((usb_dev = devs[d++]) != NULL) { - struct libusb_device_descriptor desc; + while ((usb_dev = devs[d++]) != NULL && !good_open) { struct libusb_config_descriptor *conf_desc = NULL; - int i,j,k; - libusb_get_device_descriptor(usb_dev, &desc); + int j,k; if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) continue; - for (j = 0; j < conf_desc->bNumInterfaces; j++) { + for (j = 0; j < conf_desc->bNumInterfaces && !good_open; j++) { const struct libusb_interface *intf = &conf_desc->interface[j]; - for (k = 0; k < intf->num_altsetting; k++) { - const struct libusb_interface_descriptor *intf_desc; - intf_desc = &intf->altsetting[k]; + 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); + char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); if (!strcmp(dev_path, path)) { /* Matched Paths. Open this device */ @@ -926,87 +1029,15 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) free(dev_path); break; } - good_open = 1; -#ifdef DETACH_KERNEL_DRIVER - /* Detach the kernel driver, but only if the - device is managed by the kernel */ - dev->is_driver_detached = 0; - if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { - res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); - if (res < 0) { - libusb_close(dev->device_handle); - LOG("Unable to detach Kernel Driver\n"); - free(dev_path); - good_open = 0; - break; - } - else { - dev->is_driver_detached = 1; - LOG("Driver successfully detached from kernel.\n"); - } - } -#endif - res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); - if (res < 0) { - LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); - free(dev_path); + good_open = hidapi_initialize_device(dev, intf_desc); + if (!good_open) libusb_close(dev->device_handle); - good_open = 0; - break; - } - - /* Store off the string descriptor indexes */ - dev->manufacturer_index = desc.iManufacturer; - dev->product_index = desc.iProduct; - dev->serial_index = desc.iSerialNumber; - - /* Store off the interface number */ - dev->interface = intf_desc->bInterfaceNumber; - - /* Find the INPUT and OUTPUT endpoints. An - OUTPUT endpoint is not required. */ - for (i = 0; i < intf_desc->bNumEndpoints; i++) { - const struct libusb_endpoint_descriptor *ep - = &intf_desc->endpoint[i]; - - /* Determine the type and direction of this - endpoint. */ - int is_interrupt = - (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) - == LIBUSB_TRANSFER_TYPE_INTERRUPT; - int is_output = - (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_OUT; - int is_input = - (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_IN; - - /* Decide whether to use it for input or output. */ - if (dev->input_endpoint == 0 && - is_interrupt && is_input) { - /* Use this endpoint for INPUT */ - dev->input_endpoint = ep->bEndpointAddress; - dev->input_ep_max_packet_size = ep->wMaxPacketSize; - } - if (dev->output_endpoint == 0 && - is_interrupt && is_output) { - /* Use this endpoint for OUTPUT */ - dev->output_endpoint = ep->bEndpointAddress; - } - } - - pthread_create(&dev->thread, NULL, read_thread, dev); - - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); - } free(dev_path); } } } libusb_free_config_descriptor(conf_desc); - } libusb_free_device_list(devs, 1); @@ -1023,12 +1054,92 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) } +HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num) +{ +/* 0x01000107 is a LIBUSB_API_VERSION for 1.0.23 - version when libusb_wrap_sys_device was introduced */ +#if (!defined(HIDAPI_TARGET_LIBUSB_API_VERSION) || HIDAPI_TARGET_LIBUSB_API_VERSION >= 0x01000107) && (LIBUSB_API_VERSION >= 0x01000107) + hid_device *dev = NULL; + struct libusb_config_descriptor *conf_desc = NULL; + const struct libusb_interface_descriptor *selected_intf_desc = NULL; + int res = 0; + int j = 0, k = 0; + + if(hid_init() < 0) + return NULL; + + dev = new_hid_device(); + + res = libusb_wrap_sys_device(usb_context, sys_dev, &dev->device_handle); + if (res < 0) { + LOG("libusb_wrap_sys_device failed: %d %s\n", res, libusb_error_name(res)); + goto err; + } + + res = libusb_get_active_config_descriptor(libusb_get_device(dev->device_handle), &conf_desc); + if (res < 0) + libusb_get_config_descriptor(libusb_get_device(dev->device_handle), 0, &conf_desc); + + if (!conf_desc) { + LOG("Failed to get configuration descriptor: %d %s\n", res, libusb_error_name(res)); + goto err; + } + + /* find matching HID interface */ + for (j = 0; j < conf_desc->bNumInterfaces && !selected_intf_desc; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + if (interface_num < 0 || interface_num == intf_desc->bInterfaceNumber) { + selected_intf_desc = intf_desc; + break; + } + } + } + } + + if (!selected_intf_desc) { + if (interface_num < 0) { + LOG("Sys USB device doesn't contain a HID interface\n"); + } + else { + LOG("Sys USB device doesn't contain a HID interface with number %d\n", interface_num); + } + goto err; + } + + if (!hidapi_initialize_device(dev, selected_intf_desc)) + goto err; + + return dev; + +err: + if (conf_desc) + libusb_free_config_descriptor(conf_desc); + if (dev->device_handle) + libusb_close(dev->device_handle); + free_hid_device(dev); +#else + (void)sys_dev; + (void)interface_num; + LOG("libusb_wrap_sys_device is not available\n"); +#endif + return NULL; +} + + int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) { int res; - int report_number = data[0]; + int report_number; int skipped_report_id = 0; + if (!data || (length ==0)) { + return -1; + } + + report_number = data[0]; + if (report_number == 0x0) { data++; length--; diff --git a/hidapi/linux/hid.c b/hidapi/linux/hid.c index 718541e..4ee6032 100644 --- a/hidapi/linux/hid.c +++ b/hidapi/linux/hid.c @@ -45,6 +45,22 @@ #include "hidapi.h" +#ifdef HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39 +/* This definitions first appeared in Linux Kernel 2.6.39 in linux/hidraw.h. + hidapi doesn't support kernels older than that, + so we don't define macros below explicitly, to fail builds on old kernels. + For those who really need this as a workaround (e.g. to be able to build on old build machines), + can workaround by defining the macro above. +*/ +#ifndef HIDIOCSFEATURE +#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len) +#endif +#ifndef HIDIOCGFEATURE +#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len) +#endif + +#endif + /* USB HID device property names */ const char *device_string_names[] = { @@ -947,6 +963,12 @@ int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t { int bytes_written; + if (!data || (length == 0)) { + errno = EINVAL; + register_device_error(dev, strerror(errno)); + return -1; + } + bytes_written = write(dev->device_handle, data, length); register_device_error(dev, (bytes_written == -1)? strerror(errno): NULL); diff --git a/hidapi/mac/hid.c b/hidapi/mac/hid.c index d88a9ad..b95297b 100644 --- a/hidapi/mac/hid.c +++ b/hidapi/mac/hid.c @@ -193,7 +193,6 @@ static struct hid_api_version api_version = { static IOHIDManagerRef hid_mgr = 0x0; static int is_macos_10_10_or_greater = 0; - #if 0 static void register_error(hid_device *dev, const char *op) { @@ -314,64 +313,6 @@ static wchar_t *dup_wcs(const wchar_t *s) return ret; } -/* hidapi_IOHIDDeviceGetService() - * - * Return the io_service_t corresponding to a given IOHIDDeviceRef, either by: - * - on OS X 10.6 and above, calling IOHIDDeviceGetService() - * - on OS X 10.5, extract it from the IOHIDDevice struct - */ -static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device) -{ - static void *iokit_framework = NULL; - typedef io_service_t (*dynamic_IOHIDDeviceGetService_t)(IOHIDDeviceRef device); - static dynamic_IOHIDDeviceGetService_t dynamic_IOHIDDeviceGetService = NULL; - - /* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists. - * If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL - * and the fallback method will be used. - */ - if (iokit_framework == NULL) { - iokit_framework = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY); - - if (iokit_framework != NULL) - dynamic_IOHIDDeviceGetService = (dynamic_IOHIDDeviceGetService_t) dlsym(iokit_framework, "IOHIDDeviceGetService"); - } - - if (dynamic_IOHIDDeviceGetService != NULL) { - /* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */ - return dynamic_IOHIDDeviceGetService(device); - } - else - { - /* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist. - * - * Be naughty and pull the service out of the IOHIDDevice. - * IOHIDDevice is an opaque struct not exposed to applications, but its - * layout is stable through all available versions of OS X. - * Tested and working on OS X 10.5.8 i386, x86_64, and ppc. - */ - struct IOHIDDevice_internal { - /* The first field of the IOHIDDevice struct is a - * CFRuntimeBase (which is a private CF struct). - * - * a, b, and c are the 3 fields that make up a CFRuntimeBase. - * See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h - * - * The second field of the IOHIDDevice is the io_service_t we're looking for. - */ - uintptr_t a; - uint8_t b[4]; -#if __LP64__ - uint32_t c; -#endif - io_service_t service; - }; - struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *) device; - - return tmp->service; - } -} - /* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ static int init_hid_manager(void) { @@ -439,7 +380,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, struct hid_device_info *cur_dev; io_object_t iokit_dev; kern_return_t res; - io_string_t path; + uint64_t entry_id; if (dev == NULL) { return NULL; @@ -459,13 +400,30 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, /* Fill out the record */ cur_dev->next = NULL; - /* Fill in the path (IOService plane) */ - iokit_dev = hidapi_IOHIDDeviceGetService(dev); - res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path); - if (res == KERN_SUCCESS) - cur_dev->path = strdup(path); - else + /* Fill in the path (as a unique ID of the service entry) */ + cur_dev->path = NULL; + iokit_dev = IOHIDDeviceGetService(dev); + if (iokit_dev != MACH_PORT_NULL) { + res = IORegistryEntryGetRegistryEntryID(iokit_dev, &entry_id); + } + else { + res = KERN_INVALID_ARGUMENT; + } + + if (res == KERN_SUCCESS) { + /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, + so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need + 9+1+20+1=31 bytes byffer, but allocate 32 for simple alignment */ + cur_dev->path = calloc(1, 32); + if (cur_dev->path != NULL) { + sprintf(cur_dev->path, "DevSrvsID:%llu", entry_id); + } + } + + if (cur_dev->path == NULL) { + /* for whatever reason, trying to keep it a non-NULL string */ cur_dev->path = strdup(""); + } /* Serial Number */ get_serial_number(dev, buf, BUF_LEN); @@ -817,11 +775,33 @@ static void *read_thread(void *param) return NULL; } -/* hid_open_path() - * - * path must be a valid path to an IOHIDDevice in the IOService plane - * Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver" - */ +/* \p path must be one of: + - in format 'DevSrvsID:' (as returned by hid_enumerate); + - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath, + e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"); + Second format is for compatibility with paths accepted by older versions of HIDAPI. +*/ +static io_registry_entry_t hid_open_service_registry_from_path(const char *path) +{ + if (path == NULL) + return MACH_PORT_NULL; + + /* Get the IORegistry entry for the given path */ + if (strncmp("DevSrvsID:", path, 10) == 0) { + char *endptr; + uint64_t entry_id = strtoull(path + 10, &endptr, 10); + if (*endptr == '\0') { + return IOServiceGetMatchingService(kIOMainPortDefault, IORegistryEntryIDMatching(entry_id)); + } + } + else { + /* Fallback to older format of the path */ + return IORegistryEntryFromPath(kIOMainPortDefault, path); + } + + return MACH_PORT_NULL; +} + hid_device * HID_API_EXPORT hid_open_path(const char *path) { hid_device *dev = NULL; @@ -835,7 +815,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) dev = new_hid_device(); /* Get the IORegistry entry for the given path */ - entry = IORegistryEntryFromPath(kIOMasterPortDefault, path); + entry = hid_open_service_registry_from_path(path); if (entry == MACH_PORT_NULL) { /* Path wasn't valid (maybe device was removed?) */ goto return_error; @@ -898,7 +878,13 @@ static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char const unsigned char *data_to_send = data; CFIndex length_to_send = length; IOReturn res; - const unsigned char report_id = data[0]; + unsigned char report_id; + + if (!data || (length == 0)) { + return -1; + } + + report_id = data[0]; if (report_id == 0x0) { /* Not using numbered Reports. @@ -1116,7 +1102,7 @@ int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, } int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ +{ return get_report(dev, kIOHIDReportTypeInput, data, length); } @@ -1204,18 +1190,9 @@ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) { - (void) dev; - /* TODO: */ - return L"hid_error is not implemented yet"; } - - - - - - #if 0 static int32_t get_location_id(IOHIDDeviceRef device) { diff --git a/hidapi/windows/hid.c b/hidapi/windows/hid.c index 57cb78d..6d8394c 100755 --- a/hidapi/windows/hid.c +++ b/hidapi/windows/hid.c @@ -8,7 +8,7 @@ 8/22/2009 Copyright 2009, All Rights Reserved. - + At the discretion of the user of this library, this software may be licensed under the terms of the GNU General Public License v3, a BSD-Style license, or the @@ -20,6 +20,12 @@ https://github.com/libusb/hidapi . ********************************************************/ +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +// Do not warn about mbsrtowcs and wcsncpy usage. +// https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt +#define _CRT_SECURE_NO_WARNINGS +#endif + #include #ifndef _NTDEF_ @@ -27,6 +33,7 @@ typedef LONG NTSTATUS; #endif #ifdef __MINGW32__ +#include #include #include #endif @@ -63,18 +70,14 @@ extern "C" { #include #include - +#include +#include #include "hidapi.h" #undef MIN #define MIN(x,y) ((x) < (y)? (x): (y)) -#ifdef _MSC_VER - /* Thanks Microsoft, but I know how to use strncpy(). */ - #pragma warning(disable:4996) -#endif - #ifdef __cplusplus extern "C" { #endif @@ -110,6 +113,7 @@ static struct hid_api_version api_version = { typedef void* PHIDP_PREPARSED_DATA; #define HIDP_STATUS_SUCCESS 0x110000 + typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); @@ -123,6 +127,7 @@ static struct hid_api_version api_version = { typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps); typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); + static HidD_GetHidGuid_ HidD_GetHidGuid; static HidD_GetAttributes_ HidD_GetAttributes; static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; static HidD_GetManufacturerString_ HidD_GetManufacturerString; @@ -138,19 +143,51 @@ static struct hid_api_version api_version = { static HMODULE lib_handle = NULL; static BOOLEAN initialized = FALSE; + + typedef DWORD RETURN_TYPE; + typedef RETURN_TYPE CONFIGRET; + typedef DWORD DEVNODE, DEVINST; + typedef DEVNODE* PDEVNODE, * PDEVINST; + typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; + +#define CR_SUCCESS (0x00000000) +#define CR_BUFFER_SMALL (0x0000001A) + +#define CM_LOCATE_DEVNODE_NORMAL 0x00000000 + +#define DEVPROP_TYPEMOD_LIST 0x00002000 + +#define DEVPROP_TYPE_STRING 0x00000012 +#define DEVPROP_TYPE_STRING_LIST (DEVPROP_TYPE_STRING|DEVPROP_TYPEMOD_LIST) + + typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); + typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); + typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); + typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); + + static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; + static CM_Get_Parent_ CM_Get_Parent = NULL; + static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; + static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; + + static HMODULE cfgmgr32_lib_handle = NULL; #endif /* HIDAPI_USE_DDK */ struct hid_device_ { HANDLE device_handle; BOOL blocking; USHORT output_report_length; + unsigned char *write_buf; size_t input_report_length; + USHORT feature_report_length; + unsigned char *feature_buf; void *last_error_str; DWORD last_error_num; BOOL read_pending; char *read_buf; OVERLAPPED ol; - OVERLAPPED write_ol; + OVERLAPPED write_ol; + struct hid_device_info* device_info; }; static hid_device *new_hid_device() @@ -159,7 +196,10 @@ static hid_device *new_hid_device() dev->device_handle = INVALID_HANDLE_VALUE; dev->blocking = TRUE; dev->output_report_length = 0; + dev->write_buf = NULL; dev->input_report_length = 0; + dev->feature_report_length = 0; + dev->feature_buf = NULL; dev->last_error_str = NULL; dev->last_error_num = 0; dev->read_pending = FALSE; @@ -167,7 +207,8 @@ static hid_device *new_hid_device() memset(&dev->ol, 0, sizeof(dev->ol)); dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); memset(&dev->write_ol, 0, sizeof(dev->write_ol)); - dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); + dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); + dev->device_info = NULL; return dev; } @@ -175,10 +216,13 @@ static hid_device *new_hid_device() static void free_hid_device(hid_device *dev) { CloseHandle(dev->ol.hEvent); - CloseHandle(dev->write_ol.hEvent); + CloseHandle(dev->write_ol.hEvent); CloseHandle(dev->device_handle); LocalFree(dev->last_error_str); + free(dev->write_buf); + free(dev->feature_buf); free(dev->read_buf); + free(dev->device_info); free(dev); } @@ -194,13 +238,13 @@ static void register_error(hid_device *dev, const char *op) MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msg, 0/*sz*/, NULL); - + /* Get rid of the CR and LF that FormatMessage() sticks at the end of the message. Thanks Microsoft! */ ptr = msg; while (*ptr) { - if (*ptr == '\r') { - *ptr = 0x0000; + if (*ptr == L'\r') { + *ptr = L'\0'; break; } ptr++; @@ -222,6 +266,7 @@ static int lookup_functions() # pragma GCC diagnostic ignored "-Wcast-function-type" #endif #define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; + RESOLVE(HidD_GetHidGuid); RESOLVE(HidD_GetAttributes); RESOLVE(HidD_GetSerialNumberString); RESOLVE(HidD_GetManufacturerString); @@ -242,6 +287,29 @@ static int lookup_functions() else return -1; + cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); + if (cfgmgr32_lib_handle) { +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +#define RESOLVE(x) x = (x##_)GetProcAddress(cfgmgr32_lib_handle, #x); + RESOLVE(CM_Locate_DevNodeW); + RESOLVE(CM_Get_Parent); + RESOLVE(CM_Get_DevNode_PropertyW); + RESOLVE(CM_Get_Device_Interface_PropertyW); +#undef RESOLVE +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + } + else { + CM_Locate_DevNodeW = NULL; + CM_Get_Parent = NULL; + CM_Get_DevNode_PropertyW = NULL; + CM_Get_Device_Interface_PropertyW = NULL; + } + return 0; } #endif @@ -293,50 +361,269 @@ int HID_API_EXPORT hid_exit(void) if (lib_handle) FreeLibrary(lib_handle); lib_handle = NULL; + if (cfgmgr32_lib_handle) + FreeLibrary(cfgmgr32_lib_handle); + cfgmgr32_lib_handle = NULL; initialized = FALSE; #endif return 0; } +static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) +{ + ULONG len; + CONFIGRET cr; + DEVPROPTYPE property_type; + + static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac }, 10 }; // DEVPROP_TYPE_STRING + static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 1 }; // DEVPROP_TYPE_STRING + static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 4 }; // DEVPROP_TYPE_STRING + + /* Manufacturer String */ + len = 0; + cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, NULL, &len, 0); + if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + free(dev->manufacturer_string); + dev->manufacturer_string = (wchar_t*)calloc(len, sizeof(BYTE)); + CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, (PBYTE)dev->manufacturer_string, &len, 0); + } + + /* Serial Number String (MAC Address) */ + len = 0; + cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, NULL, &len, 0); + if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + free(dev->serial_number); + dev->serial_number = (wchar_t*)calloc(len, sizeof(BYTE)); + CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, (PBYTE)dev->serial_number, &len, 0); + } + + /* Get devnode grandparent to reach out Bluetooth LE device node */ + cr = CM_Get_Parent(&dev_node, dev_node, 0); + if (cr != CR_SUCCESS) + return; + + /* Product String */ + len = 0; + cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, NULL, &len, 0); + if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + free(dev->product_string); + dev->product_string = (wchar_t*)calloc(len, sizeof(BYTE)); + CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, (PBYTE)dev->product_string, &len, 0); + } +} + +static void hid_internal_get_info(struct hid_device_info* dev) +{ + const char *tmp = NULL; + wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL; + mbstate_t state; + ULONG len; + CONFIGRET cr; + DEVPROPTYPE property_type; + DEVINST dev_node; + + static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57 }, 256 }; // DEVPROP_TYPE_STRING + static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 4 }; // DEVPROP_TYPE_STRING_LIST + + if (!CM_Get_Device_Interface_PropertyW || + !CM_Locate_DevNodeW || + !CM_Get_Parent || + !CM_Get_DevNode_PropertyW) + goto end; + + tmp = dev->path; + + len = (ULONG)strlen(tmp); + interface_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t)); + memset(&state, 0, sizeof(state)); + + if (mbsrtowcs(interface_path, &tmp, len, &state) == (size_t)-1) + goto end; + + /* Get the device id from interface path */ + len = 0; + cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, NULL, &len, 0); + if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { + device_id = (wchar_t*)calloc(len, sizeof(BYTE)); + cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, (PBYTE)device_id, &len, 0); + } + if (cr != CR_SUCCESS) + goto end; + + /* Open devnode from device id */ + cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); + if (cr != CR_SUCCESS) + goto end; + + /* Get devnode parent */ + cr = CM_Get_Parent(&dev_node, dev_node, 0); + if (cr != CR_SUCCESS) + goto end; + + /* Get the compatible ids from parent devnode */ + len = 0; + cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, NULL, &len, 0); + if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) { + compatible_ids = (wchar_t*)calloc(len, sizeof(BYTE)); + cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, (PBYTE)compatible_ids, &len, 0); + } + if (cr != CR_SUCCESS) + goto end; + + /* Now we can parse parent's compatible IDs to find out the device bus type */ + for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) { + /* Normalize to upper case */ + for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); + + /* Bluetooth LE devices */ + if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { + /* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices + Request this info via dev node properties instead. + https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */ + hid_internal_get_ble_info(dev, dev_node); + break; + } + } +end: + free(interface_path); + free(device_id); + free(compatible_ids); +} + +static struct hid_device_info *hid_get_device_info(const char *path, HANDLE handle) +{ + struct hid_device_info *dev = NULL; /* return object */ + + BOOL res; + HIDD_ATTRIBUTES attrib; + PHIDP_PREPARSED_DATA pp_data = NULL; + HIDP_CAPS caps; + + #define WSTR_LEN 512 + wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ + + /* Create the record. */ + dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); + + /* Fill out the record */ + dev->next = NULL; + + if (path) { + size_t len = strlen(path); + dev->path = (char*)calloc(len + 1, sizeof(char)); + memcpy(dev->path, path, len + 1); + } + else + dev->path = NULL; + + attrib.Size = sizeof(HIDD_ATTRIBUTES); + res = HidD_GetAttributes(handle, &attrib); + if (res) { + /* VID/PID */ + dev->vendor_id = attrib.VendorID; + dev->product_id = attrib.ProductID; + + /* Release Number */ + dev->release_number = attrib.VersionNumber; + } + + /* Get the Usage Page and Usage for this device. */ + res = HidD_GetPreparsedData(handle, &pp_data); + if (res) { + NTSTATUS nt_res = HidP_GetCaps(pp_data, &caps); + if (nt_res == HIDP_STATUS_SUCCESS) { + dev->usage_page = caps.UsagePage; + dev->usage = caps.Usage; + } + + HidD_FreePreparsedData(pp_data); + } + + /* Serial Number */ + wstr[0] = L'\0'; + res = HidD_GetSerialNumberString(handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN - 1] = L'\0'; + dev->serial_number = _wcsdup(wstr); + + /* Manufacturer String */ + wstr[0] = L'\0'; + res = HidD_GetManufacturerString(handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN - 1] = L'\0'; + dev->manufacturer_string = _wcsdup(wstr); + + /* Product String */ + wstr[0] = L'\0'; + res = HidD_GetProductString(handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN - 1] = L'\0'; + dev->product_string = _wcsdup(wstr); + + /* Interface Number. It can sometimes be parsed out of the path + on Windows if a device has multiple interfaces. See + https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections + or search for "HIDClass Hardware IDs for Top-Level Collections" at Microsoft Docs. If it's not + in the path, it's set to -1. */ + dev->interface_number = -1; + if (dev->path) { + char* interface_component = strstr(dev->path, "&mi_"); + if (interface_component) { + char* hex_str = interface_component + 4; + char* endptr = NULL; + dev->interface_number = strtol(hex_str, &endptr, 16); + if (endptr == hex_str) { + /* The parsing failed. Set interface_number to -1. */ + dev->interface_number = -1; + } + } + } + + hid_internal_get_info(dev); + + return dev; +} + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) { BOOL res; struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; + GUID interface_class_guid; /* Windows objects for interacting with the driver. */ - GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} }; SP_DEVINFO_DATA devinfo_data; SP_DEVICE_INTERFACE_DATA device_interface_data; SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; HDEVINFO device_info_set = INVALID_HANDLE_VALUE; + char driver_name[256]; int device_index = 0; - int i; if (hid_init() < 0) return NULL; + /* Retrieve HID Interface Class GUID + https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ + HidD_GetHidGuid(&interface_class_guid); + /* Initialize the Windows objects. */ memset(&devinfo_data, 0x0, sizeof(devinfo_data)); devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); /* Get information for all the devices belonging to the HID class. */ - device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - + device_info_set = SetupDiGetClassDevsA(&interface_class_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + /* Iterate over each device in the HID class, looking for the right one. */ - + for (;;) { - HANDLE write_handle = INVALID_HANDLE_VALUE; + HANDLE read_handle = INVALID_HANDLE_VALUE; DWORD required_size = 0; HIDD_ATTRIBUTES attrib; res = SetupDiEnumDeviceInterfaces(device_info_set, NULL, - &InterfaceClassGuid, + &interface_class_guid, device_index, &device_interface_data); - + if (!res) { /* A return of FALSE from this function means that there are no more devices. */ @@ -373,49 +660,34 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor goto cont; } - /* Make sure this device is of Setup Class "HIDClass" and has a - driver bound to it. */ - for (i = 0; ; i++) { - char driver_name[256]; - - /* Populate devinfo_data. This function will return failure - when there are no more interfaces left. */ - res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data); - if (!res) - goto cont; - - res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, - SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); - if (!res) - goto cont; - - if ((strcmp(driver_name, "HIDClass") == 0) || - (strcmp(driver_name, "Mouse") == 0) || - (strcmp(driver_name, "Keyboard") == 0)) { - /* See if there's a driver bound. */ - res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, - SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); - if (res) - break; - } - } + /* Populate devinfo_data. This function will return failure + when the device with such index doesn't exist. We've already checked it does. */ + res = SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data); + if (!res) + goto cont; + + + /* Make sure this device has a driver bound to it. */ + res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, + SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); + if (!res) + goto cont; //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); - /* Open a handle to the device */ - write_handle = open_device(device_interface_detail_data->DevicePath, FALSE); + /* Open read-only handle to the device */ + read_handle = open_device(device_interface_detail_data->DevicePath, FALSE); - /* Check validity of write_handle. */ - if (write_handle == INVALID_HANDLE_VALUE) { + /* Check validity of read_handle. */ + if (read_handle == INVALID_HANDLE_VALUE) { /* Unable to open the device. */ //register_error(dev, "CreateFile"); - goto cont_close; - } - + goto cont; + } /* Get the Vendor ID and Product ID for this device. */ attrib.Size = sizeof(HIDD_ATTRIBUTES); - HidD_GetAttributes(write_handle, &attrib); + HidD_GetAttributes(read_handle, &attrib); //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); /* Check the VID/PID to see if we should add this @@ -423,17 +695,13 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && (product_id == 0x0 || attrib.ProductID == product_id)) { - #define WSTR_LEN 512 - const char *str; - struct hid_device_info *tmp; - PHIDP_PREPARSED_DATA pp_data = NULL; - HIDP_CAPS caps; - NTSTATUS nt_res; - wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ - size_t len; - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + struct hid_device_info *tmp = hid_get_device_info(device_interface_detail_data->DevicePath, read_handle); + + if (tmp == NULL) { + goto cont_close; + } + if (cur_dev) { cur_dev->next = tmp; } @@ -441,84 +709,10 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor root = tmp; } cur_dev = tmp; - - /* Get the Usage Page and Usage for this device. */ - res = HidD_GetPreparsedData(write_handle, &pp_data); - if (res) { - nt_res = HidP_GetCaps(pp_data, &caps); - if (nt_res == HIDP_STATUS_SUCCESS) { - cur_dev->usage_page = caps.UsagePage; - cur_dev->usage = caps.Usage; - } - - HidD_FreePreparsedData(pp_data); - } - - /* Fill out the record */ - cur_dev->next = NULL; - str = device_interface_detail_data->DevicePath; - if (str) { - len = strlen(str); - cur_dev->path = (char*) calloc(len+1, sizeof(char)); - strncpy(cur_dev->path, str, len+1); - cur_dev->path[len] = '\0'; - } - else - cur_dev->path = NULL; - - /* Serial Number */ - wstr[0]= 0x0000; - res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN-1] = 0x0000; - if (res) { - cur_dev->serial_number = _wcsdup(wstr); - } - - /* Manufacturer String */ - wstr[0]= 0x0000; - res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN-1] = 0x0000; - if (res) { - cur_dev->manufacturer_string = _wcsdup(wstr); - } - - /* Product String */ - wstr[0]= 0x0000; - res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN-1] = 0x0000; - if (res) { - cur_dev->product_string = _wcsdup(wstr); - } - - /* VID/PID */ - cur_dev->vendor_id = attrib.VendorID; - cur_dev->product_id = attrib.ProductID; - - /* Release Number */ - cur_dev->release_number = attrib.VersionNumber; - - /* Interface Number. It can sometimes be parsed out of the path - on Windows if a device has multiple interfaces. See - http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or - search for "Hardware IDs for HID Devices" at MSDN. If it's not - in the path, it's set to -1. */ - cur_dev->interface_number = -1; - if (cur_dev->path) { - char *interface_component = strstr(cur_dev->path, "&mi_"); - if (interface_component) { - char *hex_str = interface_component + 4; - char *endptr = NULL; - cur_dev->interface_number = strtol(hex_str, &endptr, 16); - if (endptr == hex_str) { - /* The parsing failed. Set interface_number to -1. */ - cur_dev->interface_number = -1; - } - } - } } cont_close: - CloseHandle(write_handle); + CloseHandle(read_handle); cont: /* We no longer need the detail data. It can be freed */ free(device_interface_detail_data); @@ -531,7 +725,6 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor SetupDiDestroyDeviceInfoList(device_info_set); return root; - } void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) @@ -556,7 +749,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi struct hid_device_info *devs, *cur_dev; const char *path_to_open = NULL; hid_device *handle = NULL; - + devs = hid_enumerate(vendor_id, product_id); cur_dev = devs; while (cur_dev) { @@ -582,7 +775,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsi } hid_free_enumeration(devs); - + return handle; } @@ -635,20 +828,23 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) } nt_res = HidP_GetCaps(pp_data, &caps); if (nt_res != HIDP_STATUS_SUCCESS) { - register_error(dev, "HidP_GetCaps"); + register_error(dev, "HidP_GetCaps"); goto err_pp_data; } dev->output_report_length = caps.OutputReportByteLength; dev->input_report_length = caps.InputReportByteLength; + dev->feature_report_length = caps.FeatureReportByteLength; HidD_FreePreparsedData(pp_data); dev->read_buf = (char*) malloc(dev->input_report_length); + dev->device_info = hid_get_device_info(path, dev->device_handle); + return dev; err_pp_data: HidD_FreePreparsedData(pp_data); -err: +err: free_hid_device(dev); return NULL; } @@ -662,26 +858,31 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * unsigned char *buf; + if (!data || (length==0)) { + register_error(dev, "Zero length buffer"); + return function_result; + } + /* Make sure the right number of bytes are passed to WriteFile. Windows expects the number of bytes which are in the _longest_ report (plus one for the report number) bytes even if the data is a report which is shorter than that. Windows gives us this value in caps.OutputReportByteLength. If a user passes in fewer bytes than this, - create a temporary buffer which is the proper size. */ + use cached temporary buffer which is the proper size. */ if (length >= dev->output_report_length) { /* The user passed the right number of bytes. Use the buffer as-is. */ buf = (unsigned char *) data; } else { - /* Create a temporary buffer and copy the user's data - into it, padding the rest with zeros. */ - buf = (unsigned char *) malloc(dev->output_report_length); + if (dev->write_buf == NULL) + dev->write_buf = (unsigned char *) malloc(dev->output_report_length); + buf = dev->write_buf; memcpy(buf, data, length); memset(buf + length, 0, dev->output_report_length - length); length = dev->output_report_length; } res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol); - + if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* WriteFile() failed. Return error. */ @@ -714,9 +915,6 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char * } end_of_function: - if (buf != data) - free(buf); - return function_result; } @@ -737,7 +935,7 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char memset(dev->read_buf, 0, dev->input_report_length); ResetEvent(ev); res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); - + if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* ReadFile() has failed. @@ -746,11 +944,11 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char dev->read_pending = FALSE; goto end_of_function; } - overlapped = TRUE; - } + overlapped = TRUE; + } } else { - overlapped = TRUE; + overlapped = TRUE; } if (overlapped) { @@ -788,13 +986,13 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char memcpy(data, dev->read_buf, copy_len); } } - + end_of_function: if (!res) { register_error(dev, "GetOverlappedResult"); return -1; } - + return (int) copy_len; } @@ -811,7 +1009,29 @@ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonbloc int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) { - BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, (DWORD) length); + BOOL res = FALSE; + unsigned char *buf; + size_t length_to_send; + + /* Windows expects at least caps.FeatureReportByteLength bytes passed + to HidD_SetFeature(), even if the report is shorter. Any less sent and + the function fails with error ERROR_INVALID_PARAMETER set. Any more + and HidD_SetFeature() silently truncates the data sent in the report + to caps.FeatureReportByteLength. */ + if (length >= dev->feature_report_length) { + buf = (unsigned char *) data; + length_to_send = length; + } else { + if (dev->feature_buf == NULL) + dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length); + buf = dev->feature_buf; + memcpy(buf, data, length); + memset(buf + length, 0, dev->feature_report_length - length); + length_to_send = dev->feature_report_length; + } + + res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); + if (!res) { register_error(dev, "HidD_SetFeature"); return -1; @@ -820,25 +1040,16 @@ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const u return (int) length; } - -int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length) { BOOL res; -#if 0 - res = HidD_GetFeature(dev->device_handle, data, length); - if (!res) { - register_error(dev, "HidD_GetFeature"); - return -1; - } - return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */ -#else - DWORD bytes_returned; + DWORD bytes_returned = 0; OVERLAPPED ol; memset(&ol, 0, sizeof(ol)); res = DeviceIoControl(dev->device_handle, - IOCTL_HID_GET_FEATURE, + report_type, data, (DWORD) length, data, (DWORD) length, &bytes_returned, &ol); @@ -846,7 +1057,7 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned if (!res) { if (GetLastError() != ERROR_IO_PENDING) { /* DeviceIoControl() failed. Return error. */ - register_error(dev, "Send Feature Report DeviceIoControl"); + register_error(dev, "Get Input/Feature Report DeviceIoControl"); return -1; } } @@ -856,66 +1067,30 @@ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); if (!res) { /* The operation failed. */ - register_error(dev, "Send Feature Report GetOverLappedResult"); + register_error(dev, "Get Input/Feature Report GetOverLappedResult"); return -1; } - /* bytes_returned does not include the first byte which contains the - report ID. The data buffer actually contains one more byte than - bytes_returned. */ - bytes_returned++; + /* When numbered reports aren't used, + bytes_returned seem to include only what is actually received from the device + (not including the first byte with 0, as an indication "no numbered reports"). */ + if (data[0] == 0x0) { + bytes_returned++; + } return bytes_returned; -#endif } +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + /* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */ + return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length); +} int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) { - BOOL res; -#if 0 - res = HidD_GetInputReport(dev->device_handle, data, length); - if (!res) { - register_error(dev, "HidD_GetInputReport"); - return -1; - } - return length; -#else - DWORD bytes_returned; - - OVERLAPPED ol; - memset(&ol, 0, sizeof(ol)); - - res = DeviceIoControl(dev->device_handle, - IOCTL_HID_GET_INPUT_REPORT, - data, (DWORD) length, - data, (DWORD) length, - &bytes_returned, &ol); - - if (!res) { - if (GetLastError() != ERROR_IO_PENDING) { - /* DeviceIoControl() failed. Return error. */ - register_error(dev, "Send Input Report DeviceIoControl"); - return -1; - } - } - - /* Wait here until the write is done. This makes - hid_get_feature_report() synchronous. */ - res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); - if (!res) { - /* The operation failed. */ - register_error(dev, "Send Input Report GetOverLappedResult"); - return -1; - } - - /* bytes_returned does not include the first byte which contains the - report ID. The data buffer actually contains one more byte than - bytes_returned. */ - bytes_returned++; - - return bytes_returned; -#endif + /* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */ + return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length); } void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) @@ -928,39 +1103,33 @@ 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) { - BOOL res; - - res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetManufacturerString"); + if (!dev->device_info || !string || !maxlen) return -1; - } + + wcsncpy(string, dev->device_info->manufacturer_string, maxlen); + string[maxlen] = L'\0'; return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) { - BOOL res; - - res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetProductString"); + if (!dev->device_info || !string || !maxlen) return -1; - } + + wcsncpy(string, dev->device_info->product_string, maxlen); + string[maxlen] = L'\0'; return 0; } int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) { - BOOL res; - - res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetSerialNumberString"); + if (!dev->device_info || !string || !maxlen) return -1; - } + + wcsncpy(string, dev->device_info->serial_number, maxlen); + string[maxlen] = L'\0'; return 0; } @@ -995,7 +1164,7 @@ HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) /*#define PICPGM*/ /*#define S11*/ #define P32 -#ifdef S11 +#ifdef S11 unsigned short VendorID = 0xa0a0; unsigned short ProductID = 0x0001; #endif @@ -1025,7 +1194,7 @@ int __cdecl main(int argc, char* argv[]) memset(buf,0x00,sizeof(buf)); buf[0] = 0; buf[1] = 0x81; - + /* Open the device. */ int handle = open(VendorID, ProductID, L"12345");