From 1b0b6acce5505aaa66b550f648c7662a03a53f7e Mon Sep 17 00:00:00 2001 From: Dimitriy Ryazantcev Date: Mon, 1 May 2023 14:20:08 +0300 Subject: [PATCH 01/25] hotplug: Add ability to register device connection/disconnection callback (#299) - initial API; - Windows backend implementation; --- hidapi/hidapi.h | 106 ++++++++++++++ hidtest/test.c | 40 ++++++ libusb/hid.c | 22 +++ linux/hid.c | 22 +++ mac/hid.c | 22 +++ windows/hid.c | 283 +++++++++++++++++++++++++++++++++++++- windows/hidapi_cfgmgr32.h | 67 +++++++++ 7 files changed, 559 insertions(+), 3 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index 744ceb0b0..1e8b5f238 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/hidtest/test.c b/hidtest/test.c index 1b6819280..dd11ab9e0 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -109,6 +109,34 @@ void print_devices_with_descriptor(struct hid_device_info *cur_dev) { } } +int device_callback( + hid_hotplug_callback_handle callback_handle, + struct hid_device_info* device, + hid_hotplug_event event, + void* user_data) +{ + (void)user_data; + + if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) + printf("Handle %d: New device is connected: %s.\n", callback_handle, device->path); + else + printf("Handle %d: Device was disconnected: %s.\n", callback_handle, device->path); + + printf("type: %04hx %04hx\n serial_number: %ls", device->vendor_id, device->product_id, device->serial_number); + printf("\n"); + printf(" Manufacturer: %ls\n", device->manufacturer_string); + printf(" Product: %ls\n", device->product_string); + printf(" Release: %hx\n", device->release_number); + printf(" Interface: %d\n", device->interface_number); + printf(" Usage (page): 0x%hx (0x%hx)\n", device->usage, device->usage_page); + printf("\n"); + + //if (device->product_id == 0x0ce6) + // return 1; + + return 0; +} + int main(int argc, char* argv[]) { (void)argc; @@ -120,6 +148,7 @@ int main(int argc, char* argv[]) wchar_t wstr[MAX_STR]; hid_device *handle; int i; + hid_hotplug_callback_handle token1, token2; struct hid_device_info *devs; @@ -144,6 +173,17 @@ int main(int argc, char* argv[]) print_devices_with_descriptor(devs); hid_free_enumeration(devs); + hid_hotplug_register_callback(0, 0, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT, HID_API_HOTPLUG_ENUMERATE, device_callback, NULL, &token1); + hid_hotplug_register_callback(0x054c, 0x0ce6, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED | HID_API_HOTPLUG_EVENT_DEVICE_LEFT, HID_API_HOTPLUG_ENUMERATE, device_callback, NULL, &token2); + + while (1) + { + + } + + hid_hotplug_deregister_callback(token2); + hid_hotplug_deregister_callback(token1); + // Set up the command buffer. memset(buf,0x00,sizeof(buf)); buf[0] = 0x01; diff --git a/libusb/hid.c b/libusb/hid.c index 188e536d5..057034088 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -863,6 +863,28 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +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) +{ + /* Stub */ + (void)vendor_id; + (void)product_id; + (void)events; + (void)flags; + (void)callback; + (void)user_data; + (void)callback_handle; + + return -1; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + /* Stub */ + (void)callback_handle; + + return -1; +} + hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { struct hid_device_info *devs, *cur_dev; diff --git a/linux/hid.c b/linux/hid.c index dda9f597a..5338d737e 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -942,6 +942,28 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +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) +{ + /* Stub */ + (void)vendor_id; + (void)product_id; + (void)events; + (void)flags; + (void)callback; + (void)user_data; + (void)callback_handle; + + return -1; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + /* Stub */ + (void)callback_handle; + + return -1; +} + hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { struct hid_device_info *devs, *cur_dev; diff --git a/mac/hid.c b/mac/hid.c index 39b3c56c0..cc4206141 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -726,6 +726,28 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +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) +{ + /* Stub */ + (void)vendor_id; + (void)product_id; + (void)events; + (void)flags; + (void)callback; + (void)user_data; + (void)callback_handle; + + return -1; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + /* Stub */ + (void)callback_handle; + + return -1; +} + 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. */ diff --git a/windows/hid.c b/windows/hid.c index 5b12ad65e..4f251bdbe 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -101,6 +101,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 +156,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__) @@ -590,6 +594,12 @@ static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_n } } +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 hid_internal_get_info(const wchar_t* interface_path, struct hid_device_info* dev) { wchar_t *device_id = NULL, *compatible_ids = NULL; @@ -825,9 +835,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 +885,275 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d } } +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 struct hid_hotplug_context { + /* Win32 notification handle */ + HCMNOTIFICATION notify_handle; + + /* 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, + .next_handle = 1, + .hotplug_cbs = NULL, + .devs = NULL +}; + +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; + } + + 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) { + /* Unable to open the device. */ + return ERROR_SUCCESS; + } + + 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) { + return ERROR_SUCCESS; + } + + /* 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) { + /* Call the notifications for the device */ + struct hid_hotplug_callback *hotplug_cb = hid_hotplug_context.hotplug_cbs; + while (hotplug_cb != NULL) { + if ((hotplug_cb->events & hotplug_event) && + hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { + struct hid_hotplug_callback* cur_hotplug_cb = hotplug_cb; + hotplug_cb = cur_hotplug_cb->next; + + if ((*cur_hotplug_cb->callback)(cur_hotplug_cb->handle, device, hotplug_event, cur_hotplug_cb->user_data)) { + hid_hotplug_deregister_callback(cur_hotplug_cb->handle); + + /* Last callback was unregistered */ + if (hid_hotplug_context.hotplug_cbs == NULL) { + break; + } + } + } + else { + hotplug_cb = hotplug_cb->next; + } + } + + /* Free removed device */ + if (hotplug_event == HID_API_HOTPLUG_EVENT_DEVICE_LEFT) { + free(device); + } + } + + 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; + + /* TODO: protect the handle by the context hotplug lock */ + 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"); + 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"); + return -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; + } + } + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + struct hid_hotplug_callback *hotplug_cb = NULL; + + if (callback_handle <= 0 || hid_hotplug_context.hotplug_cbs == NULL) { + 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) { + struct hid_hotplug_callback *next = (*current)->next; + hotplug_cb = *current; + *current = next; + break; + } + } + + if (hotplug_cb == NULL) { + return -1; + } + + free(hotplug_cb); + + /* Unregister a HID device connection notification when removing the last callback */ + if (hid_hotplug_context.hotplug_cbs == NULL) { + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + + if (hid_hotplug_context.notify_handle == NULL) { + register_global_error(L"Device notification have already been unregistered"); + return -1; + } + + if (CM_Unregister_Notification(hid_hotplug_context.notify_handle) != CR_SUCCESS) { + register_global_error(L"hid_hotplug_deregister_callback/CM_Unregister_Notification"); + return -1; + } + + hid_hotplug_context.notify_handle = NULL; + } + + 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. */ diff --git a/windows/hidapi_cfgmgr32.h b/windows/hidapi_cfgmgr32.h index 638512a8b..460b2e583 100644 --- a/windows/hidapi_cfgmgr32.h +++ b/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 From 4d851dfe73220ae46a8eb227d9675f247dbfca2e Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 28 Nov 2023 11:35:27 +0400 Subject: [PATCH 02/25] Hotplug implementation for MacOS (HidManager approach) --- hidtest/test.c | 39 ++--- mac/hid.c | 430 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 437 insertions(+), 32 deletions(-) diff --git a/hidtest/test.c b/hidtest/test.c index ec67668f7..bc3df3dba 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -134,24 +134,27 @@ int device_callback( { (void)user_data; - if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) - printf("Handle %d: New device is connected: %s.\n", callback_handle, device->path); - else - printf("Handle %d: Device was disconnected: %s.\n", callback_handle, device->path); - - printf("type: %04hx %04hx\n serial_number: %ls", device->vendor_id, device->product_id, device->serial_number); - printf("\n"); - printf(" Manufacturer: %ls\n", device->manufacturer_string); - printf(" Product: %ls\n", device->product_string); - printf(" Release: %hx\n", device->release_number); - printf(" Interface: %d\n", device->interface_number); - printf(" Usage (page): 0x%hx (0x%hx)\n", device->usage, device->usage_page); - printf("\n"); - - //if (device->product_id == 0x0ce6) - // return 1; - - return 0; + if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) + printf("Handle %d: New device is connected: %s.\n", callback_handle, device->path); + else + printf("Handle %d: Device was disconnected: %s.\n", callback_handle, device->path); + + printf("type: %04hx %04hx\n serial_number: %ls", device->vendor_id, device->product_id, device->serial_number); + printf("\n"); + printf(" Manufacturer: %ls\n", device->manufacturer_string); + printf(" Product: %ls\n", device->product_string); + printf(" Release: %hx\n", device->release_number); + printf(" Interface: %d\n", device->interface_number); + printf(" Usage (page): 0x%hx (0x%hx)\n", device->usage, device->usage_page); + printf("\n"); + + //if (device->product_id == 0x0ce6) + // return 1; + + /* Printed data might not show on the screen - force it out */ + fflush(stdout); + + return 0; } int main(int argc, char* argv[]) diff --git a/mac/hid.c b/mac/hid.c index eca5d6bc4..75038b471 100644 --- a/mac/hid.c +++ b/mac/hid.c @@ -38,6 +38,10 @@ #include "hidapi_darwin.h" +/* 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. This implementation came from Brent Priddy and was posted on @@ -457,6 +461,111 @@ 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; + + int mutex_ready; + + /* 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_cleanup() +{ + 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() +{ + if (!hid_hotplug_context.mutex_ready) { + pthread_mutex_init(&hid_hotplug_context.mutex, NULL); + hid_hotplug_context.mutex_ready = 1; + } +} + +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); +} + /* 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. */ @@ -467,9 +576,9 @@ int HID_API_EXPORT hid_init(void) if (!hid_mgr) { 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; } @@ -481,6 +590,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 */ @@ -489,6 +599,11 @@ int HID_API_EXPORT hid_exit(void) 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 void process_pending_events(void) { SInt32 res; do { @@ -544,6 +659,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; @@ -553,7 +669,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; } @@ -572,6 +690,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; @@ -804,26 +923,309 @@ 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) +{ + 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 remove the callback and proceed */ + /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ + if (result) { + struct hid_hotplug_callback *callback = *current; + *current = (*current)->next; + free(callback); + continue; + } + } + current = &callback->next; + } +} + +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) +{ + hid_device *dev = (hid_device*) context; + CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ +} + +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_close() 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) { - /* Stub */ - (void)vendor_id; - (void)product_id; - (void)events; - (void)flags; - (void)callback; - (void)user_data; - (void)callback_handle; + struct hid_hotplug_callback* hotplug_cb; - 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; + } + + 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; + } + + 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; + } + } + + 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) { - /* Stub */ - (void)callback_handle; + if (!hid_hotplug_context.mutex_ready) { + return -1; + } - 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) { + 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) From 5937ba801513fa8b5433050d9cbc9427e02d5821 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 21 Nov 2023 08:28:19 +0400 Subject: [PATCH 03/25] Linux Hotplug: Connection-callback implementation for hidraw --- linux/hid.c | 294 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 282 insertions(+), 12 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 1eaaa112a..a3e48cedd 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -35,6 +35,7 @@ #include #include #include +#include /* Linux */ #include @@ -880,6 +881,70 @@ 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; + + /* 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 = 1, + .hotplug_cbs = NULL, + .devs = NULL +}; + +static void hid_internal_hotplug_cleanup() +{ + if (hid_hotplug_context.hotplug_cbs == NULL) { + /* 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); + } +} + +static void hid_internal_hotplug_init() +{ + pthread_mutex_init(&hid_hotplug_context.mutex, NULL); +} + +static void hid_internal_hotplug_exit() +{ + pthread_mutex_lock(&hid_hotplug_context.mutex); + hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs + /* Reove all callbacks from the list */ + while(*current) + { + hid_hotplug_callback* next = (*current)->next; + free(*current); + *current = next; + } + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + pthread_mutex_destroy(&hid_hotplug_context.mutex); +} + int HID_API_EXPORT hid_init(void) { const char *locale; @@ -892,17 +957,27 @@ int HID_API_EXPORT hid_init(void) if (!locale) setlocale(LC_CTYPE, ""); + hid_internal_hotplug_init(); + 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; @@ -1004,26 +1079,222 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } +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_invoke_callbacks(struct hid_device_info *info, hid_hotplug_event event) +{ + 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 remove the callback and proceed */ + /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ + if (result) { + struct hid_hotplug_callback *callback = *current; + *current = (*current)->next; + free(callback); + continue; + } + } + current = &callback->next; + } +} + +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) +{ + while (hid_hotplug_context.monitor_fd > 0) { + fd_set fds; + struct timeval tv; + int ret; + + 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); + + /* 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) { + /* Lock the mutex so callback/device lists don't change elsewhere from here on */ + 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); + } + } + } + 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) { - /* Stub */ - (void)vendor_id; - (void)product_id; - (void)events; - (void)flags; - (void)callback; - (void)user_data; - (void)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; + + /* TODO: protect the handle by the context hotplug lock */ + 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) + { + 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); + } return -1; } int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) { - /* Stub */ - (void)callback_handle; + pthread_mutex_lock(&hid_hotplug_context.mutex); - return -1; + 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) { + struct hid_hotplug_callback *next = (*current)->next; + free(*current); + *current = next; + result = 0; + break; + } + } + + hid_internal_cleanup_hotplugs(); + + 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) @@ -1191,7 +1462,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; From c5c2379f4f913bf7a4b863d07dba0a4d98699435 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 28 Nov 2023 15:23:42 +0400 Subject: [PATCH 04/25] Fix typo --- linux/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index a3e48cedd..5d48ffbd8 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -933,7 +933,7 @@ static void hid_internal_hotplug_exit() { pthread_mutex_lock(&hid_hotplug_context.mutex); hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs - /* Reove all callbacks from the list */ + /* Remove all callbacks from the list */ while(*current) { hid_hotplug_callback* next = (*current)->next; From 9d344eb323881d75beb8c49fda6cf052000bd38a Mon Sep 17 00:00:00 2001 From: k1-801 Date: Fri, 1 Dec 2023 10:39:56 +0400 Subject: [PATCH 05/25] Fix some mutex-related errors --- linux/hid.c | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 5d48ffbd8..4768b4241 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -895,6 +895,8 @@ static struct hid_hotplug_context { pthread_t thread; pthread_mutex_t mutex; + + int mutex_ready; /* HIDAPI unique callback handle counter */ hid_hotplug_callback_handle next_handle; @@ -908,40 +910,50 @@ static struct hid_hotplug_context { .udev_ctx = NULL, .monitor_fd = -1, .next_handle = 1, + .mutex_ready = 0, .hotplug_cbs = NULL, .devs = NULL }; static void hid_internal_hotplug_cleanup() { - if (hid_hotplug_context.hotplug_cbs == NULL) { - /* 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); + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; } + + /* 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); } static void hid_internal_hotplug_init() { - pthread_mutex_init(&hid_hotplug_context.mutex, NULL); + if (!hid_hotplug_context.mutex_ready) { + pthread_mutex_init(&hid_hotplug_context.mutex, NULL); + hid_hotplug_context.mutex_ready = 1; + } } static void hid_internal_hotplug_exit() { + if (!hid_hotplug_context.mutex_ready) { + return; + } + pthread_mutex_lock(&hid_hotplug_context.mutex); hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs /* Remove all callbacks from the list */ - while(*current) - { + while(*current) { 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); } @@ -957,8 +969,6 @@ int HID_API_EXPORT hid_init(void) if (!locale) setlocale(LC_CTYPE, ""); - hid_internal_hotplug_init(); - return 0; } @@ -1220,7 +1230,12 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven hotplug_cb->user_data = user_data; hotplug_cb->callback = callback; - /* TODO: protect the handle by the context hotplug lock */ + /* 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 */ @@ -1247,6 +1262,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven hid_hotplug_context.udev_ctx = udev_new(); if(!hid_hotplug_context.udev_ctx) { + pthread_mutex_unlock(&hid_hotplug_context.mutex); return -1; } @@ -1265,11 +1281,17 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven pthread_create(&hid_hotplug_context.thread, NULL, &hotplug_thread, NULL); } - return -1; + 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) { From ed26429e2a7a1497298267aa3f9bd7fd0938872c Mon Sep 17 00:00:00 2001 From: k1-801 Date: Fri, 1 Dec 2023 11:25:27 +0400 Subject: [PATCH 06/25] Fix space --- linux/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index 4768b4241..d5c307b50 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -946,7 +946,7 @@ static void hid_internal_hotplug_exit() pthread_mutex_lock(&hid_hotplug_context.mutex); hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs /* Remove all callbacks from the list */ - while(*current) { + while (*current) { hid_hotplug_callback* next = (*current)->next; free(*current); *current = next; From 953e1d4f0c51757a1516d66872d404e1dcbab0f9 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Fri, 1 Dec 2023 16:02:44 +0400 Subject: [PATCH 07/25] Named constant for handle --- linux/hid.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index d5c307b50..187efa6bb 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -69,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; @@ -909,7 +913,7 @@ static struct hid_hotplug_context { } hid_hotplug_context = { .udev_ctx = NULL, .monitor_fd = -1, - .next_handle = 1, + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, .mutex_ready = 0, .hotplug_cbs = NULL, .devs = NULL From 7830967383e0f952f521473da97990259c4a31a2 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Sun, 3 Dec 2023 14:19:37 +0400 Subject: [PATCH 08/25] Fix compilation errors --- linux/hid.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 187efa6bb..325693d36 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -919,6 +919,18 @@ static struct hid_hotplug_context { .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_cleanup() { if (hid_hotplug_context.hotplug_cbs != NULL) { @@ -948,10 +960,10 @@ static void hid_internal_hotplug_exit() } pthread_mutex_lock(&hid_hotplug_context.mutex); - hid_hotplug_callback** current = &hid_hotplug_context.hotplug_cbs + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; /* Remove all callbacks from the list */ while (*current) { - hid_hotplug_callback* next = (*current)->next; + struct hid_hotplug_callback* next = (*current)->next; free(*current); *current = next; } @@ -1093,18 +1105,6 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } -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_invoke_callbacks(struct hid_device_info *info, hid_hotplug_event event) { struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; @@ -1316,7 +1316,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_call } } - hid_internal_cleanup_hotplugs(); + hid_internal_hotplug_cleanup(); pthread_mutex_unlock(&hid_hotplug_context.mutex); From 46f678462a0178ec9630d76bba5d7957bbef21b9 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 21 Nov 2023 03:34:40 +0400 Subject: [PATCH 09/25] Linux Hotplug: Connection callback support for libusb backend (Squashed & rebased) --- libusb/hid.c | 530 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 408 insertions(+), 122 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 99d9ea1d6..e6767ca62 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -133,6 +133,29 @@ static struct hid_api_version api_version = { static libusb_context *usb_context = NULL; +static struct hid_hotplug_context { + /* libusb callback handle */ + libusb_hotplug_callback_handle callback_handle; + + /* HIDAPI unique callback handle counter */ + hid_hotplug_callback_handle next_handle; + + pthread_mutex_t mutex; + + int mutex_ready; + + /* 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, + .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); @@ -183,9 +206,9 @@ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur } else if (num_bytes == 4) { return (rpt[cur+4] * 0x01000000 + - rpt[cur+3] * 0x00010000 + - rpt[cur+2] * 0x00000100 + - rpt[cur+1] * 0x00000001); + rpt[cur+3] * 0x00010000 + + rpt[cur+2] * 0x00000100 + + rpt[cur+1] * 0x00000001); } else return 0; @@ -196,7 +219,7 @@ static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur Usage and Usage Page that it finds in the descriptor. The return value is 0 on success and -1 on failure. */ static int get_usage(uint8_t *report_descriptor, size_t size, - unsigned short *usage_page, unsigned short *usage) + unsigned short *usage_page, unsigned short *usage) { unsigned int i = 0; int size_code; @@ -480,6 +503,58 @@ 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_cleanup() +{ + if (hid_hotplug_context.hotplug_cbs != NULL) { + return; + } + /* Cleanup connected device list */ + hid_free_enumeration(hid_hotplug_context.devs); + hid_hotplug_context.devs = NULL; + /* Disarm the libusb listener */ + libusb_hotplug_deregister_callback(usb_context, hid_hotplug_context.callback_handle); +} + +static void hid_internal_hotplug_init() +{ + if (!hid_hotplug_context.mutex_ready) { + pthread_mutex_init(&hid_hotplug_context.mutex, NULL); + hid_hotplug_context.mutex_ready = 1; + } +} + +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) { if (!usb_context) { @@ -503,11 +578,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]; @@ -784,112 +865,134 @@ static int should_enumerate_interface(unsigned short vendor_id, const struct lib return 0; } -struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +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; + + int res = libusb_get_device_descriptor(dev, &desc); + if (res < 0) + return NULL; + + 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)) { + 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 (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); + } + #endif + + 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 */ + + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + } + + if (res >= 0) { + libusb_close(handle); + handle = NULL; + } + 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; - libusb_device_handle *handle = NULL; 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) + 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 libusb_device_descriptor desc; - struct libusb_config_descriptor *conf_desc = NULL; - int j, k; - - int res = libusb_get_device_descriptor(dev, &desc); - if (res < 0) - continue; - - 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; + 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; } - - 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); - } -#endif - - 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 */ - - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - } - - if (res >= 0) { - libusb_close(handle); - handle = NULL; - } - break; - } - } /* altsettings */ - } /* interfaces */ - libusb_free_config_descriptor(conf_desc); + 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; + } } } @@ -912,26 +1015,209 @@ void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) } } -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) +static int match_libusb_to_info(libusb_device *device, struct hid_device_info* info) { - /* Stub */ - (void)vendor_id; - (void)product_id; - (void)events; - (void)flags; - (void)callback; + /* 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) +{ + 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 remove the callback and proceed */ + /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ + if (result) { + struct hid_hotplug_callback *callback = *current; + *current = (*current)->next; + free(callback); + continue; + } + } + current = &callback->next; + } +} + +static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void * user_data) +{ + (void)ctx; (void)user_data; - (void)callback_handle; - return -1; + pthread_mutex_lock(&hid_hotplug_context.mutex); + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + struct hid_device_info* info = hid_enumerate_from_libusb(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 (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(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; + } + } + } + + /* Clean up if the last callback was removed */ + hid_internal_hotplug_cleanup(); + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return 0; +} + +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 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 { + /* 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; + + /* Arm or global callback to receive ALL notifications for HID class devices */ + int result = libusb_hotplug_register_callback(usb_context, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_CLASS_HID, &hid_libusb_hotplug_callback, NULL, + &hid_hotplug_context.callback_handle); + if (result) { + /* Major failure */ + pthread_mutex_unlock(&hid_hotplug_context.mutex); + return -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; + } + } + + 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) { - /* Stub */ - (void)callback_handle; + struct hid_hotplug_callback *hotplug_cb = NULL; + + 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; + } - 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) { + struct hid_hotplug_callback *next = (*current)->next; + hotplug_cb = *current; + *current = next; + free(hotplug_cb); + break; + } + } + + hid_internal_hotplug_cleanup(); + + pthread_mutex_unlock(&hid_hotplug_context.mutex); + + return 0; } hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) @@ -944,10 +1230,10 @@ hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const cur_dev = devs; while (cur_dev) { if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { + cur_dev->product_id == product_id) { if (serial_number) { if (cur_dev->serial_number && - wcscmp(serial_number, cur_dev->serial_number) == 0) { + wcscmp(serial_number, cur_dev->serial_number) == 0) { path_to_open = cur_dev->path; break; } @@ -1060,10 +1346,10 @@ 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. */ @@ -1078,9 +1364,9 @@ static void *read_thread(void *param) /* Break out of this loop only on fatal error.*/ if (res != LIBUSB_ERROR_BUSY && - res != LIBUSB_ERROR_TIMEOUT && - res != LIBUSB_ERROR_OVERFLOW && - res != LIBUSB_ERROR_INTERRUPTED) { + res != LIBUSB_ERROR_TIMEOUT && + res != LIBUSB_ERROR_OVERFLOW && + res != LIBUSB_ERROR_INTERRUPTED) { dev->shutdown_thread = 1; break; } @@ -1250,23 +1536,23 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa 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 && - is_interrupt && is_input) { + is_interrupt && is_input) { /* Use this endpoint for INPUT */ dev->input_endpoint = ep->bEndpointAddress; dev->input_ep_max_packet_size = ep->wMaxPacketSize; } if (dev->output_endpoint == 0 && - is_interrupt && is_output) { + is_interrupt && is_output) { /* Use this endpoint for OUTPUT */ dev->output_endpoint = ep->bEndpointAddress; } @@ -1290,7 +1576,7 @@ 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(); @@ -1361,7 +1647,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(); From a015e6e0fa26e925070d7836c637593776063230 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 21 Nov 2023 10:32:49 +0400 Subject: [PATCH 10/25] Added a mutex for hotplug callback actions (squashed) --- hidtest/test.c | 41 ++++---- windows/hid.c | 263 ++++++++++++++++++++++++++++++------------------- 2 files changed, 186 insertions(+), 118 deletions(-) diff --git a/hidtest/test.c b/hidtest/test.c index ec67668f7..71c9b6dbd 100644 --- a/hidtest/test.c +++ b/hidtest/test.c @@ -127,31 +127,34 @@ void print_devices_with_descriptor(struct hid_device_info *cur_dev) { } int device_callback( - hid_hotplug_callback_handle callback_handle, - struct hid_device_info* device, - hid_hotplug_event event, - void* user_data) + hid_hotplug_callback_handle callback_handle, + struct hid_device_info* device, + hid_hotplug_event event, + void* user_data) { (void)user_data; - if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) - printf("Handle %d: New device is connected: %s.\n", callback_handle, device->path); - else - printf("Handle %d: Device was disconnected: %s.\n", callback_handle, device->path); + if (event & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) + printf("Handle %d: New device is connected: %s.\n", callback_handle, device->path); + else + printf("Handle %d: Device was disconnected: %s.\n", callback_handle, device->path); - printf("type: %04hx %04hx\n serial_number: %ls", device->vendor_id, device->product_id, device->serial_number); - printf("\n"); - printf(" Manufacturer: %ls\n", device->manufacturer_string); - printf(" Product: %ls\n", device->product_string); - printf(" Release: %hx\n", device->release_number); - printf(" Interface: %d\n", device->interface_number); - printf(" Usage (page): 0x%hx (0x%hx)\n", device->usage, device->usage_page); - printf("\n"); + printf("type: %04hx %04hx\n serial_number: %ls", device->vendor_id, device->product_id, device->serial_number); + printf("\n"); + printf(" Manufacturer: %ls\n", device->manufacturer_string); + printf(" Product: %ls\n", device->product_string); + printf(" Release: %hx\n", device->release_number); + printf(" Interface: %d\n", device->interface_number); + printf(" Usage (page): 0x%hx (0x%hx)\n", device->usage, device->usage_page); + printf("\n"); - //if (device->product_id == 0x0ce6) - // return 1; + /* Printed data might not show on the screen - force it out */ + fflush(stdout); - return 0; + //if (device->product_id == 0x0ce6) + // return 1; + + return 0; } int main(int argc, char* argv[]) diff --git a/windows/hid.c b/windows/hid.c index 0ae326f77..a1432d63a 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -78,6 +78,10 @@ typedef LONG NTSTATUS; #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, @@ -183,19 +187,44 @@ 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; + + int critical_section_ready; + + /* 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, + .critical_section_ready = 0, + .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, + .hotplug_cbs = NULL, + .devs = NULL }; static hid_device *new_hid_device() @@ -367,21 +396,87 @@ 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.critical_section_ready) { + InitializeCriticalSection(&hid_hotplug_context.critical_section); + hid_hotplug_context.critical_section_ready = 1; + } +} + int HID_API_EXPORT hid_init(void) { register_global_error(NULL); + #ifndef HIDAPI_USE_DDK if (!hidapi_initialized) { if (lookup_functions() < 0) { register_global_winapi_error(L"resolve DLL functions"); return -1; - } + } hidapi_initialized = TRUE; } #endif + return 0; } +static void hid_internal_hotplug_cleanup() +{ + /* 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; +} + +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_exit() +{ + if (!hid_hotplug_context.critical_section_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.critical_section_ready = 0; + DeleteCriticalSection(&hid_hotplug_context.critical_section); +} + int HID_API_EXPORT hid_exit(void) { #ifndef HIDAPI_USE_DDK @@ -389,6 +484,9 @@ int HID_API_EXPORT hid_exit(void) hidapi_initialized = FALSE; #endif register_global_error(NULL); + + hid_internal_hotplug_exit(); + return 0; } @@ -930,40 +1028,13 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *d } } -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 struct hid_hotplug_context { - /* Win32 notification handle */ - HCMNOTIFICATION notify_handle; - - /* 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, - .next_handle = 1, - .hotplug_cbs = NULL, - .devs = NULL -}; - -DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA event_data, DWORD event_data_size) +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; + struct hid_device_info *device = NULL; hid_hotplug_event hotplug_event = 0; (void)notify; @@ -974,6 +1045,9 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, 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; @@ -992,20 +1066,18 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, /* Append to the end of the device list */ if (hid_hotplug_context.devs != NULL) { - struct hid_device_info* last = hid_hotplug_context.devs; + struct hid_device_info *last = hid_hotplug_context.devs; while (last->next != NULL) { last = last->next; } last->next = device; - } - else { + } else { hid_hotplug_context.devs = device; } CloseHandle(read_handle); - } - else if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { - char* path; + } else if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) { + char *path; hotplug_event = HID_API_HOTPLUG_EVENT_DEVICE_LEFT; @@ -1016,10 +1088,11 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, } /* Get and remove this device from the device list */ - for (struct hid_device_info** current = &hid_hotplug_context.devs; *current; current = &(*current)->next) { + 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; + struct hid_device_info *next = (*current)->next; device = *current; *current = next; break; @@ -1031,25 +1104,21 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, if (device) { /* Call the notifications for the device */ - struct hid_hotplug_callback *hotplug_cb = hid_hotplug_context.hotplug_cbs; - while (hotplug_cb != NULL) { - if ((hotplug_cb->events & hotplug_event) && - hid_internal_match_device_id(device->vendor_id, device->product_id, hotplug_cb->vendor_id, hotplug_cb->product_id)) { - struct hid_hotplug_callback* cur_hotplug_cb = hotplug_cb; - hotplug_cb = cur_hotplug_cb->next; - - if ((*cur_hotplug_cb->callback)(cur_hotplug_cb->handle, device, hotplug_event, cur_hotplug_cb->user_data)) { - hid_hotplug_deregister_callback(cur_hotplug_cb->handle); - - /* Last callback was unregistered */ - if (hid_hotplug_context.hotplug_cbs == NULL) { - break; - } + 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 remove the callback and proceed */ + /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ + if (result) { + struct hid_hotplug_callback *callback = *current; + *current = (*current)->next; + free(callback); + continue; } } - else { - hotplug_cb = hotplug_cb->next; - } + current = &callback->next; } /* Free removed device */ @@ -1058,6 +1127,8 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, PVOID context, } } + LeaveCriticalSection(&hid_hotplug_context.critical_section); + return ERROR_SUCCESS; } @@ -1087,7 +1158,12 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven hotplug_cb->user_data = user_data; hotplug_cb->callback = callback; - /* TODO: protect the handle by the context hotplug lock */ + /* 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 */ @@ -1120,6 +1196,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven 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; } @@ -1134,6 +1211,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven /* 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; } } @@ -1150,14 +1228,22 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven } } + 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) { - struct hid_hotplug_callback *hotplug_cb = NULL; + if (callback_handle <= 0 || !hid_hotplug_context.critical_section_ready) { + return -1; + } - if (callback_handle <= 0 || hid_hotplug_context.hotplug_cbs == NULL) { + /* 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; } @@ -1165,36 +1251,15 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_call for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { if ((*current)->handle == callback_handle) { struct hid_hotplug_callback *next = (*current)->next; - hotplug_cb = *current; *current = next; + free(*current); break; } } - if (hotplug_cb == NULL) { - return -1; - } - - free(hotplug_cb); - - /* Unregister a HID device connection notification when removing the last callback */ - if (hid_hotplug_context.hotplug_cbs == NULL) { - /* Cleanup connected device list */ - hid_free_enumeration(hid_hotplug_context.devs); - hid_hotplug_context.devs = NULL; + hid_internal_hotplug_cleanup(); - if (hid_hotplug_context.notify_handle == NULL) { - register_global_error(L"Device notification have already been unregistered"); - return -1; - } - - if (CM_Unregister_Notification(hid_hotplug_context.notify_handle) != CR_SUCCESS) { - register_global_error(L"hid_hotplug_deregister_callback/CM_Unregister_Notification"); - return -1; - } - - hid_hotplug_context.notify_handle = NULL; - } + LeaveCriticalSection(&hid_hotplug_context.critical_section); return 0; } From c839de592f1bad14a0462b16ccdd0a3ca4d6f873 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 18:36:20 +0400 Subject: [PATCH 11/25] Remove unused arg warning --- linux/hid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/linux/hid.c b/linux/hid.c index 325693d36..2a3a292b4 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -1137,6 +1137,8 @@ static int match_udev_to_info(struct udev_device* raw_dev, struct hid_device_inf static void* hotplug_thread(void* user_data) { + (void) user_data; + while (hid_hotplug_context.monitor_fd > 0) { fd_set fds; struct timeval tv; From 147548c2b0ab2aaaa7c914250aa5562a715db926 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 18:38:26 +0400 Subject: [PATCH 12/25] Fix rebase error --- libusb/hid.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index e6767ca62..09eb8c472 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -55,6 +55,10 @@ #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" { #endif From f3bc39cea13390b34b8edd4f32ee16b3ebab4236 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 18:41:46 +0400 Subject: [PATCH 13/25] Fix duplicate line --- windows/hid.c | 1 - 1 file changed, 1 deletion(-) diff --git a/windows/hid.c b/windows/hid.c index a1432d63a..f3f2b6056 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1112,7 +1112,6 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, /* If the result is non-zero, we remove the callback and proceed */ /* Do not use the deregister call as it locks the mutex, and we are currently in a lock */ if (result) { - struct hid_hotplug_callback *callback = *current; *current = (*current)->next; free(callback); continue; From 758e9669500a73cf2b312d7e761765222bd271d7 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 19:41:54 +0400 Subject: [PATCH 14/25] Make cygwin happy (fix copied from libusb) --- windows/hid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/hid.c b/windows/hid.c index f3f2b6056..2c8ebccdc 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -48,6 +48,7 @@ typedef LONG NTSTATUS; #include #include #define _wcsdup wcsdup +#define _stricmp strcasecmp #endif /*#define HIDAPI_USE_DDK*/ From 5725356dba7e89d7ba999a02faf7bc26509c35da Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 12 Mar 2024 21:28:07 +0400 Subject: [PATCH 15/25] A background thread for libusb was added --- libusb/hid.c | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 09eb8c472..6e907f20f 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -138,12 +138,17 @@ static struct hid_api_version api_version = { static libusb_context *usb_context = NULL; static struct hid_hotplug_context { + /* A separate libusb context for hotplug events */ + libusb_context * context; + /* libusb callback handle */ libusb_hotplug_callback_handle callback_handle; /* HIDAPI unique callback handle counter */ hid_hotplug_callback_handle next_handle; + pthread_t thread; + pthread_mutex_t mutex; int mutex_ready; @@ -524,11 +529,15 @@ static void hid_internal_hotplug_cleanup() if (hid_hotplug_context.hotplug_cbs != NULL) { return; } + + pthread_join(hid_hotplug_context.thread, NULL); + /* Cleanup connected device list */ hid_free_enumeration(hid_hotplug_context.devs); hid_hotplug_context.devs = NULL; /* Disarm the libusb listener */ libusb_hotplug_deregister_callback(usb_context, hid_hotplug_context.callback_handle); + libusb_exit(hid_hotplug_context.context); } static void hid_internal_hotplug_init() @@ -1103,6 +1112,23 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic return 0; } +static void* hotplug_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 */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 5000; + + while(1) + { + libusb_handle_events_timeout_completed(hid_hotplug_context.context, &tv, 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) { if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { @@ -1159,19 +1185,23 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven } else { /* Fill already connected devices so we can use this info in disconnection notification */ + if(libusb_init(&hid_hotplug_context.context)) { + 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 or global callback to receive ALL notifications for HID class devices */ - int result = libusb_hotplug_register_callback(usb_context, - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - 0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_CLASS_HID, &hid_libusb_hotplug_callback, NULL, - &hid_hotplug_context.callback_handle); - if (result) { - /* Major failure */ + /* 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)) { pthread_mutex_unlock(&hid_hotplug_context.mutex); return -1; } + pthread_create(&hid_hotplug_context.thread, NULL, hotplug_thread, NULL); } if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { From 3f600b4a34915a258a7baf36e18480e2df1dea04 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Tue, 12 Mar 2024 21:31:23 +0400 Subject: [PATCH 16/25] Wait for background thread to end --- linux/hid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/linux/hid.c b/linux/hid.c index 2a3a292b4..06b32a60b 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -937,6 +937,8 @@ static void hid_internal_hotplug_cleanup() return; } + pthread_join(hid_hotplug_context.thread, NULL); + /* Cleanup connected device list */ hid_free_enumeration(hid_hotplug_context.devs); hid_hotplug_context.devs = NULL; From ac3c377326d3ad8f0e98e4b76ea92f2051d41243 Mon Sep 17 00:00:00 2001 From: k1-801 Date: Wed, 13 Mar 2024 00:42:44 +0400 Subject: [PATCH 17/25] Rewrote the queue --- libusb/hid.c | 198 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 47 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 6e907f20f..dae7d2f14 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -137,8 +137,14 @@ 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 */ + /* A separate libusb context for hotplug events: helps avoid mutual blocking with read_thread's */ libusb_context * context; /* libusb callback handle */ @@ -147,12 +153,21 @@ static struct hid_hotplug_context { /* HIDAPI unique callback handle counter */ hid_hotplug_callback_handle next_handle; - pthread_t thread; + /* A thread that fills the event queue */ + hidapi_thread_state libusb_thread; - pthread_mutex_t mutex; + /* 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 cb_mutex; int mutex_ready; + int thread_running; + + struct hid_hotplug_queue* queue; + /* Linked list of the hotplug callbacks */ struct hid_hotplug_callback *hotplug_cbs; @@ -161,8 +176,10 @@ static struct hid_hotplug_context { } hid_hotplug_context = { .next_handle = FIRST_HOTPLUG_CALLBACK_HANDLE, .mutex_ready = 0, + .thread_running = 0, + .queue = NULL, .hotplug_cbs = NULL, - .devs = NULL + .devs = NULL, }; uint16_t get_usb_code_for_current_locale(void); @@ -530,7 +547,15 @@ static void hid_internal_hotplug_cleanup() return; } - pthread_join(hid_hotplug_context.thread, NULL); + /* Mark the threads as stopped */ + hid_hotplug_context.thread_running = 0; + + /* Forcibly wake up the thread so it can shut down immediately */ + hidapi_thread_cond_signal(&hid_hotplug_context.callback_thread); + + /* Wait for both threads to stop */ + hidapi_thread_join(&hid_hotplug_context.libusb_thread); + hidapi_thread_join(&hid_hotplug_context.callback_thread); /* Cleanup connected device list */ hid_free_enumeration(hid_hotplug_context.devs); @@ -543,7 +568,9 @@ static void hid_internal_hotplug_cleanup() static void hid_internal_hotplug_init() { if (!hid_hotplug_context.mutex_ready) { - pthread_mutex_init(&hid_hotplug_context.mutex, NULL); + hidapi_thread_state_init(&hid_hotplug_context.libusb_thread); + hidapi_thread_state_init(&hid_hotplug_context.callback_thread); + pthread_mutex_init(&hid_hotplug_context.cb_mutex, NULL); hid_hotplug_context.mutex_ready = 1; } } @@ -554,7 +581,7 @@ static void hid_internal_hotplug_exit() return; } - pthread_mutex_lock(&hid_hotplug_context.mutex); + pthread_mutex_lock(&hid_hotplug_context.cb_mutex); struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; /* Remove all callbacks from the list */ while (*current) { @@ -563,9 +590,12 @@ static void hid_internal_hotplug_exit() *current = next; } hid_internal_hotplug_cleanup(); - pthread_mutex_unlock(&hid_hotplug_context.mutex); + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); hid_hotplug_context.mutex_ready = 0; - pthread_mutex_destroy(&hid_hotplug_context.mutex); + pthread_mutex_destroy(&hid_hotplug_context.cb_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) @@ -812,9 +842,9 @@ static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_de }; 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)) { + 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]) { @@ -845,9 +875,9 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de }; if (intf_desc->bInterfaceNumber == 0 && - intf_desc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && - intf_desc->bInterfaceSubClass == xb1_iface_subclass && - intf_desc->bInterfaceProtocol == xb1_iface_protocol) { + 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]) { @@ -1041,6 +1071,8 @@ static int match_libusb_to_info(libusb_device *device, struct hid_device_info* i static void hid_internal_invoke_callbacks(struct hid_device_info* info, hid_hotplug_event event) { + pthread_mutex_lock(&hid_hotplug_context.cb_mutex); + struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; while (*current) { struct hid_hotplug_callback *callback = *current; @@ -1057,6 +1089,7 @@ static void hid_internal_invoke_callbacks(struct hid_device_info* info, hid_hotp } current = &callback->next; } + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); } static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void * user_data) @@ -1064,9 +1097,56 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic (void)ctx; (void)user_data; - pthread_mutex_lock(&hid_hotplug_context.mutex); - if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { - struct hid_device_info* info = hid_enumerate_from_libusb(device, 0, 0); + /* 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); + + return 0; +} + +static void* hotplug_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 */ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 5000; + + while(hid_hotplug_context.thread_running) + { + /* 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); + } + return NULL; +} + +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 */ @@ -1089,10 +1169,10 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic } } } - else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + 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(device, *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; @@ -1105,27 +1185,42 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic } } + /* Release the libusb device - we are done with it */ + libusb_unref_device(msg->device); + /* Clean up if the last callback was removed */ + pthread_mutex_lock(&hid_hotplug_context.cb_mutex); hid_internal_hotplug_cleanup(); - pthread_mutex_unlock(&hid_hotplug_context.mutex); - - return 0; + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); } -static void* hotplug_thread(void* user_data) +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 */ - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 5000; + hidapi_timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 5000000; + + hidapi_thread_mutex_lock(&hid_hotplug_context.callback_thread); + while(hid_hotplug_context.thread_running) { + /* We use this thread's mutex to protect the queue */ + hidapi_thread_mutex_lock(&hid_hotplug_context.libusb_thread); + while(hid_hotplug_context.queue) { + process_hotplug_event(hid_hotplug_context.queue); + + /* Empty the queue */ + hid_hotplug_context.queue = hid_hotplug_context.queue->next; + } + hidapi_thread_mutex_unlock(&hid_hotplug_context.libusb_thread); - while(1) - { - libusb_handle_events_timeout_completed(hid_hotplug_context.context, &tv, NULL); + /* Make the tread fall asleep and wait for a condition to wake it up */ + hidapi_thread_cond_timedwait(&hid_hotplug_context.callback_thread, &ts); } + hidapi_thread_mutex_unlock(&hid_hotplug_context.callback_thread); + return NULL; } @@ -1160,9 +1255,9 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven /* 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); - + /* Lock the mutex to avoid race itions */ + pthread_mutex_lock(&hid_hotplug_context.cb_mutex); + hotplug_cb->handle = hid_hotplug_context.next_handle++; /* handle the unlikely case of handle overflow */ @@ -1186,7 +1281,8 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven else { /* Fill already connected devices so we can use this info in disconnection notification */ if(libusb_init(&hid_hotplug_context.context)) { - pthread_mutex_unlock(&hid_hotplug_context.mutex); + free(hotplug_cb); + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); return -1; } @@ -1198,10 +1294,18 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven 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)) { - pthread_mutex_unlock(&hid_hotplug_context.mutex); + /* 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.cb_mutex); return -1; } - pthread_create(&hid_hotplug_context.thread, NULL, hotplug_thread, NULL); + + /* Initialization succeeded! We run the threads now */ + hid_hotplug_context.thread_running = 1; + hidapi_thread_create(&hid_hotplug_context.libusb_thread, hotplug_thread, NULL); + hidapi_thread_create(&hid_hotplug_context.callback_thread, callback_thread, NULL); } if ((flags & HID_API_HOTPLUG_ENUMERATE) && (events & HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED)) { @@ -1216,7 +1320,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven } } - pthread_mutex_unlock(&hid_hotplug_context.mutex); + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); return 0; } @@ -1229,10 +1333,10 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_call return -1; } - pthread_mutex_lock(&hid_hotplug_context.mutex); + pthread_mutex_lock(&hid_hotplug_context.cb_mutex); if (hid_hotplug_context.hotplug_cbs == NULL) { - pthread_mutex_unlock(&hid_hotplug_context.mutex); + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); return -1; } @@ -1249,7 +1353,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_call hid_internal_hotplug_cleanup(); - pthread_mutex_unlock(&hid_hotplug_context.mutex); + pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); return 0; } @@ -1439,13 +1543,13 @@ static void init_xbox360(libusb_device_handle *device_handle, unsigned short idV (void)conf_desc; if ((idVendor == 0x05ac && idProduct == 0x055b) /* Gamesir-G3w */ || - idVendor == 0x0f0d /* Hori Xbox controllers */) { + 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. - */ + 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); } @@ -1465,13 +1569,13 @@ static void init_xboxone(libusb_device_handle *device_handle, unsigned short idV 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) { + 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) { + intf_desc->bInterfaceNumber == 0 && intf_desc->bAlternateSetting == 1) { bSetAlternateSetting = 1; } else if (intf_desc->bInterfaceNumber != 0 && intf_desc->bAlternateSetting == 0) { bSetAlternateSetting = 1; From 71d6e4fa1b4fb5790a8ca62b031c6ae68128f325 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Wed, 13 Mar 2024 01:03:20 +0400 Subject: [PATCH 18/25] Wake up the queue thread --- libusb/hid.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libusb/hid.c b/libusb/hid.c index dae7d2f14..94d633d3e 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1122,6 +1122,9 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic } 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; } From 6727b7d3a7e8ea097e0a278b27334163e6729655 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Wed, 13 Mar 2024 01:51:30 +0400 Subject: [PATCH 19/25] Spaces --- libusb/hid.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libusb/hid.c b/libusb/hid.c index 94d633d3e..44f7a8ff5 100644 --- a/libusb/hid.c +++ b/libusb/hid.c @@ -1101,7 +1101,7 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic libusb_ref_device(device); struct hid_hotplug_queue* msg = calloc(1, sizeof(struct hid_hotplug_queue)); - if(NULL == msg) { + if (NULL == msg) { return 0; } @@ -1112,8 +1112,8 @@ static int hid_libusb_hotplug_callback(libusb_context *ctx, libusb_device *devic /* 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) { + if (end) { + while (end->next) { end = end->next; } end->next = msg; @@ -1138,8 +1138,7 @@ static void* hotplug_thread(void* user_data) tv.tv_sec = 0; tv.tv_usec = 5000; - while(hid_hotplug_context.thread_running) - { + while (hid_hotplug_context.thread_running) { /* 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); } @@ -1208,10 +1207,10 @@ static void* callback_thread(void* user_data) ts.tv_nsec = 5000000; hidapi_thread_mutex_lock(&hid_hotplug_context.callback_thread); - while(hid_hotplug_context.thread_running) { + while (hid_hotplug_context.thread_running) { /* We use this thread's mutex to protect the queue */ hidapi_thread_mutex_lock(&hid_hotplug_context.libusb_thread); - while(hid_hotplug_context.queue) { + while (hid_hotplug_context.queue) { process_hotplug_event(hid_hotplug_context.queue); /* Empty the queue */ @@ -1283,7 +1282,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven } else { /* Fill already connected devices so we can use this info in disconnection notification */ - if(libusb_init(&hid_hotplug_context.context)) { + if (libusb_init(&hid_hotplug_context.context)) { free(hotplug_cb); pthread_mutex_unlock(&hid_hotplug_context.cb_mutex); return -1; @@ -1293,7 +1292,7 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short ven 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, + 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)) { From 3fd25f4a60dfce7759b55e6646c5f7467c154843 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 19:41:54 +0400 Subject: [PATCH 20/25] Make cygwin happy (fix copied from libusb) --- windows/hid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/hid.c b/windows/hid.c index 0ae326f77..7c3d9f842 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -48,6 +48,7 @@ typedef LONG NTSTATUS; #include #include #define _wcsdup wcsdup +#define _stricmp strcasecmp #endif /*#define HIDAPI_USE_DDK*/ From 2966ede93dcedeeb896e92e0285f79215647dde7 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 19:41:54 +0400 Subject: [PATCH 21/25] Make cygwin happy (fix copied from libusb) --- windows/hid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/hid.c b/windows/hid.c index 0ae326f77..7c3d9f842 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -48,6 +48,7 @@ typedef LONG NTSTATUS; #include #include #define _wcsdup wcsdup +#define _stricmp strcasecmp #endif /*#define HIDAPI_USE_DDK*/ From 975cbfd7982b49276a6d5d93feb0378c82cf51a1 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Thu, 14 Mar 2024 16:21:59 +0400 Subject: [PATCH 22/25] netbsd hotplug stubs --- netbsd/hid.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/netbsd/hid.c b/netbsd/hid.c index a9b841f81..bd7e75f38 100644 --- a/netbsd/hid.c +++ b/netbsd/hid.c @@ -742,6 +742,28 @@ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *de } } +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) +{ + /* Stub */ + (void)vendor_id; + (void)product_id; + (void)events; + (void)flags; + (void)callback; + (void)user_data; + (void)callback_handle; + + return -1; +} + +int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle) +{ + /* Stub */ + (void)callback_handle; + + return -1; +} + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) { struct hid_device_info *devs; From 4ef94aae4c4a1caa04e51039de9b45f54060b175 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Mon, 11 Mar 2024 19:41:54 +0400 Subject: [PATCH 23/25] Make cygwin happy (fix copied from libusb) --- windows/hid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/hid.c b/windows/hid.c index 0ae326f77..7c3d9f842 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -48,6 +48,7 @@ typedef LONG NTSTATUS; #include #include #define _wcsdup wcsdup +#define _stricmp strcasecmp #endif /*#define HIDAPI_USE_DDK*/ From 6618c11c6e6d4d421be60c4445fdf70b317210c6 Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Thu, 28 Mar 2024 05:31:19 +0400 Subject: [PATCH 24/25] Fix cleanup order --- windows/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/hid.c b/windows/hid.c index 2c8ebccdc..7f027b2d3 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1251,8 +1251,8 @@ int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_call for (struct hid_hotplug_callback **current = &hid_hotplug_context.hotplug_cbs; *current != NULL; current = &(*current)->next) { if ((*current)->handle == callback_handle) { struct hid_hotplug_callback *next = (*current)->next; - *current = next; free(*current); + *current = next; break; } } From 157ffdbea5c94d88695454f8bab9091f45c3dd4d Mon Sep 17 00:00:00 2001 From: Dmitry Kychanov Date: Fri, 5 Apr 2024 11:50:07 +0400 Subject: [PATCH 25/25] Add a cleanup call in the callback --- windows/hid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/windows/hid.c b/windows/hid.c index 7f027b2d3..ceae30472 100644 --- a/windows/hid.c +++ b/windows/hid.c @@ -1125,6 +1125,8 @@ DWORD WINAPI hid_internal_notify_callback(HCMNOTIFICATION notify, if (hotplug_event == HID_API_HOTPLUG_EVENT_DEVICE_LEFT) { free(device); } + + hid_internal_hotplug_cleanup(); } LeaveCriticalSection(&hid_hotplug_context.critical_section);