From 417e8801be1e9c6ca3bf2091efb33149862a14ec Mon Sep 17 00:00:00 2001 From: Martin Gysel Date: Fri, 22 Nov 2024 10:30:01 +0100 Subject: [PATCH] add hidapi's connection-callback branch this is basically https://github.com/libusb/hidapi/pull/674 up to https://github.com/libusb/hidapi@1b0b6acce5505aaa66b550f648c7662a03a53f7e --- hidapi/AUTHORS.txt | 2 +- hidapi/README.md | 2 +- hidapi/hidapi.h | 106 ++ hidapi/libusb/hid.c | 1043 +++++++++++++---- hidapi/libusb/hidapi_thread_pthread.h | 174 +++ hidapi/linux/hid.c | 545 ++++++++- hidapi/mac/hid.c | 528 ++++++++- hidapi/windows/hid.c | 538 ++++++++- hidapi/windows/hidapi_cfgmgr32.h | 67 ++ .../windows/hidapi_descriptor_reconstruct.c | 12 +- .../windows/hidapi_descriptor_reconstruct.h | 13 +- hidapi/windows/hidapi_hidsdi.h | 2 - hidapi/windows/hidapi_winapi.h | 2 +- 13 files changed, 2716 insertions(+), 318 deletions(-) create mode 100644 hidapi/libusb/hidapi_thread_pthread.h diff --git a/hidapi/AUTHORS.txt b/hidapi/AUTHORS.txt index 7193d71..7c2a035 100644 --- a/hidapi/AUTHORS.txt +++ b/hidapi/AUTHORS.txt @@ -11,7 +11,7 @@ Ludovic Rousseau : Correctness fixes libusb/hidapi Team: - Development/maintainance since June 4th 2019 + Development/maintenance since June 4th 2019 For a comprehensive list of contributions, see the commit list at github: https://github.com/libusb/hidapi/graphs/contributors diff --git a/hidapi/README.md b/hidapi/README.md index 257b9f3..45e1736 100644 --- a/hidapi/README.md +++ b/hidapi/README.md @@ -2,7 +2,7 @@ | CI instance | Status | |----------------------|--------| -| `Linux/macOS/Windows (master)` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | +| `Linux/macOS/Windows (master)` | [![GitHub Builds](https://github.com/libusb/hidapi/actions/workflows/builds.yml/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | | `Windows (master)` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) | | `BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) | | `Coverity Scan (last)` | ![Coverity Scan](https://scan.coverity.com/projects/583/badge.svg) | diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 744ceb0..1e8b5f2 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -254,6 +254,112 @@ extern "C" { */ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + /** @brief Callback handle. + + Callbacks handles are generated by hid_hotplug_register_callback() + and can be used to deregister callbacks. Callback handles are unique + and it is safe to call hid_hotplug_deregister_callback() on + an already deregistered callback. + + @ingroup API + */ + typedef int hid_hotplug_callback_handle; + + /** + Hotplug events + + @ingroup API + */ + typedef enum { + /** A device has been plugged in and is ready to use */ + HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0), + + /** A device has left and is no longer available. + It is the user's responsibility to call hid_close with a disconnected device. + */ + HID_API_HOTPLUG_EVENT_DEVICE_LEFT = (1 << 1) + } hid_hotplug_event; + + /** + Hotplug flags + + @ingroup API + */ + typedef enum { + /** Arm the callback and fire it for all matching currently attached devices. */ + HID_API_HOTPLUG_ENUMERATE = (1 << 0) + } hid_hotplug_flag; + + /** @brief Hotplug callback function type. When requesting hotplug event notifications, + you pass a pointer to a callback function of this type. + + This callback may be called by an internal event thread and as such it is + recommended the callback do minimal processing before returning. + + hidapi will call this function later, when a matching event had happened on + a matching device. + + Note that when callbacks are called from hid_hotplug_register_callback() + because of the \ref HID_API_HOTPLUG_ENUMERATE flag, the callback return + value is ignored. In other words, you cannot cause a callback to be + deregistered by returning 1 when it is called from hid_hotplug_register_callback(). + + @ingroup API + + @param callback_handle The hid_hotplug_callback_handle callback handle. + @param device The hid_device_info of device this event occurred on event that occurred. + @param event Event that occurred. + @param user_data User data provided when this callback was registered. + (Optionally NULL). + + @returns bool + Whether this callback is finished processing events. + Returning non-zero value will cause this callback to be deregistered. + */ + typedef int (HID_API_CALL *hid_hotplug_callback_fn)( + hid_hotplug_callback_handle callback_handle, + struct hid_device_info *device, + hid_hotplug_event event, + void *user_data); + + /** @brief Register a HID hotplug callback function. + + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then all HID devices will be notified. + + @ingroup API + + @param vendor_id The Vendor ID (VID) of the types of device to notify about. + @param product_id The Product ID (PID) of the types of device to notify about. + @param events Bitwise or of hotplug events that will trigger this callback. + See \ref hid_hotplug_event. + @param flags Bitwise or of hotplug flags that affect registration. + See \ref hid_hotplug_flag. + @param callback The callback function that will be called on device connection/disconnection. + See \ref hid_hotplug_callback_fn. + @param user_data The user data you wanted to provide to your callback function. + @param callback_handle Pointer to store the handle of the allocated callback + (Optionally NULL). + + @returns + This function returns 0 on success or -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle); + + /** @brief Deregister a callback from a HID hotplug. + + This function is safe to call from within a hotplug callback. + + @ingroup API + + @param callback_handle The handle of the callback to deregister. + + @returns + This function returns 0 on success or -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle); + /** @brief Open a HID device using a Vendor ID (VID), Product ID (PID) and optionally a serial number. diff --git a/hidapi/libusb/hid.c b/hidapi/libusb/hid.c index 4ac3f86..ca8555c 100644 --- a/hidapi/libusb/hid.c +++ b/hidapi/libusb/hid.c @@ -37,7 +37,6 @@ #include #include #include -#include #include /* GNU / LibUSB */ @@ -55,66 +54,14 @@ #include "hidapi_libusb.h" -#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ - -/* Barrier implementation because Android/Bionic don't have pthread_barrier. - This implementation came from Brent Priddy and was posted on - StackOverflow. It is used with his permission. */ -typedef int pthread_barrierattr_t; -typedef struct pthread_barrier { - pthread_mutex_t mutex; - pthread_cond_t cond; - int count; - int trip_count; -} pthread_barrier_t; - -static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) -{ - if(count == 0) { - errno = EINVAL; - return -1; - } - - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { - return -1; - } - if(pthread_cond_init(&barrier->cond, 0) < 0) { - pthread_mutex_destroy(&barrier->mutex); - return -1; - } - barrier->trip_count = count; - barrier->count = 0; - - return 0; -} - -static int pthread_barrier_destroy(pthread_barrier_t *barrier) -{ - pthread_cond_destroy(&barrier->cond); - pthread_mutex_destroy(&barrier->mutex); - return 0; -} - -static int pthread_barrier_wait(pthread_barrier_t *barrier) -{ - pthread_mutex_lock(&barrier->mutex); - ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { - barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); - pthread_mutex_unlock(&barrier->mutex); - return 1; - } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); - pthread_mutex_unlock(&barrier->mutex); - return 0; - } -} - +#ifndef HIDAPI_THREAD_MODEL_INCLUDE +#define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_pthread.h" #endif +#include HIDAPI_THREAD_MODEL_INCLUDE + +/* The value of the first callback handle to be given upon registration */ +/* Can be any arbitrary positive integer */ +#define FIRST_HOTPLUG_CALLBACK_HANDLE 1 #ifdef __cplusplus extern "C" { @@ -176,10 +123,7 @@ struct hid_device_ { int blocking; /* boolean */ /* Read thread objects */ - pthread_t thread; - pthread_mutex_t mutex; /* Protects input_reports */ - pthread_cond_t condition; - pthread_barrier_t barrier; /* Ensures correct startup sequence */ + hidapi_thread_state thread_state; int shutdown_thread; int transfer_loop_finished; struct libusb_transfer *transfer; @@ -201,6 +145,51 @@ static struct hid_api_version api_version = { static libusb_context *usb_context = NULL; +struct hid_hotplug_queue { + libusb_device* device; + int event; /* Arrived or removed */ + struct hid_hotplug_queue* next; +}; + +static struct hid_hotplug_context { + /* A separate libusb context for hotplug events: helps avoid mutual blocking with read_thread's */ + libusb_context * context; + + /* libusb callback handle */ + libusb_hotplug_callback_handle callback_handle; + + /* HIDAPI unique callback handle counter */ + hid_hotplug_callback_handle next_handle; + + /* A thread that fills the event queue */ + hidapi_thread_state libusb_thread; + + /* A separate thread which processes hidapi's internal event queue */ + hidapi_thread_state callback_thread; + + /* This mutex prevents changes to the callback list */ + pthread_mutex_t mutex; + + /* Boolean flags */ + unsigned char mutex_ready; + unsigned char mutex_in_use; + unsigned char cb_list_dirty; + + struct hid_hotplug_queue* queue; + + /* Linked list of the hotplug callbacks */ + struct hid_hotplug_callback *hotplug_cbs; + + /* Linked list of the device infos (mandatory when the device is disconnected) */ + struct hid_device_info *devs; +} hid_hotplug_context = { + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, + .mutex_ready = 0, + .queue = NULL, + .hotplug_cbs = NULL, + .devs = NULL, +}; + uint16_t get_usb_code_for_current_locale(void); static int return_data(hid_device *dev, unsigned char *data, size_t length); @@ -209,9 +198,7 @@ static hid_device *new_hid_device(void) hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); dev->blocking = 1; - pthread_mutex_init(&dev->mutex, NULL); - pthread_cond_init(&dev->condition, NULL); - pthread_barrier_init(&dev->barrier, NULL, 2); + hidapi_thread_state_init(&dev->thread_state); return dev; } @@ -219,9 +206,7 @@ static hid_device *new_hid_device(void) static void free_hid_device(hid_device *dev) { /* Clean up the thread objects */ - pthread_barrier_destroy(&dev->barrier); - pthread_cond_destroy(&dev->condition); - pthread_mutex_destroy(&dev->mutex); + hidapi_thread_state_destroy(&dev->thread_state); hid_free_enumeration(dev->device_info); @@ -322,8 +307,16 @@ static int get_usage(uint8_t *report_descriptor, size_t size, //printf("Usage Page: %x\n", (uint32_t)*usage_page); } if (key_cmd == 0x8) { - *usage = get_bytes(report_descriptor, size, data_len, i); - usage_found = 1; + if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ + *usage_page = get_bytes(report_descriptor, size, 2, i + 2); + usage_page_found = 1; + *usage = get_bytes(report_descriptor, size, 2, i); + usage_found = 1; + } + else { + *usage = get_bytes(report_descriptor, size, data_len, i); + usage_found = 1; + } //printf("Usage: %x\n", (uint32_t)*usage); } @@ -544,6 +537,103 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) return HID_API_VERSION_STR; } +struct hid_hotplug_callback +{ + unsigned short vendor_id; + unsigned short product_id; + hid_hotplug_callback_fn callback; + void* user_data; + int events; + struct hid_hotplug_callback* next; + + hid_hotplug_callback_handle handle; +}; + +static void hid_internal_hotplug_remove_postponed() +{ + /* Unregister the callbacks whose removal was postponed */ + /* This function is always called inside a locked mutex */ + /* However, any actions are only allowed if the mutex is NOT in use and if the DIRTY flag is set */ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use || !hid_hotplug_context.cb_list_dirty) { + return; + } + + /* Traverse the list of callbacks and check if any were marked for removal */ + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if (!callback->events) { + *current = (*current)->next; + free(callback); + continue; + } + current = &callback->next; + } + + /* Clear the flag so we don't start the cycle unless necessary */ + hid_hotplug_context.cb_list_dirty = 0; +} + +static void hid_internal_hotplug_cleanup() +{ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use) { + return; + } + + /* Before checking if the list is empty, clear any entries whose removal was postponed first */ + hid_internal_hotplug_remove_postponed(); + + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; + } + + /* Wait for both threads to stop */ + hidapi_thread_join(&hid_hotplug_context.libusb_thread); +} + +static void hid_internal_hotplug_init() +{ + if (!hid_hotplug_context.mutex_ready) { + hidapi_thread_state_init(&hid_hotplug_context.libusb_thread); + hidapi_thread_state_init(&hid_hotplug_context.callback_thread); + + /* Initialize the mutex as recursive */ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&hid_hotplug_context.mutex, &attr); + pthread_mutexattr_destroy(&attr); + + /* Set state to Ready */ + hid_hotplug_context.mutex_ready = 1; + hid_hotplug_context.mutex_in_use = 0; + hid_hotplug_context.cb_list_dirty = 0; + } +} + +static void hid_internal_hotplug_exit() +{ + if (!hid_hotplug_context.mutex_ready) { + return; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + /* Remove all callbacks from the list */ + while (*current) { + struct hid_hotplug_callback* next = (*current)->next; + free(*current); + *current = next; + } + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_ready = 0; + pthread_mutex_destroy(&hid_hotplug_context.mutex); + + hidapi_thread_state_destroy(&hid_hotplug_context.callback_thread); + hidapi_thread_state_destroy(&hid_hotplug_context.libusb_thread); +} + int HID_API_EXPORT hid_init(void) { if (!usb_context) { @@ -567,11 +657,17 @@ int HID_API_EXPORT hid_exit(void) if (usb_context) { libusb_exit(usb_context); usb_context = NULL; + hid_internal_hotplug_exit(); } return 0; } +static int hid_internal_match_device_id(unsigned short vendor_id, unsigned short product_id, unsigned short expected_vendor_id, unsigned short expected_product_id) +{ + return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id); +} + static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int interface_num, uint16_t expected_report_descriptor_size, unsigned char *buf, size_t buf_size) { unsigned char tmp[HID_API_MAX_REPORT_DESCRIPTOR_SIZE]; @@ -580,7 +676,7 @@ static int hid_get_report_descriptor_libusb(libusb_device_handle *handle, int in expected_report_descriptor_size = HID_API_MAX_REPORT_DESCRIPTOR_SIZE; /* Get the HID Report Descriptor. - See USB HID Specificatin, sectin 7.1.1 + See USB HID Specification, section 7.1.1 */ int res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8), interface_num, tmp, expected_report_descriptor_size, 5000); if (res < 0) { @@ -747,108 +843,234 @@ static uint16_t get_report_descriptor_size_from_interface_descriptors(const stru return result; } -struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc) { - libusb_device **devs; - libusb_device *dev; - libusb_device_handle *handle = NULL; - ssize_t num_devs; - int i = 0; + static const int xb360_iface_subclass = 93; + static const int xb360_iface_protocol = 1; /* Wired */ + static const int xb360w_iface_protocol = 129; /* Wireless */ + static const int supported_vendors[] = { + 0x0079, /* GPD Win 2 */ + 0x044f, /* Thrustmaster */ + 0x045e, /* Microsoft */ + 0x046d, /* Logitech */ + 0x056e, /* Elecom */ + 0x06a3, /* Saitek */ + 0x0738, /* Mad Catz */ + 0x07ff, /* Mad Catz */ + 0x0e6f, /* PDP */ + 0x0f0d, /* Hori */ + 0x1038, /* SteelSeries */ + 0x11c9, /* Nacon */ + 0x12ab, /* Unknown */ + 0x1430, /* RedOctane */ + 0x146b, /* BigBen */ + 0x1532, /* Razer Sabertooth */ + 0x15e4, /* Numark */ + 0x162e, /* Joytech */ + 0x1689, /* Razer Onza */ + 0x1949, /* Lab126, Inc. */ + 0x1bad, /* Harmonix */ + 0x20d6, /* PowerA */ + 0x24c6, /* PowerA */ + 0x2c22, /* Qanba */ + 0x2dc8, /* 8BitDo */ + 0x9886, /* ASTRO Gaming */ + }; + + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && + intf_desc->bInterfaceSubClass == xb360_iface_subclass && + (intf_desc->bInterfaceProtocol == xb360_iface_protocol || + intf_desc->bInterfaceProtocol == xb360w_iface_protocol)) { + size_t i; + for (i = 0; i < sizeof(supported_vendors)/sizeof(supported_vendors[0]); ++i) { + if (vendor_id == supported_vendors[i]) { + return 1; + } + } + } + return 0; +} + +static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc) +{ + static const int xb1_iface_subclass = 71; + static const int xb1_iface_protocol = 208; + static const int supported_vendors[] = { + 0x044f, /* Thrustmaster */ + 0x045e, /* Microsoft */ + 0x0738, /* Mad Catz */ + 0x0e6f, /* PDP */ + 0x0f0d, /* Hori */ + 0x10f5, /* Turtle Beach */ + 0x1532, /* Razer Wildcat */ + 0x20d6, /* PowerA */ + 0x24c6, /* PowerA */ + 0x2dc8, /* 8BitDo */ + 0x2e24, /* Hyperkin */ + 0x3537, /* GameSir */ + }; + + if (intf_desc->bInterfaceNumber == 0 && + intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && + intf_desc->bInterfaceSubClass == xb1_iface_subclass && + intf_desc->bInterfaceProtocol == xb1_iface_protocol) { + size_t i; + for (i = 0; i < sizeof(supported_vendors)/sizeof(supported_vendors[0]); ++i) { + if (vendor_id == supported_vendors[i]) { + return 1; + } + } + } + return 0; +} + +static int should_enumerate_interface(unsigned short vendor_id, const struct libusb_interface_descriptor *intf_desc) +{ +#if 0 + printf("Checking interface 0x%x %d/%d/%d/%d\n", vendor_id, intf_desc->bInterfaceNumber, intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol); +#endif + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) + return 1; + + /* Also enumerate Xbox 360 controllers */ + if (is_xbox360(vendor_id, intf_desc)) + return 1; + + /* Also enumerate Xbox One controllers */ + if (is_xboxone(vendor_id, intf_desc)) + return 1; + + return 0; +} + +static struct hid_device_info* hid_enumerate_from_libusb(libusb_device *dev, unsigned short vendor_id, unsigned short product_id) +{ struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + libusb_device_handle *handle = NULL; + int j, k; - if(hid_init() < 0) - return NULL; - - num_devs = libusb_get_device_list(usb_context, &devs); - if (num_devs < 0) + int res = libusb_get_device_descriptor(dev, &desc); + if (res < 0) return NULL; - while ((dev = devs[i++]) != NULL) { - struct libusb_device_descriptor desc; - struct libusb_config_descriptor *conf_desc = NULL; - int j, k; - int res = libusb_get_device_descriptor(dev, &desc); - unsigned short dev_vid = desc.idVendor; - unsigned short dev_pid = desc.idProduct; + 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; - } + if ((vendor_id != 0x0 && vendor_id != dev_vid) || + (product_id != 0x0 && product_id != dev_pid)) { + return NULL; + } - res = libusb_get_active_config_descriptor(dev, &conf_desc); - if (res < 0) - libusb_get_config_descriptor(dev, 0, &conf_desc); - if (conf_desc) { - for (j = 0; j < conf_desc->bNumInterfaces; 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]; - if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - struct hid_device_info *tmp; - - res = libusb_open(dev, &handle); + res = libusb_get_active_config_descriptor(dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(dev, 0, &conf_desc); + if (conf_desc) { + for (j = 0; j < conf_desc->bNumInterfaces; 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]; + if (should_enumerate_interface(dev_vid, intf_desc)) { + struct hid_device_info *tmp; + 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 - tmp = create_device_info_for_device(dev, handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); - if (tmp) { + tmp = create_device_info_for_device(dev, handle, &desc, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); + 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. */ - if (handle) { - uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); - - invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size); - } -#endif /* 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. */ + if (handle) { + uint16_t report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; + invasive_fill_device_info_usage(tmp, handle, intf_desc->bInterfaceNumber, report_descriptor_size); } +#endif /* INVASIVE_GET_USAGE */ - if (res >= 0) { - libusb_close(handle); - handle = NULL; + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; } + cur_dev = tmp; + } + + if (res >= 0) { + libusb_close(handle); + handle = NULL; } - } /* altsettings */ - } /* interfaces */ - libusb_free_config_descriptor(conf_desc); + break; + } + } /* altsettings */ + } /* interfaces */ + libusb_free_config_descriptor(conf_desc); + } + return root; +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + libusb_device **devs; + libusb_device *dev; + ssize_t num_devs; + int i = 0; + + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + + if (hid_init() < 0) + return NULL; + + num_devs = libusb_get_device_list(usb_context, &devs); + if (num_devs < 0) + return NULL; + + while ((dev = devs[i++]) != NULL) { + struct hid_device_info *tmp = hid_enumerate_from_libusb(dev, vendor_id, product_id); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + cur_dev = tmp; + } + /* Traverse to the end of newly attached tail */ + if (cur_dev) { + while (cur_dev->next) { + cur_dev = cur_dev->next; + } } } @@ -871,6 +1093,346 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +static int match_libusb_to_info(libusb_device *device, struct hid_device_info* info) +{ + /* make a path from this libusb device, but leave the last 2 fields as 0 */ + char pseudo_path[64]; + get_path(&pseudo_path, device, 0, 0); + int len = strlen(pseudo_path) - sizeof("0.0"); + /* If the path on this HID device matches the template, aside from the last 2 fields, */ + /* we assume the HID device is located on this libusb device */ + return !strncmp(info->path, pseudo_path, len); +} + +static void hid_internal_invoke_callbacks(struct hid_device_info* info, hid_hotplug_event event) +{ + pthread_mutex_lock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_in_use = 1; + + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if ((callback->events & event) && hid_internal_match_device_id(info->vendor_id, info->product_id, callback->vendor_id, callback->product_id)) { + int result = callback->callback(callback->handle, info, event, callback->user_data); + /* If the result is non-zero, we mark the callback for removal and proceed */ + if (result) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + continue; + } + } + current = &callback->next; + } + + hid_hotplug_context.mutex_in_use = 0; + hid_internal_hotplug_remove_postponed(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); +} + +static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void * user_data) +{ + (void)ctx; + (void)user_data; + + /* Make sure we HOLD the device until we are done with it - otherwise libusb would delete it the moment we exit this function */ + libusb_ref_device(device); + + struct hid_hotplug_queue* msg = calloc(1, sizeof(struct hid_hotplug_queue)); + if (NULL == msg) { + return 0; + } + + msg->device = device; + msg->event = event; + msg->next = NULL; + + /* We use this thread's mutex to protect the queue */ + hidapi_thread_mutex_lock(&hid_hotplug_context.libusb_thread); + struct hid_hotplug_queue* end = hid_hotplug_context.queue; + if (end) { + while (end->next) { + end = end->next; + } + end->next = msg; + } else { + hid_hotplug_context.queue = msg; + } + hidapi_thread_mutex_unlock(&hid_hotplug_context.libusb_thread); + + /* Wake up the other thread so it can react to the new message immediately */ + hidapi_thread_cond_signal(&hid_hotplug_context.callback_thread); + + return 0; +} + +static void process_hotplug_event(struct hid_hotplug_queue* msg) +{ + if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + struct hid_device_info* info = hid_enumerate_from_libusb(msg->device, 0, 0); + struct hid_device_info* info_cur = info; + while (info_cur) { + /* For each device, call all matching callbacks */ + /* TODO: possibly make the `next` field NULL to match the behavior on other systems */ + hid_internal_invoke_callbacks(info_cur, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED); + info_cur = info_cur->next; + } + + /* Append all we got to the end of the device list */ + if (info) { + if (hid_hotplug_context.devs != NULL) { + struct hid_device_info* last = hid_hotplug_context.devs; + while (last->next != NULL) { + last = last->next; + } + last->next = info; + } + else { + hid_hotplug_context.devs = info; + } + } + } + else if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + for (struct hid_device_info **current = &hid_hotplug_context.devs; *current;) { + struct hid_device_info* info = *current; + if (match_libusb_to_info(msg->device, *current)) { + /* If the libusb device that's left matches this HID device, we detach it from the list */ + *current = (*current)->next; + info->next = NULL; + hid_internal_invoke_callbacks(info, HID_API_HOTPLUG_EVENT_DEVICE_LEFT); + /* Free every removed device */ + free(info); + } else { + current = &info->next; + } + } + } + + /* Release the libusb device - we are done with it */ + libusb_unref_device(msg->device); + + /* Cleanup note: this function is called inside a thread that the clenup function would be waiting to finish */ + /* Any callbacks that await removal are removed in hid_internal_invoke_callbacks */ + /* No further cleaning is needed */ +} + +static void* callback_thread(void* user_data) +{ + (void) user_data; + + /* 5 msec timeout seems reasonable; don't set too low to avoid high CPU usage */ + /* This timeout only affects how much time it takes to stop the thread */ + hidapi_timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 5000000; + + hidapi_thread_mutex_lock(&hid_hotplug_context.callback_thread); + + /* We stop the thread if by the moment there are no events left in the queue there are no callbacks left */ + while (1) { + /* Make the tread fall asleep and wait for a condition to wake it up */ + hidapi_thread_cond_timedwait(&hid_hotplug_context.callback_thread, &ts); + + /* We use this thread's mutex to protect the queue */ + hidapi_thread_mutex_lock(&hid_hotplug_context.libusb_thread); + while (hid_hotplug_context.queue) { + struct hid_hotplug_queue *cur_event = hid_hotplug_context.queue; + process_hotplug_event(cur_event); + + /* Empty the queue */ + cur_event = cur_event->next; + free(hid_hotplug_context.queue); + hid_hotplug_context.queue = cur_event; + } + hidapi_thread_mutex_unlock(&hid_hotplug_context.libusb_thread); + if (!hid_hotplug_context.hotplug_cbs) { + break; + } + } + + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + + hidapi_thread_mutex_unlock(&hid_hotplug_context.callback_thread); + + return NULL; +} + +static void* hotplug_thread(void* user_data) +{ + (void) user_data; + + hidapi_thread_create(&hid_hotplug_context.callback_thread, callback_thread, NULL); + + /* 5 msec timeout seems reasonable; don't set too low to avoid high CPU usage */ + /* This timeout only affects how much time it takes to stop the thread */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 5000; + + while (hid_hotplug_context.hotplug_cbs) { + /* This will allow libusb to call the callbacks, which will fill up the queue */ + libusb_handle_events_timeout_completed(hid_hotplug_context.context, &tv, NULL); + } + + /* Disarm the libusb listener */ + libusb_hotplug_deregister_callback(hid_hotplug_context.context, hid_hotplug_context.callback_handle); + libusb_exit(hid_hotplug_context.context); + + /* Forcibly wake up the thread so it can shut down immediately and wait for it to stop */ + hidapi_thread_cond_signal(&hid_hotplug_context.callback_thread); + + hidapi_thread_join(&hid_hotplug_context.callback_thread); + + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle) +{ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + return -1; + } + + /* Check params */ + if (events == 0 + || (events & ~(HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT)) + || (flags & ~(HID_API_HOTPLUG_ENUMERATE)) + || callback == NULL) { + return -1; + } + + struct hid_hotplug_callback* hotplug_cb = (struct hid_hotplug_callback*)calloc(1, sizeof(struct hid_hotplug_callback)); + + if (hotplug_cb == NULL) { + return -1; + } + + /* Fill out the record */ + hotplug_cb->next = NULL; + hotplug_cb->vendor_id = vendor_id; + hotplug_cb->product_id = product_id; + hotplug_cb->events = events; + hotplug_cb->user_data = user_data; + hotplug_cb->callback = callback; + + /* Ensure we are ready to actually use the mutex */ + hid_internal_hotplug_init(); + + /* Lock the mutex to avoid race itions */ + pthread_mutex_lock(&hid_hotplug_context.mutex); + + hotplug_cb->handle = hid_hotplug_context.next_handle++; + + /* handle the unlikely case of handle overflow */ + if (hid_hotplug_context.next_handle < 0) + { + hid_hotplug_context.next_handle = 1; + } + + /* Return allocated handle */ + if (callback_handle != NULL) { + *callback_handle = hotplug_cb->handle; + } + /* Append a new callback to the end */ + if (hid_hotplug_context.hotplug_cbs != NULL) { + struct hid_hotplug_callback *last = hid_hotplug_context.hotplug_cbs; + while (last->next != NULL) { + last = last->next; + } + last->next = hotplug_cb; + } + else { + /* Fill already connected devices so we can use this info in disconnection notification */ + if (libusb_init(&hid_hotplug_context.context)) { + free(hotplug_cb); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + hid_hotplug_context.devs = hid_enumerate(0, 0); + hid_hotplug_context.hotplug_cbs = hotplug_cb; + + /* Arm a global callback to receive ALL notifications for HID class devices */ + if (libusb_hotplug_register_callback(hid_hotplug_context.context, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, &hid_libusb_hotplug_callback, NULL, + &hid_hotplug_context.callback_handle)) { + /* Major malfunction, failed to register a callback */ + libusb_exit(hid_hotplug_context.context); + free(hotplug_cb); + hid_hotplug_context.hotplug_cbs = NULL; + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + /* Initialization succeeded! We run the threads now */ + hidapi_thread_create(&hid_hotplug_context.libusb_thread, hotplug_thread, NULL); + } + + /* Mark the mutex as IN USE, to prevent callback removal from inside a callback */ + unsigned char old_state = hid_hotplug_context.mutex_in_use; + hid_hotplug_context.mutex_in_use = 1; + + if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { + struct hid_device_info* device = hid_hotplug_context.devs; + /* Notify about already connected devices, if asked so */ + while (device != NULL) { + if (hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { + (*hotplug_cb->callback)(hotplug_cb->handle, device, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, hotplug_cb->user_data); + } + + device = device->next; + } + } + + hid_hotplug_context.mutex_in_use = old_state; + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) || !hid_hotplug_context.mutex_ready || callback_handle <= 0) { + return -1; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + + if (hid_hotplug_context.hotplug_cbs == NULL) { + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + int result = -1; + + /* Remove this notification */ + for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { + if ((*current)->handle == callback_handle) { + /* Check if we were already in a locked state, as we are NOT allowed to remove any callbacks if we are */ + if (hid_hotplug_context.mutex_in_use) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + } else { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + } + result = 0; + break; + } + } + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return result; +} + hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { struct hid_device_info *devs, *cur_dev; @@ -907,7 +1469,7 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const return handle; } -static void read_callback(struct libusb_transfer *transfer) +static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer) { hid_device *dev = transfer->user_data; int res; @@ -920,13 +1482,13 @@ static void read_callback(struct libusb_transfer *transfer) rpt->len = transfer->actual_length; rpt->next = NULL; - pthread_mutex_lock(&dev->mutex); + hidapi_thread_mutex_lock(&dev->thread_state); /* Attach the new report object to the end of the list. */ if (dev->input_reports == NULL) { /* The list is empty. Put it at the root. */ dev->input_reports = rpt; - pthread_cond_signal(&dev->condition); + hidapi_thread_cond_signal(&dev->thread_state); } else { /* Find the end of the list and attach. */ @@ -945,7 +1507,7 @@ static void read_callback(struct libusb_transfer *transfer) return_data(dev, NULL, 0); } } - pthread_mutex_unlock(&dev->mutex); + hidapi_thread_mutex_unlock(&dev->thread_state); } else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { dev->shutdown_thread = 1; @@ -997,14 +1559,14 @@ static void *read_thread(void *param) /* Make the first submission. Further submissions are made from inside read_callback() */ res = libusb_submit_transfer(dev->transfer); - if(res < 0) { - LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res)); - dev->shutdown_thread = 1; - dev->transfer_loop_finished = 1; + if (res < 0) { + LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res)); + dev->shutdown_thread = 1; + dev->transfer_loop_finished = 1; } /* Notify the main thread that the read thread is up and running. */ - pthread_barrier_wait(&dev->barrier); + hidapi_thread_barrier_wait(&dev->thread_state); /* Handle all the events. */ while (!dev->shutdown_thread) { @@ -1036,23 +1598,86 @@ static void *read_thread(void *param) make sure that a thread which is about to go to sleep waiting on the condition actually will go to sleep before the condition is signaled. */ - pthread_mutex_lock(&dev->mutex); - pthread_cond_broadcast(&dev->condition); - pthread_mutex_unlock(&dev->mutex); + hidapi_thread_mutex_lock(&dev->thread_state); + hidapi_thread_cond_broadcast(&dev->thread_state); + hidapi_thread_mutex_unlock(&dev->thread_state); /* The dev->transfer->buffer and dev->transfer objects are cleaned up in hid_close(). They are not cleaned up here because this thread could end either due to a disconnect or due to a user call to hid_close(). In both cases the objects can be safely - cleaned up after the call to pthread_join() (in hid_close()), but + cleaned up after the call to hidapi_thread_join() (in hid_close()), but since hid_close() calls libusb_cancel_transfer(), on these objects, they can not be cleaned up here. */ return NULL; } +static void init_xbox360(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, const struct libusb_config_descriptor *conf_desc) +{ + (void)conf_desc; + + if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || + idVendor == 0x0f0d /* Hori Xbox controllers */) { + unsigned char data[20]; + + /* The HORIPAD FPS for Nintendo Switch requires this to enable input reports. + This VID/PID is also shared with other HORI controllers, but they all seem + to be fine with this as well. + */ + memset(data, 0, sizeof(data)); + libusb_control_transfer(device_handle, 0xC1, 0x01, 0x100, 0x0, data, sizeof(data), 100); + } +} -static int hidapi_initialize_device(hid_device *dev, int config_number, const struct libusb_interface_descriptor *intf_desc) +static void init_xboxone(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, const struct libusb_config_descriptor *conf_desc) +{ + static const int vendor_microsoft = 0x045e; + static const int xb1_iface_subclass = 71; + static const int xb1_iface_protocol = 208; + int j, k, res; + + (void)idProduct; + + for (j = 0; j < conf_desc->bNumInterfaces; 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_VENDOR_SPEC && + intf_desc->bInterfaceSubClass == xb1_iface_subclass && + intf_desc->bInterfaceProtocol == xb1_iface_protocol) { + int bSetAlternateSetting = 0; + + /* Newer Microsoft Xbox One controllers have a high speed alternate setting */ + if (idVendor == vendor_microsoft && + intf_desc->bInterfaceNumber == 0 && intf_desc->bAlternateSetting == 1) { + bSetAlternateSetting = 1; + } else if (intf_desc->bInterfaceNumber != 0 && intf_desc->bAlternateSetting == 0) { + bSetAlternateSetting = 1; + } + + if (bSetAlternateSetting) { + res = libusb_claim_interface(device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + continue; + } + + LOG("Setting alternate setting for VID/PID 0x%x/0x%x interface %d to %d\n", idVendor, idProduct, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting); + + res = libusb_set_interface_alt_setting(device_handle, intf_desc->bInterfaceNumber, intf_desc->bAlternateSetting); + if (res < 0) { + LOG("xbox init: can't set alt setting %d: %d\n", intf_desc->bInterfaceNumber, res); + } + + libusb_release_interface(device_handle, intf_desc->bInterfaceNumber); + } + } + } + } +} + +static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc, const struct libusb_config_descriptor *conf_desc) { int i =0; int res = 0; @@ -1089,13 +1714,23 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st return 0; } + /* Initialize XBox 360 controllers */ + if (is_xbox360(desc.idVendor, intf_desc)) { + init_xbox360(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc); + } + + /* Initialize XBox One controllers */ + if (is_xboxone(desc.idVendor, intf_desc)) { + init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc); + } + /* Store off the string descriptor indexes */ dev->manufacturer_index = desc.iManufacturer; dev->product_index = desc.iProduct; dev->serial_index = desc.iSerialNumber; /* Store off the USB information */ - dev->config_number = config_number; + dev->config_number = conf_desc->bConfigurationValue; dev->interface = intf_desc->bInterfaceNumber; dev->report_descriptor_size = get_report_descriptor_size_from_interface_descriptors(intf_desc); @@ -1114,13 +1749,13 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st endpoint. */ int is_interrupt = (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) - == LIBUSB_TRANSFER_TYPE_INTERRUPT; + == LIBUSB_TRANSFER_TYPE_INTERRUPT; int is_output = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_OUT; + == LIBUSB_ENDPOINT_OUT; int is_input = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_IN; + == LIBUSB_ENDPOINT_IN; /* Decide whether to use it for input or output. */ if (dev->input_endpoint == 0 && @@ -1136,10 +1771,10 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st } } - pthread_create(&dev->thread, NULL, read_thread, dev); + hidapi_thread_create(&dev->thread_state, read_thread, dev); /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); + hidapi_thread_barrier_wait(&dev->thread_state); return 1; } @@ -1154,23 +1789,32 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) int d = 0; int good_open = 0; - if(hid_init() < 0) + if (hid_init() < 0) return NULL; dev = new_hid_device(); libusb_get_device_list(usb_context, &devs); while ((usb_dev = devs[d++]) != NULL && !good_open) { + struct libusb_device_descriptor desc; struct libusb_config_descriptor *conf_desc = NULL; int j,k; - if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) + res = libusb_get_device_descriptor(usb_dev, &desc); + if (res < 0) + continue; + + res = libusb_get_active_config_descriptor(usb_dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(usb_dev, 0, &conf_desc); + if (!conf_desc) continue; + 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 && !good_open; k++) { const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; - if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + if (should_enumerate_interface(desc.idVendor, intf_desc)) { char dev_path[64]; get_path(&dev_path, usb_dev, conf_desc->bConfigurationValue, intf_desc->bInterfaceNumber); if (!strcmp(dev_path, path)) { @@ -1182,7 +1826,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) LOG("can't open device\n"); break; } - good_open = hidapi_initialize_device(dev, conf_desc->bConfigurationValue, intf_desc); + good_open = hidapi_initialize_device(dev, intf_desc, conf_desc); if (!good_open) libusb_close(dev->device_handle); } @@ -1216,7 +1860,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys int res = 0; int j = 0, k = 0; - if(hid_init() < 0) + if (hid_init() < 0) return NULL; dev = new_hid_device(); @@ -1260,7 +1904,7 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys goto err; } - if (!hidapi_initialize_device(dev, conf_desc->bConfigurationValue, selected_intf_desc)) + if (!hidapi_initialize_device(dev, selected_intf_desc, conf_desc)) goto err; return dev; @@ -1355,7 +1999,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) static void cleanup_mutex(void *param) { hid_device *dev = param; - pthread_mutex_unlock(&dev->mutex); + hidapi_thread_mutex_unlock(&dev->thread_state); } @@ -1371,8 +2015,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t /* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */ int bytes_read; /* = -1; */ - pthread_mutex_lock(&dev->mutex); - pthread_cleanup_push(&cleanup_mutex, dev); + hidapi_thread_mutex_lock(&dev->thread_state); + hidapi_thread_cleanup_push(cleanup_mutex, dev); bytes_read = -1; @@ -1393,7 +2037,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t if (milliseconds == -1) { /* Blocking */ while (!dev->input_reports && !dev->shutdown_thread) { - pthread_cond_wait(&dev->condition, &dev->mutex); + hidapi_thread_cond_wait(&dev->thread_state); } if (dev->input_reports) { bytes_read = return_data(dev, data, length); @@ -1402,17 +2046,12 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t else if (milliseconds > 0) { /* Non-blocking, but called with timeout. */ int res; - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += milliseconds / 1000; - ts.tv_nsec += (milliseconds % 1000) * 1000000; - if (ts.tv_nsec >= 1000000000L) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000L; - } + hidapi_timespec ts; + hidapi_thread_gettime(&ts); + hidapi_thread_addtime(&ts, milliseconds); while (!dev->input_reports && !dev->shutdown_thread) { - res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts); + res = hidapi_thread_cond_timedwait(&dev->thread_state, &ts); if (res == 0) { if (dev->input_reports) { bytes_read = return_data(dev, data, length); @@ -1423,7 +2062,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t or the read thread was shutdown. Run the loop again (ie: don't break). */ } - else if (res == ETIMEDOUT) { + else if (res == HIDAPI_THREAD_TIMED_OUT) { /* Timed out. */ bytes_read = 0; break; @@ -1441,8 +2080,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t } ret: - pthread_mutex_unlock(&dev->mutex); - pthread_cleanup_pop(0); + hidapi_thread_mutex_unlock(&dev->thread_state); + hidapi_thread_cleanup_pop(0); return bytes_read; } @@ -1560,7 +2199,7 @@ void HID_API_EXPORT hid_close(hid_device *dev) libusb_cancel_transfer(dev->transfer); /* Wait for read_thread() to end. */ - pthread_join(dev->thread, NULL); + hidapi_thread_join(&dev->thread_state); /* Clean up the Transfer objects allocated in read_thread(). */ free(dev->transfer->buffer); @@ -1583,11 +2222,11 @@ void HID_API_EXPORT hid_close(hid_device *dev) libusb_close(dev->device_handle); /* Clear out the queue of received reports. */ - pthread_mutex_lock(&dev->mutex); + hidapi_thread_mutex_lock(&dev->thread_state); while (dev->input_reports) { return_data(dev, NULL, 0); } - pthread_mutex_unlock(&dev->mutex); + hidapi_thread_mutex_unlock(&dev->thread_state); free_hid_device(dev); } @@ -1811,7 +2450,7 @@ uint16_t get_usb_code_for_current_locale(void) return 0x0; /* Make a copy of the current locale string. */ - strncpy(search_string, locale, sizeof(search_string)); + strncpy(search_string, locale, sizeof(search_string)-1); search_string[sizeof(search_string)-1] = '\0'; /* Chop off the encoding part, and make it lower case. */ diff --git a/hidapi/libusb/hidapi_thread_pthread.h b/hidapi/libusb/hidapi_thread_pthread.h new file mode 100644 index 0000000..0abe733 --- /dev/null +++ b/hidapi/libusb/hidapi_thread_pthread.h @@ -0,0 +1,174 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Sam Lantinga + + Copyright 2023, 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 . +********************************************************/ + +#include + +#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ + +/* Barrier implementation because Android/Bionic don't have pthread_barrier. + This implementation came from Brent Priddy and was posted on + StackOverflow. It is used with his permission. */ +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int trip_count; +} pthread_barrier_t; + +static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + if(count == 0) { + errno = EINVAL; + return -1; + } + + if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->trip_count) { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +#endif + +#define HIDAPI_THREAD_TIMED_OUT ETIMEDOUT + +typedef struct timespec hidapi_timespec; + +typedef struct +{ + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ + +} hidapi_thread_state; + +static void hidapi_thread_state_init(hidapi_thread_state *state) +{ + pthread_mutex_init(&state->mutex, NULL); + pthread_cond_init(&state->condition, NULL); + pthread_barrier_init(&state->barrier, NULL, 2); +} + +static void hidapi_thread_state_destroy(hidapi_thread_state *state) +{ + pthread_barrier_destroy(&state->barrier); + pthread_cond_destroy(&state->condition); + pthread_mutex_destroy(&state->mutex); +} + +#define hidapi_thread_cleanup_push pthread_cleanup_push +#define hidapi_thread_cleanup_pop pthread_cleanup_pop + +static void hidapi_thread_mutex_lock(hidapi_thread_state *state) +{ + pthread_mutex_lock(&state->mutex); +} + +static void hidapi_thread_mutex_unlock(hidapi_thread_state *state) +{ + pthread_mutex_unlock(&state->mutex); +} + +static void hidapi_thread_cond_wait(hidapi_thread_state *state) +{ + pthread_cond_wait(&state->condition, &state->mutex); +} + +static int hidapi_thread_cond_timedwait(hidapi_thread_state *state, hidapi_timespec *ts) +{ + return pthread_cond_timedwait(&state->condition, &state->mutex, ts); +} + +static void hidapi_thread_cond_signal(hidapi_thread_state *state) +{ + pthread_cond_signal(&state->condition); +} + +static void hidapi_thread_cond_broadcast(hidapi_thread_state *state) +{ + pthread_cond_broadcast(&state->condition); +} + +static void hidapi_thread_barrier_wait(hidapi_thread_state *state) +{ + pthread_barrier_wait(&state->barrier); +} + +static void hidapi_thread_create(hidapi_thread_state *state, void *(*func)(void*), void *func_arg) +{ + pthread_create(&state->thread, NULL, func, func_arg); +} + +static void hidapi_thread_join(hidapi_thread_state *state) +{ + pthread_join(state->thread, NULL); +} + +static void hidapi_thread_gettime(hidapi_timespec *ts) +{ + clock_gettime(CLOCK_REALTIME, ts); +} + +static void hidapi_thread_addtime(hidapi_timespec *ts, int milliseconds) +{ + ts->tv_sec += milliseconds / 1000; + ts->tv_nsec += (milliseconds % 1000) * 1000000; + if (ts->tv_nsec >= 1000000000L) { + ts->tv_sec++; + ts->tv_nsec -= 1000000000L; + } +} diff --git a/hidapi/linux/hid.c b/hidapi/linux/hid.c index a499f04..e77bf28 100644 --- a/hidapi/linux/hid.c +++ b/hidapi/linux/hid.c @@ -35,6 +35,7 @@ #include #include #include +#include /* Linux */ #include @@ -68,6 +69,10 @@ #define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) #endif +/* The value of the first callback handle to be given upon registration */ +/* Can be any arbitrary positive integer */ +#define FIRST_HOTPLUG_CALLBACK_HANDLE 1 + struct hid_device_ { int device_handle; int blocking; @@ -192,7 +197,7 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) * Returns 1 if successful, 0 if an invalid key * Sets data_len and key_size when successful */ -static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) +static int get_hid_item_size(const __u8 *report_descriptor, __u32 size, unsigned int pos, int *data_len, int *key_size) { int key = report_descriptor[pos]; int size_code; @@ -248,7 +253,7 @@ static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 si * Get bytes from a HID Report Descriptor. * Only call with a num_bytes of 0, 1, 2, or 4. */ -static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur) +static __u32 get_hid_report_bytes(const __u8 *rpt, size_t len, size_t num_bytes, size_t cur) { /* Return if there aren't enough bytes. */ if (cur + num_bytes >= len) @@ -271,6 +276,60 @@ static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_ return 0; } +/* + * Iterates until the end of a Collection. + * Assumes that *pos is exactly at the beginning of a Collection. + * Skips all nested Collection, i.e. iterates until the end of current level Collection. + * + * The return value is non-0 when an end of current Collection is found, + * 0 when error is occured (broken Descriptor, end of a Collection is found before its begin, + * or no Collection is found at all). + */ +static int hid_iterate_over_collection(const __u8 *report_descriptor, __u32 size, unsigned int *pos, int *data_len, int *key_size) +{ + int collection_level = 0; + + while (*pos < size) { + int key = report_descriptor[*pos]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, size, *pos, data_len, key_size)) + return 0; /* malformed report */ + + switch (key_cmd) { + case 0xa0: /* Collection 6.2.2.4 (Main) */ + collection_level++; + break; + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + collection_level--; + break; + } + + if (collection_level < 0) { + /* Broken descriptor or someone is using this function wrong, + * i.e. should be called exactly at the collection start */ + return 0; + } + + if (collection_level == 0) { + /* Found it! + * Also possible when called not at the collection start, but should not happen if used correctly */ + return 1; + } + + *pos += *data_len + *key_size; + } + + return 0; /* Did not find the end of a Collection */ +} + +struct hid_usage_iterator { + unsigned int pos; + int usage_page_found; + unsigned short usage_page; +}; + /* * Retrieves the device's Usage Page and Usage from the report descriptor. * The algorithm returns the current Usage Page/Usage pair whenever a new @@ -288,70 +347,71 @@ static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_ * 1 when finished processing descriptor. * -1 on a malformed report. */ -static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage) +static int get_next_hid_usage(const __u8 *report_descriptor, __u32 size, struct hid_usage_iterator *ctx, unsigned short *usage_page, unsigned short *usage) { int data_len, key_size; - int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */ - int usage_pair_ready = 0; + int initial = ctx->pos == 0; /* Used to handle case where no top-level application collection is defined */ - /* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */ int usage_found = 0; - while (*pos < size) { - int key = report_descriptor[*pos]; + while (ctx->pos < size) { + int key = report_descriptor[ctx->pos]; int key_cmd = key & 0xfc; /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size)) + if (!get_hid_item_size(report_descriptor, size, ctx->pos, &data_len, &key_size)) return -1; /* malformed report */ switch (key_cmd) { case 0x4: /* Usage Page 6.2.2.7 (Global) */ - *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos); + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + ctx->usage_page_found = 1; break; case 0x8: /* Usage 6.2.2.8 (Local) */ - *usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos); - usage_found = 1; + if (data_len == 4) { /* Usages 5.5 / Usage Page 6.2.2.7 */ + ctx->usage_page = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos + 2); + ctx->usage_page_found = 1; + *usage = get_hid_report_bytes(report_descriptor, size, 2, ctx->pos); + usage_found = 1; + } + else { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, ctx->pos); + usage_found = 1; + } break; case 0xa0: /* Collection 6.2.2.4 (Main) */ - /* A Usage Item (Local) must be found for the pair to be valid */ - if (usage_found) - usage_pair_ready = 1; + if (!hid_iterate_over_collection(report_descriptor, size, &ctx->pos, &data_len, &key_size)) { + return -1; + } - /* Usage is a Local Item, unset it */ - usage_found = 0; - break; + /* A pair is valid - to be reported when Collection is found */ + if (usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; + } - case 0x80: /* Input 6.2.2.4 (Main) */ - case 0x90: /* Output 6.2.2.4 (Main) */ - case 0xb0: /* Feature 6.2.2.4 (Main) */ - case 0xc0: /* End Collection 6.2.2.4 (Main) */ - /* Usage is a Local Item, unset it */ - usage_found = 0; break; } /* Skip over this key and its associated data */ - *pos += data_len + key_size; - - /* Return usage pair */ - if (usage_pair_ready) - return 0; + ctx->pos += data_len + key_size; } /* If no top-level application collection is found and usage page/usage pair is found, pair is valid https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ - if (initial && usage_found) - return 0; /* success */ + if (initial && usage_found && ctx->usage_page_found) { + *usage_page = ctx->usage_page; + return 0; /* success */ + } return 1; /* finished processing */ } /* * Retrieves the hidraw report descriptor from a file. - * When using this form, /device/report_descriptor, elevated priviledges are not required. + * When using this form, /device/report_descriptor, elevated privileges are not required. */ static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) { @@ -512,6 +572,11 @@ static int parse_uevent_info(const char *uevent, unsigned *bus_type, char **serial_number_utf8, char **product_name_utf8) { char tmp[1024]; + + if (!uevent) { + return 0; + } + size_t uevent_len = strlen(uevent); if (uevent_len > sizeof(tmp) - 1) uevent_len = sizeof(tmp) - 1; @@ -722,12 +787,14 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); if (result >= 0) { unsigned short page = 0, usage = 0; - unsigned int pos = 0; + struct hid_usage_iterator usage_iterator; + memset(&usage_iterator, 0, sizeof(usage_iterator)); + /* * 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)) { + if (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &page, &usage)) { cur_dev->usage_page = page; cur_dev->usage = usage; } @@ -736,7 +803,7 @@ static struct hid_device_info * create_device_info_for_device(struct udev_device * 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)) { + while (!get_next_hid_usage(report_desc.value, report_desc.size, &usage_iterator, &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; @@ -818,6 +885,133 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) return HID_API_VERSION_STR; } +static struct hid_hotplug_context { + /* UDEV context that handles the monitor */ + struct udev* udev_ctx; + + /* UDEV monitor that receives events */ + struct udev_monitor* mon; + + /* File descriptor for the UDEV monitor that allows to check for new events with select() */ + int monitor_fd; + + /* Thread for the UDEV monitor */ + pthread_t thread; + + pthread_mutex_t mutex; + + /* Boolean flags */ + unsigned char mutex_ready; + unsigned char mutex_in_use; + unsigned char cb_list_dirty; + + /* HIDAPI unique callback handle counter */ + hid_hotplug_callback_handle next_handle; + + /* Linked list of the hotplug callbacks */ + struct hid_hotplug_callback *hotplug_cbs; + + /* Linked list of the device infos (mandatory when the device is disconnected) */ + struct hid_device_info *devs; +} hid_hotplug_context = { + .udev_ctx = NULL, + .monitor_fd = -1, + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, + .mutex_ready = 0, + .hotplug_cbs = NULL, + .devs = NULL +}; + +struct hid_hotplug_callback { + hid_hotplug_callback_handle handle; + unsigned short vendor_id; + unsigned short product_id; + hid_hotplug_event events; + void *user_data; + hid_hotplug_callback_fn callback; + + /* Pointer to the next notification */ + struct hid_hotplug_callback *next; +}; + +static void hid_internal_hotplug_remove_postponed() +{ + /* Unregister the callbacks whose removal was postponed */ + /* This function is always called inside a locked mutex */ + /* However, any actions are only allowed if the mutex is NOT in use and if the DIRTY flag is set */ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use || !hid_hotplug_context.cb_list_dirty) { + return; + } + + /* Traverse the list of callbacks and check if any were marked for removal */ + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if (!callback->events) { + *current = (*current)->next; + free(callback); + continue; + } + current = &callback->next; + } + + /* Clear the flag so we don't start the cycle unless necessary */ + hid_hotplug_context.cb_list_dirty = 0; +} + +static void hid_internal_hotplug_cleanup() +{ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use) { + return; + } + + /* Before checking if the list is empty, clear any entries whose removal was postponed first */ + hid_internal_hotplug_remove_postponed(); + + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; + } + + pthread_join(hid_hotplug_context.thread, NULL); +} + +static void hid_internal_hotplug_init() +{ + if (!hid_hotplug_context.mutex_ready) { + /* Initialize the mutex as recursive */ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&hid_hotplug_context.mutex, &attr); + pthread_mutexattr_destroy(&attr); + + /* Set state to Ready */ + hid_hotplug_context.mutex_ready = 1; + hid_hotplug_context.mutex_in_use = 0; + hid_hotplug_context.cb_list_dirty = 0; + } +} + +static void hid_internal_hotplug_exit() +{ + if (!hid_hotplug_context.mutex_ready) { + return; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + /* Remove all callbacks from the list */ + while (*current) { + struct hid_hotplug_callback* next = (*current)->next; + free(*current); + *current = next; + } + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_ready = 0; + pthread_mutex_destroy(&hid_hotplug_context.mutex); +} + int HID_API_EXPORT hid_init(void) { const char *locale; @@ -833,14 +1027,22 @@ int HID_API_EXPORT hid_init(void) return 0; } + int HID_API_EXPORT hid_exit(void) { /* Free global error message */ register_global_error(NULL); + hid_internal_hotplug_exit(); + return 0; } +static int hid_internal_match_device_id(unsigned short vendor_id, unsigned short product_id, unsigned short expected_vendor_id, unsigned short expected_product_id) +{ + return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id); +} + struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) { struct udev *udev; @@ -905,7 +1107,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } cur_dev = tmp; - /* move the pointer to the tail of returnd list */ + /* move the pointer to the tail of returned list */ while (cur_dev->next != NULL) { cur_dev = cur_dev->next; } @@ -942,6 +1144,276 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +static void hid_internal_invoke_callbacks(struct hid_device_info *info, hid_hotplug_event event) +{ + pthread_mutex_lock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_in_use = 1; + + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if ((callback->events & event) && hid_internal_match_device_id(info->vendor_id, info->product_id, + callback->vendor_id, callback->product_id)) { + int result = callback->callback(callback->handle, info, event, callback->user_data); + /* If the result is non-zero, we mark the callback for removal and proceed */ + if (result) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + continue; + } + } + current = &callback->next; + } + + hid_hotplug_context.mutex_in_use = 0; + hid_internal_hotplug_remove_postponed(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); +} + +static int match_udev_to_info(struct udev_device* raw_dev, struct hid_device_info *info) +{ + const char *path = udev_device_get_devnode(raw_dev); + if (!strcmp(path, info->path)) { + return 1; + } + return 0; +} + +static void* hotplug_thread(void* user_data) +{ + (void) user_data; + + /* Note: the cleanup sequence is always executed with the mutex locked, so we shoud never lock the mutex without checking if we need to stop */ + + while (hid_hotplug_context.monitor_fd > 0) { + fd_set fds; + struct timeval tv; + int ret; + + /* On every iteration, check if we still have any callbacks left and leave if none are left */ + /* NOTE: the check is performed UNLOCKED and the value CAN change in the background */ + if (!hid_hotplug_context.hotplug_cbs) { + break; + } + + FD_ZERO(&fds); + FD_SET(hid_hotplug_context.monitor_fd, &fds); + /* 5 msec timeout seems reasonable; don't set too low to avoid high CPU usage */ + /* This timeout only affects how much time it takes to stop the thread */ + tv.tv_sec = 0; + tv.tv_usec = 5000; + + ret = select(hid_hotplug_context.monitor_fd+1, &fds, NULL, NULL, &tv); + + /* An extra check, just in case within those 5msec the thread was told to stop */ + if (!hid_hotplug_context.hotplug_cbs) { + break; + } + + /* Check if our file descriptor has received data. */ + if (ret > 0 && FD_ISSET(hid_hotplug_context.monitor_fd, &fds)) { + + /* Make the call to receive the device. + select() ensured that this will not block. */ + struct udev_device *raw_dev = udev_monitor_receive_device(hid_hotplug_context.mon); + if (raw_dev) { + pthread_mutex_lock(&hid_hotplug_context.mutex); + const char* action = udev_device_get_action(raw_dev); + if (!strcmp(action, "add")) { + // We create a list of all usages on this UDEV device + struct hid_device_info *info = create_device_info_for_device(raw_dev); + struct hid_device_info *info_cur = info; + while (info_cur) { + /* For each device, call all matching callbacks */ + /* TODO: possibly make the `next` field NULL to match the behavior on other systems */ + hid_internal_invoke_callbacks(info_cur, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED); + info_cur = info_cur->next; + } + + /* Append all we got to the end of the device list */ + if (info) { + if (hid_hotplug_context.devs != NULL) { + struct hid_device_info *last = hid_hotplug_context.devs; + while (last->next != NULL) { + last = last->next; + } + last->next = info; + } else { + hid_hotplug_context.devs = info; + } + } + } else if (!strcmp(action, "remove")) { + for (struct hid_device_info **current = &hid_hotplug_context.devs; *current;) { + struct hid_device_info* info = *current; + if (match_udev_to_info(raw_dev, *current)) { + /* If the libusb device that's left matches this HID device, we detach it from the list */ + *current = (*current)->next; + info->next = NULL; + hid_internal_invoke_callbacks(info, HID_API_HOTPLUG_EVENT_DEVICE_LEFT); + /* Free every removed device */ + free(info); + } else { + current = &info->next; + } + } + } + udev_device_unref(raw_dev); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + } + } + } + + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + /* Disarm the udev monitor */ + udev_monitor_unref(hid_hotplug_context.mon); + udev_unref(hid_hotplug_context.udev_ctx); + + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle) +{ + struct hid_hotplug_callback* hotplug_cb; + + /* Check params */ + if (events == 0 + || (events & ~(HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT)) + || (flags & ~(HID_API_HOTPLUG_ENUMERATE)) + || callback == NULL) { + return -1; + } + + hotplug_cb = (struct hid_hotplug_callback*)calloc(1, sizeof(struct hid_hotplug_callback)); + + if (hotplug_cb == NULL) { + return -1; + } + + /* Fill out the record */ + hotplug_cb->next = NULL; + hotplug_cb->vendor_id = vendor_id; + hotplug_cb->product_id = product_id; + hotplug_cb->events = events; + hotplug_cb->user_data = user_data; + hotplug_cb->callback = callback; + + /* Ensure we are ready to actually use the mutex */ + hid_internal_hotplug_init(); + + /* Lock the mutex to avoid race conditions */ + pthread_mutex_lock(&hid_hotplug_context.mutex); + + hotplug_cb->handle = hid_hotplug_context.next_handle++; + + /* handle the unlikely case of handle overflow */ + if (hid_hotplug_context.next_handle < 0) + { + hid_hotplug_context.next_handle = 1; + } + + /* Return allocated handle */ + if (callback_handle != NULL) { + *callback_handle = hotplug_cb->handle; + } + + /* Append a new callback to the end */ + if (hid_hotplug_context.hotplug_cbs != NULL) { + struct hid_hotplug_callback *last = hid_hotplug_context.hotplug_cbs; + while (last->next != NULL) { + last = last->next; + } + last->next = hotplug_cb; + } + else { + // Prepare a UDEV context to run monitoring on + hid_hotplug_context.udev_ctx = udev_new(); + if (!hid_hotplug_context.udev_ctx) + { + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + hid_hotplug_context.mon = udev_monitor_new_from_netlink(hid_hotplug_context.udev_ctx, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(hid_hotplug_context.mon, "hidraw", NULL); + udev_monitor_enable_receiving(hid_hotplug_context.mon); + hid_hotplug_context.monitor_fd = udev_monitor_get_fd(hid_hotplug_context.mon); + + /* After monitoring is all set up, enumerate all devices */ + hid_hotplug_context.devs = hid_enumerate(0, 0); + + /* Don't forget to actually register the callback */ + hid_hotplug_context.hotplug_cbs = hotplug_cb; + + /* Start the thread that will be doing the event scanning */ + pthread_create(&hid_hotplug_context.thread, NULL, &hotplug_thread, NULL); + } + + /* Mark the mutex as IN USE, to prevent callback removal from inside a callback */ + unsigned char old_state = hid_hotplug_context.mutex_in_use; + hid_hotplug_context.mutex_in_use = 1; + + if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { + struct hid_device_info* device = hid_hotplug_context.devs; + /* Notify about already connected devices, if asked so */ + while (device != NULL) { + if (hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { + (*hotplug_cb->callback)(hotplug_cb->handle, device, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, hotplug_cb->user_data); + } + + device = device->next; + } + } + + hid_hotplug_context.mutex_in_use = old_state; + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + if (!hid_hotplug_context.mutex_ready || callback_handle <= 0) { + return -1; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + + if (hid_hotplug_context.hotplug_cbs == NULL) { + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + int result = -1; + + /* Remove this notification */ + for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { + if ((*current)->handle == callback_handle) { + /* Check if we were already in a locked state, as we are NOT allowed to remove any callbacks if we are */ + if (hid_hotplug_context.mutex_in_use) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + } else { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + } + result = 0; + break; + } + } + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return result; +} + hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { struct hid_device_info *devs, *cur_dev; @@ -1107,7 +1579,6 @@ int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) return 0; /* Success */ } - int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) { int res; diff --git a/hidapi/mac/hid.c b/hidapi/mac/hid.c index 5c0914a..9107476 100644 --- a/hidapi/mac/hid.c +++ b/hidapi/mac/hid.c @@ -38,8 +38,9 @@ #include "hidapi_darwin.h" -/* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ -extern const double NSAppKitVersionNumber; +/* The value of the first callback handle to be given upon registration */ +/* Can be any arbitrary positive integer */ +#define FIRST_HOTPLUG_CALLBACK_HANDLE 1 /* Barrier implementation because Mac OSX doesn't have pthread_barrier. It also doesn't have clock_gettime(). So much for POSIX and SUSv2. @@ -57,15 +58,15 @@ static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrie { (void) attr; - if(count == 0) { + if (count == 0) { errno = EINVAL; return -1; } - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + if (pthread_mutex_init(&barrier->mutex, 0) < 0) { return -1; } - if(pthread_cond_init(&barrier->cond, 0) < 0) { + if (pthread_cond_init(&barrier->cond, 0) < 0) { pthread_mutex_destroy(&barrier->mutex); return -1; } @@ -86,16 +87,18 @@ static int pthread_barrier_wait(pthread_barrier_t *barrier) { pthread_mutex_lock(&barrier->mutex); ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { + if (barrier->count >= barrier->trip_count) { barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); pthread_mutex_unlock(&barrier->mutex); + pthread_cond_broadcast(&barrier->cond); return 1; } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + else { + do { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + } + while (barrier->count != 0); + pthread_mutex_unlock(&barrier->mutex); return 0; } @@ -376,7 +379,7 @@ static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t buf[0] = 0; - if (str) { + if (str && CFGetTypeID(str) == CFStringGetTypeID()) { CFIndex str_len = CFStringGetLength(str); CFRange range; CFIndex used_buf_len; @@ -458,6 +461,155 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) return HID_API_VERSION_STR; } +struct hid_hotplug_callback { + hid_hotplug_callback_handle handle; + unsigned short vendor_id; + unsigned short product_id; + hid_hotplug_event events; + void *user_data; + hid_hotplug_callback_fn callback; + + /* Pointer to the next notification */ + struct hid_hotplug_callback *next; +}; + +/* When a HID device is removed, we are no longer able to generate a path for it, but we can still match io_service_t */ +struct hid_device_info_ex +{ + struct hid_device_info info; + io_service_t service; +}; + +static struct hid_hotplug_context { + /* MacOS specific notification handles */ + IOHIDManagerRef manager; + + /* Thread and RunLoop for the manager to work in */ + pthread_t thread; + CFRunLoopRef run_loop; + CFRunLoopSourceRef source; + CFStringRef run_loop_mode; + pthread_barrier_t startup_barrier; /* Ensures correct startup sequence */ + int thread_state; + + /* HIDAPI unique callback handle counter */ + hid_hotplug_callback_handle next_handle; + + pthread_mutex_t mutex; + + /* Boolean flags */ + unsigned char mutex_ready; + unsigned char mutex_in_use; + unsigned char cb_list_dirty; + + /* Linked list of the hotplug callbacks */ + struct hid_hotplug_callback *hotplug_cbs; + + /* Linked list of the device infos (mandatory when the device is disconnected) */ + struct hid_device_info *devs; +} hid_hotplug_context = { + .manager = NULL, + .run_loop = NULL, + .run_loop_mode = NULL, + .source = NULL, + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, + .mutex_ready = 0, + .thread_state = 0, /* 0 = starting (events ignored), 1 = running (events processed), 2 = shutting down */ + .hotplug_cbs = NULL, + .devs = NULL +}; + +static void hid_internal_hotplug_remove_postponed(void) +{ + /* Unregister the callbacks whose removal was postponed */ + /* This function is always called inside a locked mutex */ + /* However, any actions are only allowed if the mutex is NOT in use and if the DIRTY flag is set */ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use || !hid_hotplug_context.cb_list_dirty) { + return; + } + + /* Traverse the list of callbacks and check if any were marked for removal */ + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if (!callback->events) { + *current = (*current)->next; + free(callback); + continue; + } + current = &callback->next; + } + + /* Clear the flag so we don't start the cycle unless necessary */ + hid_hotplug_context.cb_list_dirty = 0; +} + +static void hid_internal_hotplug_cleanup(void) +{ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use) { + return; + } + + /* Before checking if the list is empty, clear any entries whose removal was postponed first */ + hid_internal_hotplug_remove_postponed(); + + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; + } + + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + + /* Cause hotplug_thread() to stop. */ + hid_hotplug_context.thread_state = 2; + + /* Wake up the run thread's event loop so that the thread can exit. */ + CFRunLoopSourceSignal(hid_hotplug_context.source); + CFRunLoopWakeUp(hid_hotplug_context.run_loop); + + /* Wait for read_thread() to end. */ + pthread_join(hid_hotplug_context.thread, NULL); +} + +static void hid_internal_hotplug_init(void) +{ + if (!hid_hotplug_context.mutex_ready) { + /* Initialize the mutex as recursive */ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&hid_hotplug_context.mutex, &attr); + pthread_mutexattr_destroy(&attr); + + /* Set state to Ready */ + hid_hotplug_context.mutex_ready = 1; + hid_hotplug_context.mutex_in_use = 0; + hid_hotplug_context.cb_list_dirty = 0; + } +} + +static void hid_internal_hotplug_exit(void) +{ + if (!hid_hotplug_context.mutex_ready) { + return; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + struct hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs; + + /* Remove all callbacks from the list */ + while (*current) { + struct hid_hotplug_callback* next = (*current)->next; + free(*current); + *current = next; + } + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_ready = 0; + pthread_mutex_destroy(&hid_hotplug_context.mutex); +} + /* Initialize the IOHIDManager if necessary. This is the public function, and it is safe to call this function repeatedly. Return 0 for success and -1 for failure. */ @@ -466,11 +618,11 @@ int HID_API_EXPORT hid_init(void) register_global_error(NULL); if (!hid_mgr) { - is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ + is_macos_10_10_or_greater = (kCFCoreFoundationVersionNumber >= 1151.16); /* kCFCoreFoundationVersionNumber10_10 */ hid_darwin_set_open_exclusive(1); /* Backward compatibility */ + return init_hid_manager(); } - /* Already initialized. */ return 0; } @@ -482,6 +634,7 @@ int HID_API_EXPORT hid_exit(void) IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); CFRelease(hid_mgr); hid_mgr = NULL; + hid_internal_hotplug_exit(); } /* Free global error message */ @@ -490,11 +643,17 @@ int HID_API_EXPORT hid_exit(void) return 0; } -static void process_pending_events(void) { +static int hid_internal_match_device_id(unsigned short vendor_id, unsigned short product_id, unsigned short expected_vendor_id, unsigned short expected_product_id) +{ + return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id); +} + +static void process_pending_events(void) +{ SInt32 res; do { res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); - } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); + } while (res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); } static int read_usb_interface_from_hid_service_parent(io_service_t hid_service) @@ -545,6 +704,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, wchar_t buf[BUF_LEN]; CFTypeRef transport_prop; + struct hid_device_info_ex *cur_dev_ex; struct hid_device_info *cur_dev; io_service_t hid_service; kern_return_t res; @@ -554,7 +714,9 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, return NULL; } - cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info)); + /* A small trick to store an io_service_t tag along with hid_device_info for matching info with unplugged device */ + cur_dev_ex = (struct hid_device_info_ex *)calloc(1, sizeof(struct hid_device_info_ex)); + cur_dev = &(cur_dev_ex->info); if (cur_dev == NULL) { return NULL; } @@ -573,6 +735,7 @@ static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, hid_service = IOHIDDeviceGetService(dev); if (hid_service != MACH_PORT_NULL) { res = IORegistryEntryGetRegistryEntryID(hid_service, &entry_id); + cur_dev_ex->service = hid_service; } else { res = KERN_INVALID_ARGUMENT; @@ -769,7 +932,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } cur_dev = tmp; - /* move the pointer to the tail of returnd list */ + /* move the pointer to the tail of returned list */ while (cur_dev->next != NULL) { cur_dev = cur_dev->next; } @@ -805,6 +968,331 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +static void hid_internal_invoke_callbacks(struct hid_device_info *info, hid_hotplug_event event) +{ + pthread_mutex_lock(&hid_hotplug_context.mutex); + hid_hotplug_context.mutex_in_use = 1; + + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if ((callback->events & event) && hid_internal_match_device_id(info->vendor_id, info->product_id, + callback->vendor_id, callback->product_id)) { + int result = callback->callback(callback->handle, info, event, callback->user_data); + /* If the result is non-zero, we mark the callback for removal and proceed */ + /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ + if (result) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + continue; + } + } + current = &callback->next; + } + + hid_hotplug_context.mutex_in_use = 0; + hid_internal_hotplug_remove_postponed(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); +} + +static void hid_internal_hotplug_connect_callback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + (void) context; + (void) result; + (void) sender; + + struct hid_device_info* info = create_device_info(device); + if (!info) { + return; + } + struct hid_device_info* info_cur = info; + + /* NOTE: we don't call any callbacks and we don't lock the mutex during initialization: the mutex is held by the main thread, but it's waiting by a barrier */ + if (hid_hotplug_context.thread_state > 0) + { + /* Lock the mutex to avoid race conditions */ + pthread_mutex_lock(&hid_hotplug_context.mutex); + + /* Invoke all callbacks */ + while (info_cur) + { + hid_internal_invoke_callbacks(info_cur, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED); + info_cur = info_cur->next; + } + } + + /* Append all we got to the end of the device list */ + if (info) { + if (hid_hotplug_context.devs != NULL) { + struct hid_device_info* last = hid_hotplug_context.devs; + while (last->next != NULL) { + last = last->next; + } + last->next = info; + } + else { + hid_hotplug_context.devs = info; + } + } + + if (hid_hotplug_context.thread_state > 0) + { + pthread_mutex_unlock(&hid_hotplug_context.mutex); + } +} + +int match_ref_to_info(IOHIDDeviceRef device, struct hid_device_info *info) +{ + if (!device || !info) { + return 0; + } + + struct hid_device_info_ex* ex = (struct hid_device_info_ex*)info; + io_service_t service = IOHIDDeviceGetService(device); + + return (service == ex->service); +} + +static void hid_internal_hotplug_disconnect_callback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) +{ + (void) context; + (void) result; + (void) sender; + + /* NOTE: we don't call any callbacks and we don't lock the mutex during initialization: the mutex is held by the main thread, but it's waiting by a barrier*/ + if (hid_hotplug_context.thread_state > 0) + { + pthread_mutex_lock(&hid_hotplug_context.mutex); + } + + for (struct hid_device_info **current = &hid_hotplug_context.devs; *current;) { + struct hid_device_info* info = *current; + if (match_ref_to_info(device, *current)) { + /* If the IOHIDDeviceRef device that's left matches this HID device, we detach it from the list */ + *current = (*current)->next; + info->next = NULL; + hid_internal_invoke_callbacks(info, HID_API_HOTPLUG_EVENT_DEVICE_LEFT); + /* Free every removed device */ + free(info); + } else { + current = &info->next; + } + } + + if (hid_hotplug_context.thread_state > 0) + { + /* Clean up if the last callback was removed */ + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + } +} + +static void hotplug_stop_callback(void* context) +{ + (void) context; + CFRunLoopStop(hid_hotplug_context.run_loop); +} + +static void* hotplug_thread(void* user_data) +{ + (void) user_data; + + hid_hotplug_context.thread_state = 0; + hid_hotplug_context.manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + + if (!hid_hotplug_context.run_loop_mode) { + const char *str = "HIDAPI_hotplug"; + hid_hotplug_context.run_loop_mode = CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + } + + /* Ensure the manager runs in this thread */ + IOHIDManagerScheduleWithRunLoop(hid_hotplug_context.manager, CFRunLoopGetCurrent(), hid_hotplug_context.run_loop_mode); + /* Store a reference to this runloop if we ever need to stop it - e.g. if we have no callbacks left or hid_exit was called */ + hid_hotplug_context.run_loop = CFRunLoopGetCurrent(); + + /* Create the RunLoopSource which is used to signal the + event loop to stop when hid_internal_hotplug_cleanup() is called. */ + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.version = 0; + ctx.perform = &hotplug_stop_callback; + hid_hotplug_context.source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); + CFRunLoopAddSource(hid_hotplug_context.run_loop, hid_hotplug_context.source, hid_hotplug_context.run_loop_mode); + + /* Set the manager to receive events for ALL HID devices */ + IOHIDManagerSetDeviceMatching(hid_hotplug_context.manager, NULL); + + /* Install callbacks */ + IOHIDManagerRegisterDeviceMatchingCallback(hid_hotplug_context.manager, + hid_internal_hotplug_connect_callback, + NULL); + + IOHIDManagerRegisterDeviceRemovalCallback(hid_hotplug_context.manager, + hid_internal_hotplug_disconnect_callback, + NULL); + + /* After monitoring is all set up, enumerate all devices */ + /* Opening the manager should result in the internal callback being called for all connected devices */ + IOHIDManagerOpen(hid_hotplug_context.manager, kIOHIDOptionsTypeNone); + + /* TODO: We need to flush all events from the runloop to ensure the already connected devices don't send any unwanted events */ + process_pending_events(); + + /* Now that all events are flushed, we are ready to notify the main thread that we are ready */ + hid_hotplug_context.thread_state = 1; + pthread_barrier_wait(&hid_hotplug_context.startup_barrier); + + while (hid_hotplug_context.thread_state != 2) { + int code = CFRunLoopRunInMode(hid_hotplug_context.run_loop_mode, 1000/*sec*/, FALSE); + /* If runloop stopped for whatever reason, exit the thread */ + if (code != kCFRunLoopRunTimedOut && + code != kCFRunLoopRunHandledSource) { + hid_hotplug_context.thread_state = 2; + break; + } + } + + /* Kill the manager */ + IOHIDManagerClose(hid_hotplug_context.manager, kIOHIDOptionsTypeNone); + + IOHIDManagerUnscheduleFromRunLoop(hid_hotplug_context.manager, hid_hotplug_context.run_loop, hid_hotplug_context.run_loop_mode); + + CFRelease(hid_hotplug_context.manager); + hid_hotplug_context.manager = NULL; + + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle) +{ + struct hid_hotplug_callback* hotplug_cb; + + /* Check params */ + if (events == 0 + || (events & ~(HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT)) + || (flags & ~(HID_API_HOTPLUG_ENUMERATE)) + || callback == NULL) { + return -1; + } + + hotplug_cb = (struct hid_hotplug_callback*)calloc(1, sizeof(struct hid_hotplug_callback)); + + + if (hotplug_cb == NULL) { + return -1; + } + + /* Fill out the record */ + hotplug_cb->next = NULL; + hotplug_cb->vendor_id = vendor_id; + hotplug_cb->product_id = product_id; + hotplug_cb->events = events; + hotplug_cb->user_data = user_data; + hotplug_cb->callback = callback; + + /* Ensure we are ready to actually use the mutex */ + hid_internal_hotplug_init(); + + /* Lock the mutex to avoid race conditions */ + pthread_mutex_lock(&hid_hotplug_context.mutex); + + hotplug_cb->handle = hid_hotplug_context.next_handle++; + + /* handle the unlikely case of handle overflow */ + if (hid_hotplug_context.next_handle < 0) + { + hid_hotplug_context.next_handle = 1; + } + + /* Return allocated handle */ + if (callback_handle != NULL) { + *callback_handle = hotplug_cb->handle; + } + + /* Append a new callback to the end */ + if (hid_hotplug_context.hotplug_cbs != NULL) { + struct hid_hotplug_callback *last = hid_hotplug_context.hotplug_cbs; + while (last->next != NULL) { + last = last->next; + } + last->next = hotplug_cb; + } + else { + pthread_barrier_init(&hid_hotplug_context.startup_barrier, NULL, 2); + pthread_create(&hid_hotplug_context.thread, NULL, hotplug_thread, NULL); + + /* Wait for the thread to finish setting up - without it the callback may be registered too early*/ + + pthread_barrier_wait(&hid_hotplug_context.startup_barrier); + + /* Don't forget to actually register the callback */ + hid_hotplug_context.hotplug_cbs = hotplug_cb; + } + + /* Mark the mutex as IN USE, to prevent callback removal from inside a callback */ + unsigned char old_state = hid_hotplug_context.mutex_in_use; + hid_hotplug_context.mutex_in_use = 1; + + if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { + struct hid_device_info* device = hid_hotplug_context.devs; + /* Notify about already connected devices, if asked so */ + while (device != NULL) { + if (hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { + (*hotplug_cb->callback)(hotplug_cb->handle, device, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, hotplug_cb->user_data); + } + + device = device->next; + } + } + + hid_hotplug_context.mutex_in_use = old_state; + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + if (!hid_hotplug_context.mutex_ready) { + return -1; + } + + pthread_mutex_lock(&hid_hotplug_context.mutex); + + if (hid_hotplug_context.hotplug_cbs == NULL) { + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -1; + } + + int result = -1; + + /* Remove this notification */ + for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { + if ((*current)->handle == callback_handle) { + /* Check if we were already in a locked state, as we are NOT allowed to remove any callbacks if we are */ + if (hid_hotplug_context.mutex_in_use) { + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + } else { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + } + result = 0; + break; + } + } + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return result; +} + hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* This function is identical to the Linux version. Platform independent. */ @@ -1188,7 +1676,9 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length) return buffer (data), and delete the liked list item. */ struct input_report *rpt = dev->input_reports; size_t len = (length < rpt->len)? length: rpt->len; - memcpy(data, rpt->data, len); + if (data != NULL) { + memcpy(data, rpt->data, len); + } dev->input_reports = rpt->next; free(rpt->data); free(rpt); diff --git a/hidapi/windows/hid.c b/hidapi/windows/hid.c index 6999691..d45c103 100644 --- a/hidapi/windows/hid.c +++ b/hidapi/windows/hid.c @@ -48,6 +48,7 @@ typedef LONG NTSTATUS; #include #include #define _wcsdup wcsdup +#define _stricmp strcasecmp #endif /*#define HIDAPI_USE_DDK*/ @@ -69,6 +70,19 @@ typedef LONG NTSTATUS; /* BLUETOOTH_DEVICE_NAME_SIZE from bluetoothapis.h is 256 */ #define MAX_STRING_WCHARS 256 +/* For certain USB devices, using a buffer larger or equal to 127 wchars results + in successful completion of HID API functions, but a broken string is stored + in the output buffer. This behaviour persists even if HID API is bypassed and + HID IOCTLs are passed to the HID driver directly. Therefore, for USB devices, + the buffer MUST NOT exceed 126 WCHARs. +*/ + +#define MAX_STRING_WCHARS_USB 126 + +/* The value of the first callback handle to be given upon registration */ +/* Can be any arbitrary positive integer */ +#define FIRST_HOTPLUG_CALLBACK_HANDLE 1 + static struct hid_api_version api_version = { .major = HID_API_VERSION_MAJOR, .minor = HID_API_VERSION_MINOR, @@ -101,6 +115,8 @@ static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; static CM_Get_Device_Interface_List_SizeW_ CM_Get_Device_Interface_List_SizeW = NULL; static CM_Get_Device_Interface_ListW_ CM_Get_Device_Interface_ListW = NULL; +static CM_Register_Notification_ CM_Register_Notification = NULL; +static CM_Unregister_Notification_ CM_Unregister_Notification = NULL; static HMODULE hid_lib_handle = NULL; static HMODULE cfgmgr32_lib_handle = NULL; @@ -154,6 +170,8 @@ static int lookup_functions() RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_PropertyW); RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_List_SizeW); RESOLVE(cfgmgr32_lib_handle, CM_Get_Device_Interface_ListW); + RESOLVE(cfgmgr32_lib_handle, CM_Register_Notification); + RESOLVE(cfgmgr32_lib_handle, CM_Unregister_Notification); #undef RESOLVE #if defined(__GNUC__) @@ -170,19 +188,47 @@ static int lookup_functions() #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; - wchar_t *last_error_str; - BOOL read_pending; - char *read_buf; - OVERLAPPED ol; - OVERLAPPED write_ol; - struct hid_device_info* device_info; + 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; + wchar_t *last_error_str; + BOOL read_pending; + char *read_buf; + OVERLAPPED ol; + OVERLAPPED write_ol; + struct hid_device_info *device_info; +}; + +static struct hid_hotplug_context { + /* Win32 notification handle */ + HCMNOTIFICATION notify_handle; + + /* Critical section (faster mutex substitute), for both cached device list and callback list changes */ + CRITICAL_SECTION critical_section; + + /* Boolean flags */ + unsigned char mutex_ready; + unsigned char mutex_in_use; + unsigned char cb_list_dirty; + + /* HIDAPI unique callback handle counter */ + hid_hotplug_callback_handle next_handle; + + /* Linked list of the hotplug callbacks */ + struct hid_hotplug_callback *hotplug_cbs; + + /* Linked list of the device infos (mandatory when the device is disconnected) */ + struct hid_device_info *devs; +} hid_hotplug_context = { + .notify_handle = NULL, + .mutex_ready = 0, + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, + .hotplug_cbs = NULL, + .devs = NULL }; static hid_device *new_hid_device() @@ -206,7 +252,7 @@ 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 /*initial state f=nonsignaled*/, NULL); dev->device_info = NULL; return dev; @@ -275,7 +321,7 @@ static void register_winapi_error_to_buffer(wchar_t **error_buffer, const WCHAR /* Get rid of the CR and LF that FormatMessage() sticks at the end of the message. Thanks Microsoft! */ - while(msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ') + while (msg[msg_len-1] == L'\r' || msg[msg_len-1] == L'\n' || msg[msg_len-1] == L' ') { msg[msg_len-1] = L'\0'; msg_len--; @@ -354,9 +400,22 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void) return HID_API_VERSION_STR; } +static void hid_internal_hotplug_init() +{ + if (!hid_hotplug_context.mutex_ready) { + InitializeCriticalSection(&hid_hotplug_context.critical_section); + + /* Set state to Ready */ + hid_hotplug_context.mutex_ready = 1; + hid_hotplug_context.mutex_in_use = 0; + hid_hotplug_context.cb_list_dirty = 0; + } +} + int HID_API_EXPORT hid_init(void) { register_global_error(NULL); + #ifndef HIDAPI_USE_DDK if (!hidapi_initialized) { if (lookup_functions() < 0) { @@ -366,16 +425,108 @@ int HID_API_EXPORT hid_init(void) hidapi_initialized = TRUE; } #endif + return 0; } +struct hid_hotplug_callback { + hid_hotplug_callback_handle handle; + unsigned short vendor_id; + unsigned short product_id; + hid_hotplug_event events; + void *user_data; + hid_hotplug_callback_fn callback; + + /* Pointer to the next notification */ + struct hid_hotplug_callback *next; +}; + +static void hid_internal_hotplug_remove_postponed() +{ + /* Unregister the callbacks whose removal was postponed */ + /* This function is always called inside a locked mutex */ + /* However, any actions are only allowed if the mutex is NOT in use and if the DIRTY flag is set */ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use || !hid_hotplug_context.cb_list_dirty) { + return; + } + + /* Traverse the list of callbacks and check if any were marked for removal */ + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if (!callback->events) { + *current = (*current)->next; + free(callback); + continue; + } + current = &callback->next; + } + + /* Clear the flag so we don't start the cycle unless necessary */ + hid_hotplug_context.cb_list_dirty = 0; +} + +static void hid_internal_hotplug_cleanup() +{ + if (!hid_hotplug_context.mutex_ready || hid_hotplug_context.mutex_in_use) { + return; + } + + /* Before checking if the list is empty, clear any entries whose removal was postponed first */ + hid_internal_hotplug_remove_postponed(); + + /* Unregister the HID device connection notification when removing the last callback */ + /* This function is always called inside a locked mutex */ + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; + } + + if (hid_hotplug_context.devs) { + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + } + + if (hid_hotplug_context.notify_handle) { + if (CM_Unregister_Notification(hid_hotplug_context.notify_handle) != CR_SUCCESS) { + /* We mark an error, but we proceed with the cleanup */ + register_global_error(L"CM_Unregister_Notification failed for Hotplug notification"); + } + } + + hid_hotplug_context.notify_handle = NULL; +} + +static void hid_internal_hotplug_exit() +{ + if (!hid_hotplug_context.mutex_ready) { + /* If the critical section is not initialized, we are safe to assume nothing else is */ + return; + } + EnterCriticalSection(&hid_hotplug_context.critical_section); + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + /* Remove all callbacks from the list */ + while (*current) { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + } + hid_internal_hotplug_cleanup(); + LeaveCriticalSection(&hid_hotplug_context.critical_section); + hid_hotplug_context.mutex_ready = 0; + DeleteCriticalSection(&hid_hotplug_context.critical_section); +} + int HID_API_EXPORT hid_exit(void) { + hid_internal_hotplug_exit(); + #ifndef HIDAPI_USE_DDK free_library_handles(); hidapi_initialized = FALSE; #endif register_global_error(NULL); + return 0; } @@ -590,11 +741,26 @@ static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_n } } -static void hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev) +static int hid_internal_match_device_id(unsigned short vendor_id, unsigned short product_id, unsigned short expected_vendor_id, unsigned short expected_product_id) +{ + return (expected_vendor_id == 0x0 || vendor_id == expected_vendor_id) && (expected_product_id == 0x0 || product_id == expected_product_id); +} + +/* Unfortunately, HID_API_BUS_xxx constants alone aren't enough to distinguish between BLUETOOTH and BLE */ +#define HID_API_BUS_FLAG_BLE 0x01 + +typedef struct hid_internal_detect_bus_type_result_ { + DEVINST dev_node; + hid_bus_type bus_type; + unsigned int bus_flags; +} hid_internal_detect_bus_type_result; + +static hid_internal_detect_bus_type_result hid_internal_detect_bus_type(const wchar_t* interface_path) { wchar_t *device_id = NULL, *compatible_ids = NULL; CONFIGRET cr; DEVINST dev_node; + hid_internal_detect_bus_type_result result = { 0 }; /* Get the device id from interface path */ device_id = hid_internal_get_device_interface_property(interface_path, &DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING); @@ -625,42 +791,45 @@ static void hid_internal_get_info(const wchar_t* interface_path, struct hid_devi https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers */ if (wcsstr(compatible_id, L"USB") != NULL) { - dev->bus_type = HID_API_BUS_USB; - hid_internal_get_usb_info(dev, dev_node); + result.bus_type = HID_API_BUS_USB; break; } /* Bluetooth devices https://docs.microsoft.com/windows-hardware/drivers/bluetooth/installing-a-bluetooth-device */ if (wcsstr(compatible_id, L"BTHENUM") != NULL) { - dev->bus_type = HID_API_BUS_BLUETOOTH; + result.bus_type = HID_API_BUS_BLUETOOTH; break; } /* Bluetooth LE devices */ if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { - dev->bus_type = HID_API_BUS_BLUETOOTH; - hid_internal_get_ble_info(dev, dev_node); + result.bus_type = HID_API_BUS_BLUETOOTH; + result.bus_flags |= HID_API_BUS_FLAG_BLE; break; } /* I2C devices https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-support-and-power-management */ if (wcsstr(compatible_id, L"PNP0C50") != NULL) { - dev->bus_type = HID_API_BUS_I2C; + result.bus_type = HID_API_BUS_I2C; break; } /* SPI devices https://docs.microsoft.com/windows-hardware/drivers/hid/plug-and-play-for-spi */ if (wcsstr(compatible_id, L"PNP0C51") != NULL) { - dev->bus_type = HID_API_BUS_SPI; + result.bus_type = HID_API_BUS_SPI; break; } } + + result.dev_node = dev_node; + end: free(device_id); free(compatible_ids); + return result; } static char *hid_internal_UTF16toUTF8(const wchar_t *src) @@ -699,7 +868,10 @@ static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HIDD_ATTRIBUTES attrib; PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; - wchar_t string[MAX_STRING_WCHARS]; + wchar_t string[MAX_STRING_WCHARS + 1]; + ULONG len; + ULONG size; + hid_internal_detect_bus_type_result detect_bus_type_result; /* Create the record. */ dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); @@ -733,25 +905,46 @@ static struct hid_device_info *hid_internal_get_device_info(const wchar_t *path, HidD_FreePreparsedData(pp_data); } + /* detect bus type before reading string descriptors */ + detect_bus_type_result = hid_internal_detect_bus_type(path); + dev->bus_type = detect_bus_type_result.bus_type; + + len = dev->bus_type == HID_API_BUS_USB ? MAX_STRING_WCHARS_USB : MAX_STRING_WCHARS; + string[len] = L'\0'; + size = len * sizeof(wchar_t); + /* Serial Number */ string[0] = L'\0'; - HidD_GetSerialNumberString(handle, string, sizeof(string)); - string[MAX_STRING_WCHARS - 1] = L'\0'; + HidD_GetSerialNumberString(handle, string, size); dev->serial_number = _wcsdup(string); /* Manufacturer String */ string[0] = L'\0'; - HidD_GetManufacturerString(handle, string, sizeof(string)); - string[MAX_STRING_WCHARS - 1] = L'\0'; + HidD_GetManufacturerString(handle, string, size); dev->manufacturer_string = _wcsdup(string); /* Product String */ string[0] = L'\0'; - HidD_GetProductString(handle, string, sizeof(string)); - string[MAX_STRING_WCHARS - 1] = L'\0'; + HidD_GetProductString(handle, string, size); dev->product_string = _wcsdup(string); - hid_internal_get_info(path, dev); + /* now, the portion that depends on string descriptors */ + switch (dev->bus_type) { + case HID_API_BUS_USB: + hid_internal_get_usb_info(dev, detect_bus_type_result.dev_node); + break; + + case HID_API_BUS_BLUETOOTH: + if (detect_bus_type_result.bus_flags & HID_API_BUS_FLAG_BLE) + hid_internal_get_ble_info(dev, detect_bus_type_result.dev_node); + break; + + case HID_API_BUS_UNKNOWN: + case HID_API_BUS_SPI: + case HID_API_BUS_I2C: + /* shut down -Wswitch */ + break; + } return dev; } @@ -825,9 +1018,7 @@ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned shor /* Check the VID/PID to see if we should add this device to the enumeration list. */ - if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && - (product_id == 0x0 || attrib.ProductID == product_id)) { - + if (hid_internal_match_device_id(attrib.VendorID, attrib.ProductID, vendor_id, product_id)) { /* VID/PID match. Create the record. */ struct hid_device_info *tmp = hid_internal_get_device_info(device_interface, device_handle); @@ -877,6 +1068,255 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d } } +DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA event_data, DWORD event_data_size) +{ + struct hid_device_info *device = NULL; + hid_hotplug_event hotplug_event = 0; + + (void)notify; + (void)context; + (void)event_data_size; + + if (event_data == NULL || event_data->FilterType != CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE) { + return ERROR_SUCCESS; + } + + /* Lock the mutex to avoid race conditions */ + EnterCriticalSection(&hid_hotplug_context.critical_section); + + if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL) { + HANDLE read_handle; + + hotplug_event = HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED; + + /* Open read-only handle to the device */ + read_handle = open_device(event_data->u.DeviceInterface.SymbolicLink, FALSE); + + /* Check validity of read_handle. */ + if (read_handle != INVALID_HANDLE_VALUE) { + device = hid_internal_get_device_info(event_data->u.DeviceInterface.SymbolicLink, read_handle); + + /* Append to the end of the device list */ + if (hid_hotplug_context.devs != NULL) { + struct hid_device_info *last = hid_hotplug_context.devs; + while (last->next != NULL) { + last = last->next; + } + last->next = device; + } else { + hid_hotplug_context.devs = device; + } + + CloseHandle(read_handle); + } + } else if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { + char *path; + + hotplug_event = HID_API_HOTPLUG_EVENT_DEVICE_LEFT; + + path = hid_internal_UTF16toUTF8(event_data->u.DeviceInterface.SymbolicLink); + + if (path != NULL) { + /* Get and remove this device from the device list */ + for (struct hid_device_info **current = &hid_hotplug_context.devs; *current; current = &(*current)->next) { + /* Case-independent path comparison is mandatory */ + if (_stricmp((*current)->path, path) == 0) { + struct hid_device_info *next = (*current)->next; + device = *current; + *current = next; + break; + } + } + + free(path); + } + } + + if (device) { + /* Mark the critical section as IN USE, to prevent callback removal from inside a callback */ + hid_hotplug_context.mutex_in_use = 1; + + /* Call the notifications for the device */ + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; + while (*current) { + struct hid_hotplug_callback *callback = *current; + if ((callback->events & hotplug_event) && hid_internal_match_device_id(device->vendor_id, device->product_id, callback->vendor_id, callback->product_id)) { + int result = (callback->callback)(callback->handle, device, hotplug_event, callback->user_data); + + /* If the result is non-zero, we MARK the callback for future removal and proceed */ + /* We avoid changing the list until we are done calling the callbacks to simplify the process */ + if (result) { + callback->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + } + } + current = &callback->next; + } + + hid_hotplug_context.mutex_in_use = 0; + + /* Free removed device */ + if (hotplug_event == HID_API_HOTPLUG_EVENT_DEVICE_LEFT) { + free(device); + } + + /* Remove any callbacks that were marked for removal and stop the notification if none are left */ + hid_internal_hotplug_cleanup(); + } + + LeaveCriticalSection(&hid_hotplug_context.critical_section); + + return ERROR_SUCCESS; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void* user_data, hid_hotplug_callback_handle* callback_handle) +{ + struct hid_hotplug_callback* hotplug_cb; + + /* Check params */ + if (events == 0 + || (events & ~(HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT)) + || (flags & ~(HID_API_HOTPLUG_ENUMERATE)) + || callback == NULL) { + return -1; + } + + hotplug_cb = (struct hid_hotplug_callback*)calloc(1, sizeof(struct hid_hotplug_callback)); + + if (hotplug_cb == NULL) { + return -1; + } + + /* Fill out the record */ + hotplug_cb->next = NULL; + hotplug_cb->vendor_id = vendor_id; + hotplug_cb->product_id = product_id; + hotplug_cb->events = events; + hotplug_cb->user_data = user_data; + hotplug_cb->callback = callback; + + /* Ensure we are ready to actually use the mutex */ + hid_internal_hotplug_init(); + + /* Lock the mutex to avoid race conditions */ + EnterCriticalSection(&hid_hotplug_context.critical_section); + + hotplug_cb->handle = hid_hotplug_context.next_handle++; + + /* handle the unlikely case of handle overflow */ + if (hid_hotplug_context.next_handle < 0) + { + hid_hotplug_context.next_handle = 1; + } + + /* Return allocated handle */ + if (callback_handle != NULL) { + *callback_handle = hotplug_cb->handle; + } + + /* Append a new callback to the end */ + if (hid_hotplug_context.hotplug_cbs != NULL) { + struct hid_hotplug_callback *last = hid_hotplug_context.hotplug_cbs; + while (last->next != NULL) { + last = last->next; + } + last->next = hotplug_cb; + } + else { + GUID interface_class_guid; + CM_NOTIFY_FILTER notify_filter = { 0 }; + + /* Fill already connected devices so we can use this info in disconnection notification */ + hid_hotplug_context.devs = hid_enumerate(0, 0); + + hid_hotplug_context.hotplug_cbs = hotplug_cb; + + if (hid_hotplug_context.notify_handle != NULL) { + register_global_error(L"Device notification have already been registered"); + LeaveCriticalSection(&hid_hotplug_context.critical_section); + return -1; + } + + /* Retrieve HID Interface Class GUID + https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ + HidD_GetHidGuid(&interface_class_guid); + + notify_filter.cbSize = sizeof(notify_filter); + notify_filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE; + notify_filter.u.DeviceInterface.ClassGuid = interface_class_guid; + + /* Register for a HID device notification when adding the first callback */ + if (CM_Register_Notification(¬ify_filter, NULL, hid_internal_notify_callback, &hid_hotplug_context.notify_handle) != CR_SUCCESS) { + register_global_error(L"hid_hotplug_register_callback/CM_Register_Notification"); + LeaveCriticalSection(&hid_hotplug_context.critical_section); + return -1; + } + } + + /* Mark the critical section as IN USE, to prevent callback removal from inside a callback */ + unsigned char old_state = hid_hotplug_context.mutex_in_use; + hid_hotplug_context.mutex_in_use = 1; + + if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { + struct hid_device_info* device = hid_hotplug_context.devs; + /* Notify about already connected devices, if asked so */ + while (device != NULL) { + if (hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { + (*hotplug_cb->callback)(hotplug_cb->handle, device, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, hotplug_cb->user_data); + } + + device = device->next; + } + } + + hid_hotplug_context.mutex_in_use = old_state; + + /* Remove any callbacks that were marked for removal and stop the notification if none are left */ + hid_internal_hotplug_cleanup(); + + LeaveCriticalSection(&hid_hotplug_context.critical_section); + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + if (callback_handle <= 0 || !hid_hotplug_context.mutex_ready) { + return -1; + } + + /* Lock the mutex to avoid race conditions */ + EnterCriticalSection(&hid_hotplug_context.critical_section); + + if (!hid_hotplug_context.hotplug_cbs) { + LeaveCriticalSection(&hid_hotplug_context.critical_section); + return -1; + } + + /* Remove this notification */ + for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { + if ((*current)->handle == callback_handle) { + /* Check if we were already in the critical section, as we are NOT allowed to remove any callbacks if we are */ + if (hid_hotplug_context.mutex_in_use) { + /* If we are not allowed to remove the callback, we mark it as pending removal */ + (*current)->events = 0; + hid_hotplug_context.cb_list_dirty = 1; + } else { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + } + break; + } + } + + hid_internal_hotplug_cleanup(); + + LeaveCriticalSection(&hid_hotplug_context.critical_section); + + return 0; +} + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ @@ -1116,20 +1556,19 @@ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char } if (overlapped) { - if (milliseconds >= 0) { - /* See if there is any data yet. */ - res = WaitForSingleObject(ev, milliseconds); - if (res != WAIT_OBJECT_0) { - /* There was no data this time. Return zero bytes available, - but leave the Overlapped I/O running. */ - return 0; - } + /* See if there is any data yet. */ + res = WaitForSingleObject(ev, milliseconds >= 0 ? (DWORD)milliseconds : INFINITE); + if (res != WAIT_OBJECT_0) { + /* There was no data this time. Return zero bytes available, + but leave the Overlapped I/O running. */ + return 0; } - /* Either WaitForSingleObject() told us that ReadFile has completed, or - we are in non-blocking mode. Get the number of bytes read. The actual - data has been copied to the data[] array which was passed to ReadFile(). */ - res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); + /* Get the number of bytes read. The actual data has been copied to the data[] + array which was passed to ReadFile(). We must not wait here because we've + already waited on our event above, and since it's auto-reset, it will have + been reset back to unsignalled by now. */ + res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, FALSE/*don't wait now - already did on the prev step*/); } /* Set pending back to false, even if GetOverlappedResult() returned error. */ dev->read_pending = FALSE; @@ -1356,7 +1795,12 @@ int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int { BOOL res; - res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); + if (dev->device_info && dev->device_info->bus_type == HID_API_BUS_USB && maxlen > MAX_STRING_WCHARS_USB) { + string[MAX_STRING_WCHARS_USB] = L'\0'; + maxlen = MAX_STRING_WCHARS_USB; + } + + res = HidD_GetIndexedString(dev->device_handle, string_index, string, (ULONG)maxlen * sizeof(wchar_t)); if (!res) { register_winapi_error(dev, L"HidD_GetIndexedString"); return -1; diff --git a/hidapi/windows/hidapi_cfgmgr32.h b/hidapi/windows/hidapi_cfgmgr32.h index 638512a..460b2e5 100644 --- a/hidapi/windows/hidapi_cfgmgr32.h +++ b/hidapi/windows/hidapi_cfgmgr32.h @@ -57,6 +57,73 @@ typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDevi typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags); typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, PZZWSTR Buffer, ULONG BufferLen, ULONG ulFlags); +DECLARE_HANDLE(HCMNOTIFICATION); +typedef HCMNOTIFICATION* PHCMNOTIFICATION; + +typedef enum _CM_NOTIFY_FILTER_TYPE { + CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE = 0, + CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE, + CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE, + CM_NOTIFY_FILTER_TYPE_MAX +} CM_NOTIFY_FILTER_TYPE, * PCM_NOTIFY_FILTER_TYPE; + +typedef struct _CM_NOTIFY_FILTER { + DWORD cbSize; + DWORD Flags; + CM_NOTIFY_FILTER_TYPE FilterType; + DWORD Reserved; + union { + struct { + GUID ClassGuid; + } DeviceInterface; + struct { + HANDLE hTarget; + } DeviceHandle; + struct { + WCHAR InstanceId[200]; + } DeviceInstance; + } u; +} CM_NOTIFY_FILTER, * PCM_NOTIFY_FILTER; + +typedef enum _CM_NOTIFY_ACTION { + CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL = 0, + CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL, + CM_NOTIFY_ACTION_DEVICEQUERYREMOVE, + CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED, + CM_NOTIFY_ACTION_DEVICEREMOVEPENDING, + CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE, + CM_NOTIFY_ACTION_DEVICECUSTOMEVENT, + CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED, + CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED, + CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED, + CM_NOTIFY_ACTION_MAX +} CM_NOTIFY_ACTION, * PCM_NOTIFY_ACTION; + +typedef struct _CM_NOTIFY_EVENT_DATA { + CM_NOTIFY_FILTER_TYPE FilterType; + DWORD Reserved; + union { + struct { + GUID ClassGuid; + WCHAR SymbolicLink[ANYSIZE_ARRAY]; + } DeviceInterface; + struct { + GUID EventGuid; + LONG NameOffset; + DWORD DataSize; + BYTE Data[ANYSIZE_ARRAY]; + } DeviceHandle; + struct { + WCHAR InstanceId[ANYSIZE_ARRAY]; + } DeviceInstance; + } u; +} CM_NOTIFY_EVENT_DATA, * PCM_NOTIFY_EVENT_DATA; + +typedef DWORD(CALLBACK* PCM_NOTIFY_CALLBACK)(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize); + +typedef CONFIGRET(__stdcall* CM_Register_Notification_)(PCM_NOTIFY_FILTER pFilter, PVOID pContext, PCM_NOTIFY_CALLBACK pCallback, PHCMNOTIFICATION pNotifyContext); +typedef CONFIGRET(__stdcall* CM_Unregister_Notification_)(HCMNOTIFICATION NotifyContext); + // from devpkey.h DEFINE_DEVPROPKEY(DEVPKEY_NAME, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); // DEVPROP_TYPE_STRING DEFINE_DEVPROPKEY(DEVPKEY_Device_Manufacturer, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); // DEVPROP_TYPE_STRING diff --git a/hidapi/windows/hidapi_descriptor_reconstruct.c b/hidapi/windows/hidapi_descriptor_reconstruct.c index a4efb25..c76d4ea 100644 --- a/hidapi/windows/hidapi_descriptor_reconstruct.c +++ b/hidapi/windows/hidapi_descriptor_reconstruct.c @@ -46,7 +46,7 @@ static void rd_append_byte(unsigned char byte, struct rd_buffer* rpt_desc) { * * @param[in] rd_item Enumeration identifying type (Main, Global, Local) and function (e.g Usage or Report Count) of the item. * @param[in] data Data (Size depends on rd_item 0,1,2 or 4bytes). - * @param list Chained list of report descriptor bytes. + * @param rpt_desc Pointer to report descriptor buffer struct. * * @return Returns 0 if successful, -1 for error. */ @@ -235,7 +235,7 @@ int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned cha } // ************************************************************************* - // -Determine hierachy levels of each collections and store it in: + // -Determine hierarchy levels of each collections and store it in: // coll_levels[COLLECTION_INDEX] // -Determine number of direct childs of each collections and store it in: // coll_number_of_direct_childs[COLLECTION_INDEX] @@ -303,10 +303,10 @@ int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned cha } } - // ************************************************************************************************* - // Determine child collection order of the whole hierachy, based on previously determined bit ranges + // ************************************************************************************************** + // Determine child collection order of the whole hierarchy, based on previously determined bit ranges // and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX] - // ************************************************************************************************* + // ************************************************************************************************** USHORT **coll_child_order; coll_child_order = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_child_order)); { @@ -326,7 +326,7 @@ int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned cha { // Create list of child collection indices // sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild - // which seems to match teh original order, as long as no bit position needs to be considered + // which seems to match the original order, as long as no bit position needs to be considered USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild; int child_count = coll_number_of_direct_childs[collection_node_idx] - 1; coll_child_order[collection_node_idx][child_count] = child_idx; diff --git a/hidapi/windows/hidapi_descriptor_reconstruct.h b/hidapi/windows/hidapi_descriptor_reconstruct.h index 2ec8b20..4b8ca83 100644 --- a/hidapi/windows/hidapi_descriptor_reconstruct.h +++ b/hidapi/windows/hidapi_descriptor_reconstruct.h @@ -27,15 +27,17 @@ #include "hidapi_winapi.h" -#if _MSC_VER +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4200) #pragma warning(disable: 4201) +#pragma warning(disable: 4214) #endif #include #include "hidapi_hidsdi.h" +#include #define NUM_OF_HIDP_REPORT_TYPES 3 @@ -124,6 +126,13 @@ typedef struct hid_pp_link_collection_node_ { // Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end } hid_pp_link_collection_node, *phid_pp_link_collection_node; +// Note: This is risk-reduction-measure for this specific struct, as it has ULONG bit-field. +// Although very unlikely, it might still be possible that the compiler creates a memory layout that is +// not binary compatile. +// Other structs are not checked at the time of writing. +static_assert(sizeof(struct hid_pp_link_collection_node_) == 16, + "Size of struct hid_pp_link_collection_node_ not as expected. This might break binary compatibility"); + typedef struct hidp_unknown_token_ { UCHAR Token; /* Specifies the one-byte prefix of a global item. */ UCHAR Reserved[3]; @@ -212,7 +221,7 @@ typedef struct hidp_preparsed_data_ { USHORT FirstByteOfLinkCollectionArray; USHORT NumberLinkCollectionNodes; -#if defined(__MINGW32__) || defined(__CYGWIN__) +#ifndef _MSC_VER // MINGW fails with: Flexible array member in union not supported // Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html union { diff --git a/hidapi/windows/hidapi_hidsdi.h b/hidapi/windows/hidapi_hidsdi.h index 453f899..4da6b91 100644 --- a/hidapi/windows/hidapi_hidsdi.h +++ b/hidapi/windows/hidapi_hidsdi.h @@ -40,8 +40,6 @@ typedef struct _HIDD_ATTRIBUTES{ USHORT VersionNumber; } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; -typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA; - 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); diff --git a/hidapi/windows/hidapi_winapi.h b/hidapi/windows/hidapi_winapi.h index 43071f5..a991992 100644 --- a/hidapi/windows/hidapi_winapi.h +++ b/hidapi/windows/hidapi_winapi.h @@ -46,7 +46,7 @@ extern "C" { @ingroup API @param dev A device handle returned from hid_open(). - @param guid The device's container ID on return. + @param container_id The device's container ID on return. @returns This function returns 0 on success and -1 on error.