From 107eaaf56082f8941906cc054e3c93e357da28c3 Mon Sep 17 00:00:00 2001 From: Adam Honse Date: Wed, 14 Jan 2026 19:33:18 -0600 Subject: [PATCH] [WIP] Add optional HID device hotplug support (requires hidapi hotplug support) --- .../HYTEKeyboardController.cpp | 2 +- .../HyperXAlloyElite2Controller.cpp | 2 +- .../HyperXAlloyOrigins60and65Controller.cpp | 2 +- .../HyperXAlloyOriginsController.cpp | 2 +- .../HyperXAlloyOriginsCoreController.cpp | 2 +- .../HyperXMicrophoneController.cpp | 2 +- .../HyperXMicrophoneV2Controller.cpp | 2 +- .../HyperXPulsefireFPSProController.cpp | 2 +- .../HyperXPulsefireHasteController.cpp | 2 +- .../HyperXPulsefireSurgeController.cpp | 2 +- .../HyperXMousematController.cpp | 2 +- .../JGINYUEInternalUSBController.cpp | 2 +- .../JGINYUEInternalUSBV2Controller.cpp | 2 +- .../PatriotViperMouseController.cpp | 2 +- .../WushiController/WushiL50USBController.cpp | 4 +- .../ZETBladeOpticalController.cpp | 2 +- DetectionManager.cpp | 218 ++++- DetectionManager.h | 42 + OpenRGB.pro | 48 +- debian/control | 2 +- .../hidapi-hotplug-win/include/hidapi.h | 797 ++++++++++++++++++ .../include/hidapi_winapi.h | 94 +++ .../hidapi-hotplug-win/x64/hidapi-hotplug.dll | Bin 0 -> 45056 bytes .../hidapi-hotplug-win/x64/hidapi-hotplug.lib | Bin 0 -> 8500 bytes .../hidapi-hotplug-win/x86/hidapi-hotplug.dll | Bin 0 -> 35328 bytes .../hidapi-hotplug-win/x86/hidapi-hotplug.lib | Bin 0 -> 8640 bytes hidapi_wrapper/hidapi_wrapper.h | 57 +- scripts/build-appimage.sh | 11 +- 28 files changed, 1209 insertions(+), 94 deletions(-) create mode 100644 dependencies/hidapi-hotplug-win/include/hidapi.h create mode 100644 dependencies/hidapi-hotplug-win/include/hidapi_winapi.h create mode 100644 dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.dll create mode 100644 dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.lib create mode 100644 dependencies/hidapi-hotplug-win/x86/hidapi-hotplug.dll create mode 100644 dependencies/hidapi-hotplug-win/x86/hidapi-hotplug.lib diff --git a/Controllers/HYTEKeyboardController/HYTEKeyboardController.cpp b/Controllers/HYTEKeyboardController/HYTEKeyboardController.cpp index e3a2daee6..61ab16a52 100644 --- a/Controllers/HYTEKeyboardController/HYTEKeyboardController.cpp +++ b/Controllers/HYTEKeyboardController/HYTEKeyboardController.cpp @@ -26,7 +26,7 @@ HYTEKeyboardController::~HYTEKeyboardController() std::string HYTEKeyboardController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HYTEKeyboardController::GetDeviceName() diff --git a/Controllers/HyperXKeyboardController/HyperXAlloyElite2Controller/HyperXAlloyElite2Controller.cpp b/Controllers/HyperXKeyboardController/HyperXAlloyElite2Controller/HyperXAlloyElite2Controller.cpp index b108eb865..379157ee6 100644 --- a/Controllers/HyperXKeyboardController/HyperXAlloyElite2Controller/HyperXAlloyElite2Controller.cpp +++ b/Controllers/HyperXKeyboardController/HyperXAlloyElite2Controller/HyperXAlloyElite2Controller.cpp @@ -32,7 +32,7 @@ HyperXAlloyElite2Controller::~HyperXAlloyElite2Controller() std::string HyperXAlloyElite2Controller::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXAlloyElite2Controller::GetNameString() diff --git a/Controllers/HyperXKeyboardController/HyperXAlloyOrigins60and65Controller/HyperXAlloyOrigins60and65Controller.cpp b/Controllers/HyperXKeyboardController/HyperXAlloyOrigins60and65Controller/HyperXAlloyOrigins60and65Controller.cpp index 229c2747a..e3de1a593 100644 --- a/Controllers/HyperXKeyboardController/HyperXAlloyOrigins60and65Controller/HyperXAlloyOrigins60and65Controller.cpp +++ b/Controllers/HyperXKeyboardController/HyperXAlloyOrigins60and65Controller/HyperXAlloyOrigins60and65Controller.cpp @@ -27,7 +27,7 @@ HyperXAlloyOrigins60and65Controller::~HyperXAlloyOrigins60and65Controller() std::string HyperXAlloyOrigins60and65Controller::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXAlloyOrigins60and65Controller::GetNameString() diff --git a/Controllers/HyperXKeyboardController/HyperXAlloyOriginsController/HyperXAlloyOriginsController.cpp b/Controllers/HyperXKeyboardController/HyperXAlloyOriginsController/HyperXAlloyOriginsController.cpp index 742e215f1..e8eade6e3 100644 --- a/Controllers/HyperXKeyboardController/HyperXAlloyOriginsController/HyperXAlloyOriginsController.cpp +++ b/Controllers/HyperXKeyboardController/HyperXAlloyOriginsController/HyperXAlloyOriginsController.cpp @@ -30,7 +30,7 @@ HyperXAlloyOriginsController::~HyperXAlloyOriginsController() std::string HyperXAlloyOriginsController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXAlloyOriginsController::GetNameString() diff --git a/Controllers/HyperXKeyboardController/HyperXAlloyOriginsCoreController/HyperXAlloyOriginsCoreController.cpp b/Controllers/HyperXKeyboardController/HyperXAlloyOriginsCoreController/HyperXAlloyOriginsCoreController.cpp index 554c82477..fb2a529c3 100644 --- a/Controllers/HyperXKeyboardController/HyperXAlloyOriginsCoreController/HyperXAlloyOriginsCoreController.cpp +++ b/Controllers/HyperXKeyboardController/HyperXAlloyOriginsCoreController/HyperXAlloyOriginsCoreController.cpp @@ -42,7 +42,7 @@ HyperXAlloyOriginsCoreController::~HyperXAlloyOriginsCoreController() std::string HyperXAlloyOriginsCoreController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXAlloyOriginsCoreController::GetNameString() diff --git a/Controllers/HyperXMicrophoneController/HyperXMicrophoneController.cpp b/Controllers/HyperXMicrophoneController/HyperXMicrophoneController.cpp index 458d3e5b6..b501e3240 100644 --- a/Controllers/HyperXMicrophoneController/HyperXMicrophoneController.cpp +++ b/Controllers/HyperXMicrophoneController/HyperXMicrophoneController.cpp @@ -37,7 +37,7 @@ HyperXMicrophoneController::~HyperXMicrophoneController() std::string HyperXMicrophoneController::GetDeviceLocation() { - return(location); + return("HID: " + location); } std::string HyperXMicrophoneController::GetNameString() diff --git a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp index 9f93a2bc6..d88d4bc87 100644 --- a/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp +++ b/Controllers/HyperXMicrophoneV2Controller/HyperXMicrophoneV2Controller.cpp @@ -37,7 +37,7 @@ HyperXMicrophoneV2Controller::~HyperXMicrophoneV2Controller() std::string HyperXMicrophoneV2Controller::GetDeviceLocation() { - return(location); + return("HID: " + location); } std::string HyperXMicrophoneV2Controller::GetNameString() diff --git a/Controllers/HyperXMouseController/HyperXPulsefireFPSProController/HyperXPulsefireFPSProController.cpp b/Controllers/HyperXMouseController/HyperXPulsefireFPSProController/HyperXPulsefireFPSProController.cpp index 1907648a2..a0a54be48 100644 --- a/Controllers/HyperXMouseController/HyperXPulsefireFPSProController/HyperXPulsefireFPSProController.cpp +++ b/Controllers/HyperXMouseController/HyperXPulsefireFPSProController/HyperXPulsefireFPSProController.cpp @@ -27,7 +27,7 @@ HyperXPulsefireFPSProController::~HyperXPulsefireFPSProController() std::string HyperXPulsefireFPSProController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXPulsefireFPSProController::GetNameString() diff --git a/Controllers/HyperXMouseController/HyperXPulsefireHasteController/HyperXPulsefireHasteController.cpp b/Controllers/HyperXMouseController/HyperXPulsefireHasteController/HyperXPulsefireHasteController.cpp index 23b2b5448..122d9bfb5 100644 --- a/Controllers/HyperXMouseController/HyperXPulsefireHasteController/HyperXPulsefireHasteController.cpp +++ b/Controllers/HyperXMouseController/HyperXPulsefireHasteController/HyperXPulsefireHasteController.cpp @@ -27,7 +27,7 @@ HyperXPulsefireHasteController::~HyperXPulsefireHasteController() std::string HyperXPulsefireHasteController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXPulsefireHasteController::GetNameString() diff --git a/Controllers/HyperXMouseController/HyperXPulsefireSurgeController/HyperXPulsefireSurgeController.cpp b/Controllers/HyperXMouseController/HyperXPulsefireSurgeController/HyperXPulsefireSurgeController.cpp index 917cd46cb..b11cb310f 100644 --- a/Controllers/HyperXMouseController/HyperXPulsefireSurgeController/HyperXPulsefireSurgeController.cpp +++ b/Controllers/HyperXMouseController/HyperXPulsefireSurgeController/HyperXPulsefireSurgeController.cpp @@ -27,7 +27,7 @@ HyperXPulsefireSurgeController::~HyperXPulsefireSurgeController() std::string HyperXPulsefireSurgeController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXPulsefireSurgeController::GetNameString() diff --git a/Controllers/HyperXMousematController/HyperXMousematController.cpp b/Controllers/HyperXMousematController/HyperXMousematController.cpp index 91320c8e6..97dd4e402 100644 --- a/Controllers/HyperXMousematController/HyperXMousematController.cpp +++ b/Controllers/HyperXMousematController/HyperXMousematController.cpp @@ -28,7 +28,7 @@ HyperXMousematController::~HyperXMousematController() std::string HyperXMousematController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string HyperXMousematController::GetNameString() diff --git a/Controllers/JGINYUEInternalUSBController/JGINYUEInternalUSBController.cpp b/Controllers/JGINYUEInternalUSBController/JGINYUEInternalUSBController.cpp index 96f5207e6..2785afeae 100644 --- a/Controllers/JGINYUEInternalUSBController/JGINYUEInternalUSBController.cpp +++ b/Controllers/JGINYUEInternalUSBController/JGINYUEInternalUSBController.cpp @@ -54,7 +54,7 @@ unsigned int JGINYUEInternalUSBController::GetZoneCount() std::string JGINYUEInternalUSBController::GetDeviceLocation() { - return("HID:" + location); + return("HID: " + location); } std::string JGINYUEInternalUSBController::GetDeviceName() diff --git a/Controllers/JGINYUEInternalUSBV2Controller/JGINYUEInternalUSBV2Controller.cpp b/Controllers/JGINYUEInternalUSBV2Controller/JGINYUEInternalUSBV2Controller.cpp index 3afda6f10..adeb486fa 100644 --- a/Controllers/JGINYUEInternalUSBV2Controller/JGINYUEInternalUSBV2Controller.cpp +++ b/Controllers/JGINYUEInternalUSBV2Controller/JGINYUEInternalUSBV2Controller.cpp @@ -66,7 +66,7 @@ unsigned int JGINYUEInternalUSBV2Controller::GetZoneCount() std::string JGINYUEInternalUSBV2Controller::GetDeviceLocation() { - return("HID:" + location); + return("HID: " + location); } std::string JGINYUEInternalUSBV2Controller::GetDeviceName() diff --git a/Controllers/PatriotViperMouseController/PatriotViperMouseController.cpp b/Controllers/PatriotViperMouseController/PatriotViperMouseController.cpp index f4fdbb507..dfdcb9881 100644 --- a/Controllers/PatriotViperMouseController/PatriotViperMouseController.cpp +++ b/Controllers/PatriotViperMouseController/PatriotViperMouseController.cpp @@ -28,7 +28,7 @@ PatriotViperMouseController::~PatriotViperMouseController() std::string PatriotViperMouseController::GetLocation() { - return("HID " + location); + return("HID: " + location); } std::string PatriotViperMouseController::GetName() diff --git a/Controllers/WushiController/WushiL50USBController.cpp b/Controllers/WushiController/WushiL50USBController.cpp index 45949a27f..6f017d1b1 100644 --- a/Controllers/WushiController/WushiL50USBController.cpp +++ b/Controllers/WushiController/WushiL50USBController.cpp @@ -26,12 +26,12 @@ WushiL50USBController::~WushiL50USBController() std::string WushiL50USBController::getName() { - return name; + return(name); } std::string WushiL50USBController::getLocation() { - return location; + return("HID: " + location); } std::string WushiL50USBController::GetSerialString() diff --git a/Controllers/ZETKeyboardController/ZETBladeOpticalController.cpp b/Controllers/ZETKeyboardController/ZETBladeOpticalController.cpp index 9467f3448..802a881fc 100644 --- a/Controllers/ZETKeyboardController/ZETBladeOpticalController.cpp +++ b/Controllers/ZETKeyboardController/ZETBladeOpticalController.cpp @@ -43,7 +43,7 @@ ZETBladeOpticalController::~ZETBladeOpticalController() std::string ZETBladeOpticalController::GetDeviceLocation() { - return("HID " + location); + return("HID: " + location); } std::string ZETBladeOpticalController::GetNameString() diff --git a/DetectionManager.cpp b/DetectionManager.cpp index b6803b886..941bf2918 100644 --- a/DetectionManager.cpp +++ b/DetectionManager.cpp @@ -76,14 +76,18 @@ const char* UDEV_MUTLI = QT_TRANSLATE_NOOP("DetectionManager", const hidapi_wrapper default_hidapi_wrapper = { NULL, - (hidapi_wrapper_send_feature_report) hid_send_feature_report, - (hidapi_wrapper_get_feature_report) hid_get_feature_report, - (hidapi_wrapper_get_serial_number_string) hid_get_serial_number_string, - (hidapi_wrapper_open_path) hid_open_path, - (hidapi_wrapper_enumerate) hid_enumerate, - (hidapi_wrapper_free_enumeration) hid_free_enumeration, - (hidapi_wrapper_close) hid_close, - (hidapi_wrapper_error) hid_error + (hidapi_wrapper_send_feature_report) hid_send_feature_report, + (hidapi_wrapper_get_feature_report) hid_get_feature_report, + (hidapi_wrapper_get_serial_number_string) hid_get_serial_number_string, + (hidapi_wrapper_open_path) hid_open_path, + (hidapi_wrapper_enumerate) hid_enumerate, + (hidapi_wrapper_free_enumeration) hid_free_enumeration, + (hidapi_wrapper_close) hid_close, + (hidapi_wrapper_error) hid_error, +#if(HID_HOTPLUG_ENABLED) + (hidapi_wrapper_hotplug_register_callback) hid_hotplug_register_callback, + (hidapi_wrapper_hotplug_deregister_callback) hid_hotplug_deregister_callback, +#endif }; /*---------------------------------------------------------*\ @@ -143,6 +147,12 @@ DetectionManager::DetectionManager() dynamic_detectors_processed = false; initial_detection = true; +#ifdef __linux__ +#ifdef __GLIBC__ + hidapi_libusb_handle = nullptr; +#endif +#endif + /*-----------------------------------------------------*\ | Start the background thread | \*-----------------------------------------------------*/ @@ -711,7 +721,12 @@ void DetectionManager::BackgroundDetectDevices() } else { +#if(HID_HOTPLUG_ENABLED) + StopHIDHotplug(); + StartHIDHotplug(); +#else BackgroundDetectHIDDevices(hid_devices, detector_settings); +#endif } /*-----------------------------------------------------*\ @@ -1141,31 +1156,12 @@ void DetectionManager::BackgroundDetectHIDDevicesWrapped(hid_device_info* hid_de LOG_INFO("| Detecting libusb HID devices |"); LOG_INFO("------------------------------------------------------"); - void * dyn_handle = NULL; - hidapi_wrapper wrapper; - /*-----------------------------------------------------*\ | Load the libhidapi-libusb library | \*-----------------------------------------------------*/ - if((dyn_handle = dlopen("libhidapi-libusb.so", RTLD_NOW | RTLD_NODELETE | RTLD_DEEPBIND))) + if(hidapi_libusb_handle) { - /*-------------------------------------------------*\ - | Create a wrapper with the libusb functions | - \*-------------------------------------------------*/ - wrapper = - { - .dyn_handle = dyn_handle, - .hid_send_feature_report = (hidapi_wrapper_send_feature_report) dlsym(dyn_handle,"hid_send_feature_report"), - .hid_get_feature_report = (hidapi_wrapper_get_feature_report) dlsym(dyn_handle,"hid_get_feature_report"), - .hid_get_serial_number_string = (hidapi_wrapper_get_serial_number_string) dlsym(dyn_handle,"hid_get_serial_number_string"), - .hid_open_path = (hidapi_wrapper_open_path) dlsym(dyn_handle,"hid_open_path"), - .hid_enumerate = (hidapi_wrapper_enumerate) dlsym(dyn_handle,"hid_enumerate"), - .hid_free_enumeration = (hidapi_wrapper_free_enumeration) dlsym(dyn_handle,"hid_free_enumeration"), - .hid_close = (hidapi_wrapper_close) dlsym(dyn_handle,"hid_close"), - .hid_error = (hidapi_wrapper_error) dlsym(dyn_handle,"hid_free_enumeration") - }; - - hid_devices = wrapper.hid_enumerate(0, 0); + hid_devices = hidapi_libusb_wrapper.hid_enumerate(0, 0); hid_device_info* current_hid_device = hid_devices; @@ -1177,7 +1173,7 @@ void DetectionManager::BackgroundDetectHIDDevicesWrapped(hid_device_info* hid_de while(current_hid_device) { - RunHIDWrappedDetector(&wrapper, current_hid_device, detector_settings); + RunHIDWrappedDetector(&hidapi_libusb_wrapper, current_hid_device, detector_settings); /*---------------------------------------------*\ | Update detection percent | @@ -1195,7 +1191,7 @@ void DetectionManager::BackgroundDetectHIDDevicesWrapped(hid_device_info* hid_de /*-------------------------------------------------*\ | Done using the device list, free it | \*-------------------------------------------------*/ - wrapper.hid_free_enumeration(hid_devices); + hidapi_libusb_wrapper.hid_free_enumeration(hid_devices); } } #endif @@ -1268,6 +1264,48 @@ void DetectionManager::BackgroundHIDInit() int hid_status = hid_init(); LOG_DEBUG("[%s] Initializing HID interfaces: %s", DETECTIONMANAGER, ((hid_status == 0) ? "Success" : "Failed")); + +#ifdef __linux__ +#ifdef __GLIBC__ + /*-----------------------------------------------------*\ + | Load the libhidapi-libusb library | + \*-----------------------------------------------------*/ +#if(HID_HOTPLUG_ENABLED) + hidapi_libusb_handle = dlopen("libhidapi-hotplug-libusb.so", RTLD_NOW | RTLD_NODELETE | RTLD_DEEPBIND); +#else + hidapi_libusb_handle = dlopen("libhidapi-libusb.so", RTLD_NOW | RTLD_NODELETE | RTLD_DEEPBIND); +#endif + + if(hidapi_libusb_handle) + { + LOG_DEBUG("[%s] Loaded libusb HID library", DETECTIONMANAGER); + + /*-------------------------------------------------*\ + | Create a wrapper with the libusb functions | + \*-------------------------------------------------*/ + hidapi_libusb_wrapper = + { + .dyn_handle = hidapi_libusb_handle, + .hid_send_feature_report = (hidapi_wrapper_send_feature_report) dlsym(hidapi_libusb_handle,"hid_send_feature_report"), + .hid_get_feature_report = (hidapi_wrapper_get_feature_report) dlsym(hidapi_libusb_handle,"hid_get_feature_report"), + .hid_get_serial_number_string = (hidapi_wrapper_get_serial_number_string) dlsym(hidapi_libusb_handle,"hid_get_serial_number_string"), + .hid_open_path = (hidapi_wrapper_open_path) dlsym(hidapi_libusb_handle,"hid_open_path"), + .hid_enumerate = (hidapi_wrapper_enumerate) dlsym(hidapi_libusb_handle,"hid_enumerate"), + .hid_free_enumeration = (hidapi_wrapper_free_enumeration) dlsym(hidapi_libusb_handle,"hid_free_enumeration"), + .hid_close = (hidapi_wrapper_close) dlsym(hidapi_libusb_handle,"hid_close"), + .hid_error = (hidapi_wrapper_error) dlsym(hidapi_libusb_handle,"hid_free_enumeration"), +#if(HID_HOTPLUG_ENABLED) + .hid_hotplug_register_callback = (hidapi_wrapper_hotplug_register_callback) dlsym(hidapi_libusb_handle,"hid_hotplug_register_callback"), + .hid_hotplug_deregister_callback = (hidapi_wrapper_hotplug_deregister_callback) dlsym(hidapi_libusb_handle,"hid_hotplug_deregister_callback"), +#endif + }; + } + else + { + LOG_DEBUG("[%s] Failed to load libusb HID library", DETECTIONMANAGER); + } +#endif +#endif } /*---------------------------------------------------------*\ @@ -1320,6 +1358,10 @@ void DetectionManager::RunHIDDetector(hid_device_info* current_hid_device, json for(std::size_t detected_controller_idx = 0; detected_controller_idx < detected_controllers.size(); detected_controller_idx++) { +#if(HID_HOTPLUG_ENABLED) + int handle; + hid_hotplug_register_callback(current_hid_device->vendor_id, current_hid_device->product_id, HID_API_HOTPLUG_EVENT_DEVICE_LEFT, 0, &DetectionManager::UnplugCallbackFunction, detected_controllers[detected_controller_idx], &handle); +#endif RegisterRGBController(detected_controllers[detected_controller_idx]); } } @@ -1376,6 +1418,10 @@ void DetectionManager::RunHIDWrappedDetector(const hidapi_wrapper* wrapper, hid_ for(std::size_t detected_controller_idx = 0; detected_controller_idx < detected_controllers.size(); detected_controller_idx++) { +#if(HID_HOTPLUG_ENABLED) + int handle; + wrapper->hid_hotplug_register_callback(current_hid_device->vendor_id, current_hid_device->product_id, HID_API_HOTPLUG_EVENT_DEVICE_LEFT, 0, &DetectionManager::UnplugCallbackFunction, detected_controllers[detected_controller_idx], &handle); +#endif RegisterRGBController(detected_controllers[detected_controller_idx]); } } @@ -1392,6 +1438,10 @@ void DetectionManager::ProcessCleanup() { WaitForDetection(); +#if(HID_HOTPLUG_ENABLED) + StopHIDHotplug(); +#endif + /*-----------------------------------------------------*\ | Make a copy of the list so that the controllers can | | be deleted after the list is cleared | @@ -1619,6 +1669,112 @@ void DetectionManager::UpdateDetectorSettings() } } +#if(HID_HOTPLUG_ENABLED) +/*---------------------------------------------------------*\ +| HID hotplug management functions | +\*---------------------------------------------------------*/ +void DetectionManager::StartHIDHotplug() +{ + hid_hotplug_register_callback(0, 0, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, HID_API_HOTPLUG_ENUMERATE, &DetectionManager::HotplugCallbackFunction, nullptr, &hotplug_callback_handle); + +#ifdef __linux__ +#ifdef __GLIBC__ + if(hidapi_libusb_handle != nullptr) + { + hidapi_libusb_wrapper.hid_hotplug_register_callback(0, 0, HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED, HID_API_HOTPLUG_ENUMERATE, &DetectionManager::WrappedHotplugCallbackFunction, nullptr, &libusb_hotplug_callback_handle); + } +#endif +#endif +} + +void DetectionManager::StopHIDHotplug() +{ + hid_hotplug_deregister_callback(hotplug_callback_handle); + +#ifdef __linux__ +#ifdef __GLIBC__ + if(hidapi_libusb_handle != nullptr) + { + hidapi_libusb_wrapper.hid_hotplug_deregister_callback(libusb_hotplug_callback_handle); + } +#endif +#endif +} + +/*---------------------------------------------------------*\ +| HID hotplug callback functions | +\*---------------------------------------------------------*/ +int DetectionManager::HotplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data) +{ + /*-----------------------------------------------------*\ + | Open device disable list and read in disabled | + | device strings | + \*-----------------------------------------------------*/ + json detector_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("Detectors"); + DetectionManager* dm = DetectionManager::get(); + + if(event == HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) + { + LOG_INFO("[%s] HID device connected: [%04x:%04x]", DETECTIONMANAGER, device->vendor_id, device->product_id); + + dm->RunHIDDetector(device, detector_settings); + dm->RunHIDWrappedDetector(&default_hidapi_wrapper, device, detector_settings); + } + + return 0; +} + +int DetectionManager::UnplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data) +{ + DetectionManager* dm = DetectionManager::get(); + + if(event == HID_API_HOTPLUG_EVENT_DEVICE_LEFT) + { + LOG_INFO("[%s] HID device disconnected: [%04x:%04x]", DETECTIONMANAGER, device->vendor_id, device->product_id); + + std::string location = "HID: "; + location.append(device->path); + + // User data is the pointer to the controller being removed + RGBController* controller = (RGBController*)(user_data); + + LOG_DEBUG("Checking device location:\"%s\":\"%s\"", location.c_str(), controller->location.c_str()); + + if(controller && controller->location == location) + { + dm->UnregisterRGBController(controller); + delete controller; + return 1; + } + } + return 0; +} + +#ifdef __linux__ +#ifdef __GLIBC__ +int DetectionManager::WrappedHotplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data) +{ + /*-----------------------------------------------------*\ + | Open device disable list and read in disabled | + | device strings | + \*-----------------------------------------------------*/ + json detector_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("Detectors"); + DetectionManager* dm = DetectionManager::get(); + + if(event == HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED) + { + LOG_INFO("[%s] libusb HID device connected: [%04x:%04x]", DETECTIONMANAGER, device->vendor_id, device->product_id); + + dm->RunHIDDetector(device, detector_settings); + dm->RunHIDWrappedDetector(&DetectionManager::get()->hidapi_libusb_wrapper, device, detector_settings); + } + + return 0; +} +#endif +#endif +#endif + /*---------------------------------------------------------*\ | Function for signalling DetectionManager updates to | | registered callbacks | diff --git a/DetectionManager.h b/DetectionManager.h index 49358bfec..d978d0067 100644 --- a/DetectionManager.h +++ b/DetectionManager.h @@ -228,6 +228,28 @@ private: std::vector dynamic_detector_strings; std::vector pre_detection_hooks; +#ifdef __linux__ +#ifdef __GLIBC__ + /*-----------------------------------------------------*\ + | Wrapped libusb hidapi handles | + \*-----------------------------------------------------*/ + void * hidapi_libusb_handle; + hidapi_wrapper hidapi_libusb_wrapper; +#endif +#endif + + /*-----------------------------------------------------*\ + | HID Hotplug handles | + \*-----------------------------------------------------*/ +#if(HID_HOTPLUG_ENABLED) + hid_hotplug_callback_handle hotplug_callback_handle; +#ifdef __linux__ +#ifdef __GLIBC__ + hid_hotplug_callback_handle libusb_hotplug_callback_handle; +#endif +#endif +#endif + /*-----------------------------------------------------*\ | Detection Callbacks | \*-----------------------------------------------------*/ @@ -303,6 +325,26 @@ private: void ProcessPreDetectionHooks(); void UpdateDetectorSettings(); +#if(HID_HOTPLUG_ENABLED) + /*-----------------------------------------------------*\ + | HID hotplug management functions | + \*-----------------------------------------------------*/ + void StartHIDHotplug(); + void StopHIDHotplug(); + + /*-----------------------------------------------------*\ + | HID hotplug callback functions | + \*-----------------------------------------------------*/ + static int HotplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data); + static int UnplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data); + +#ifdef __linux__ +#ifdef __GLIBC__ + static int WrappedHotplugCallbackFunction(hid_hotplug_callback_handle callback_handle, hid_device_info *device, hid_hotplug_event event, void *user_data); +#endif +#endif +#endif + /*-----------------------------------------------------*\ | Function for signalling DetectionManager updates to | | registered callbacks | diff --git a/OpenRGB.pro b/OpenRGB.pro index b4792725d..c989ad2e7 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -328,7 +328,7 @@ TRANSLATIONS += win32:QMAKE_CXXFLAGS += /utf-8 win32:INCLUDEPATH += \ dependencies/display-library/include \ - dependencies/hidapi-win/include \ + dependencies/hidapi-hotplug-win/include \ dependencies/libusb-1.0.27/include \ dependencies/mbedtls-3.2.1/include \ dependencies/NVFC \ @@ -374,7 +374,7 @@ win32:contains(QMAKE_TARGET.arch, x86_64) { -lws2_32 \ -liphlpapi \ -L"$$PWD/dependencies/libusb-1.0.27/VS2019/MS64/dll" -llibusb-1.0 \ - -L"$$PWD/dependencies/hidapi-win/x64/" -lhidapi \ + -L"$$PWD/dependencies/hidapi-hotplug-win/x64/" -lhidapi-hotplug \ -L"$$PWD/dependencies/mbedtls-3.2.1/lib/x64/" -lmbedcrypto -lmbedtls -lmbedx509 \ -L"$$PWD/dependencies/PawnIO/" -lPawnIOLib \ } @@ -387,7 +387,7 @@ win32:contains(QMAKE_TARGET.arch, x86) { -lws2_32 \ -liphlpapi \ -L"$$PWD/dependencies/libusb-1.0.27/VS2019/MS32/dll" -llibusb-1.0 \ - -L"$$PWD/dependencies/hidapi-win/x86/" -lhidapi \ + -L"$$PWD/dependencies/hidapi-hotplug-win/x86/" -lhidapi-hotplug \ -L"$$PWD/dependencies/mbedtls-3.2.1/lib/x86/" -lmbedcrypto -lmbedtls -lmbedx509 \ } @@ -401,6 +401,7 @@ win32:DEFINES += _CRT_SECURE_NO_WARNINGS \ _WINSOCK_DEPRECATED_NO_WARNINGS \ WIN32_LEAN_AND_MEAN \ + HID_HOTPLUG_ENABLED=1 \ win32:RC_ICONS += \ qt/OpenRGB.ico @@ -432,7 +433,7 @@ win32:UI_DIR = _intermediate_$$DESTDIR/.ui win32:contains(QMAKE_TARGET.arch, x86_64) { copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/libusb-1.0.27/VS2019/MS64/dll/libusb-1.0.dll)\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) - copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/hidapi-win/x64/hidapi.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) + copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/PawnIOLib.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusPIIX4.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/PawnIO/modules/SmbusI801.bin )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) @@ -446,7 +447,7 @@ win32:contains(QMAKE_TARGET.arch, x86_64) { win32:contains(QMAKE_TARGET.arch, x86) { copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/libusb-1.0.27/VS2019/MS32/dll/libusb-1.0.dll)\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) - copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/hidapi-win/x86/hidapi.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) + copydata.commands += $(COPY_FILE) \"$$shell_path($$PWD/dependencies/hidapi-hotplug-win/x86/hidapi-hotplug.dll )\" \"$$shell_path($$DESTDIR)\" $$escape_expand(\n\t) first.depends = $(first) copydata export(first.depends) @@ -501,22 +502,28 @@ contains(QMAKE_PLATFORM, linux) { # Determine which hidapi to use based on availability # # Prefer hidraw backend, then libusb # #-------------------------------------------------------------------------------------------# - packagesExist(hidapi-hidraw) { - PKGCONFIG += hidapi-hidraw - - #---------------------------------------------------------------------------------------# - # hidapi-hidraw >= 0.10.1 supports USAGE/USAGE_PAGE # - # Define USE_HID_USAGE if hidapi-hidraw supports it # - #---------------------------------------------------------------------------------------# - HIDAPI_HIDRAW_VERSION = $$system($$PKG_CONFIG --modversion hidapi-hidraw) - if(versionAtLeast(HIDAPI_HIDRAW_VERSION, "0.10.1")) { - DEFINES += USE_HID_USAGE - } + packagesExist(hidapi-hotplug-hidraw) { + PKGCONFIG += hidapi-hotplug-hidraw + DEFINES += USE_HID_USAGE=1 \ + HID_HOTPLUG_ENABLED=1 } else { - packagesExist(hidapi-libusb) { - PKGCONFIG += hidapi-libusb + packagesExist(hidapi-hidraw) { + PKGCONFIG += hidapi-hidraw + + #-----------------------------------------------------------------------------------# + # hidapi-hidraw >= 0.10.1 supports USAGE/USAGE_PAGE # + # Define USE_HID_USAGE if hidapi-hidraw supports it # + #-----------------------------------------------------------------------------------# + HIDAPI_HIDRAW_VERSION = $$system($$PKG_CONFIG --modversion hidapi-hidraw) + if(versionAtLeast(HIDAPI_HIDRAW_VERSION, "0.10.1")) { + DEFINES += USE_HID_USAGE + } } else { - PKGCONFIG += hidapi + packagesExist(hidapi-libusb) { + PKGCONFIG += hidapi-libusb + } else { + PKGCONFIG += hidapi + } } } @@ -710,10 +717,11 @@ macx { PKGCONFIG += \ libusb-1.0 \ - hidapi + hidapi-hotplug DEFINES += \ USE_HID_USAGE \ + HID_HOTPLUG_ENABLED=1 \ QMAKE_CXXFLAGS += \ -Wno-narrowing \ diff --git a/debian/control b/debian/control index 7f0335fab..092c7e98f 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Build-Depends: qt6-tools-dev-tools, qt5-qmake, libusb-1.0-0-dev, - libhidapi-dev, + libhidapi-hotplug-dev, libmbedtls-dev, Homepage: https://gitlab.com/CalcProgrammer1/OpenRGB diff --git a/dependencies/hidapi-hotplug-win/include/hidapi.h b/dependencies/hidapi-hotplug-win/include/hidapi.h new file mode 100644 index 000000000..089d330ac --- /dev/null +++ b/dependencies/hidapi-hotplug-win/include/hidapi.h @@ -0,0 +1,797 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + libusb/hidapi Team + + Copyright 2023, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +/* #480: this is to be refactored properly for v1.0 */ +#ifdef _WIN32 + #ifndef HID_API_NO_EXPORT_DEFINE + #define HID_API_EXPORT __declspec(dllexport) + #endif +#endif +#ifndef HID_API_EXPORT + #define HID_API_EXPORT /**< API export macro */ +#endif +/* To be removed in v1.0 */ +#define HID_API_CALL /**< API call macro */ + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +/** @brief Static/compile-time major version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MAJOR 0 +/** @brief Static/compile-time minor version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_MINOR 16 +/** @brief Static/compile-time patch version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_PATCH 0 + +/* Helper macros */ +#define HID_API_AS_STR_IMPL(x) #x +#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) +#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) + +/** @brief Coverts a version as Major/Minor/Patch into a number: + <8 bit major><16 bit minor><8 bit patch>. + + This macro was added in version 0.12.0. + + Convenient function to be used for compile-time checks, like: + @code{.c} + #if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + @endcode + + @ingroup API +*/ +#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p)) + +/** @brief Static/compile-time version of the library. + + This macro was added in version 0.12.0. + + @see @ref HID_API_MAKE_VERSION. + + @ingroup API +*/ +#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Static/compile-time string version of the library. + + @ingroup API +*/ +#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) + +/** @brief Maximum expected HID Report descriptor size in bytes. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API +*/ +#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096 + +#ifdef __cplusplus +extern "C" { +#endif + /** A structure to hold the version numbers. */ + struct hid_api_version { + int major; /**< major version number */ + int minor; /**< minor version number */ + int patch; /**< patch version number */ + }; + + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** @brief HID underlying bus types. + + @ingroup API + */ + typedef enum { + /** Unknown bus type */ + HID_API_BUS_UNKNOWN = 0x00, + + /** USB bus + Specifications: + https://usb.org/hid */ + HID_API_BUS_USB = 0x01, + + /** Bluetooth or Bluetooth LE bus + Specifications: + https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/ + https://www.bluetooth.com/specifications/specs/hid-service-1-0/ + https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */ + HID_API_BUS_BLUETOOTH = 0x02, + + /** I2C bus + Specifications: + https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */ + HID_API_BUS_I2C = 0x03, + + /** SPI bus + Specifications: + https://www.microsoft.com/download/details.aspx?id=103325 */ + HID_API_BUS_SPI = 0x04, + + /** Virtual device + E.g.: https://elixir.bootlin.com/linux/v4.0/source/include/uapi/linux/input.h#L955 + + Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0) + */ + HID_API_BUS_VIRTUAL = 0x05, + } hid_bus_type; + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac/hidraw only) */ + unsigned short usage; + /** The USB interface which this logical device + represents. + + Valid only if the device is a USB HID device. + Set to -1 in all other cases. + */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + + /** Underlying bus type + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + */ + hid_bus_type bus_type; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(NULL) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + 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 returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device_info, containing information about the HID devices + attached to the system, + or NULL in the case of failure or if no HID devices present in the system. + Call hid_error(NULL) to get the failure reason. + + @note The returned value by this function must to be freed by calling hid_free_enumeration(), + when not needed anymore. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + 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. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + Call hid_error(NULL) to get the failure reason. + + @note The returned object must be freed by calling hid_close(), + when not needed anymore. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first interrupt OUT + endpoint, if one exists. If it does not the behaviour is as + @ref hid_send_output_report + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. + Call hid_read_error(dev) to get the failure reason. + If no packet was available to be read within + the timeout period, this function returns 0. + + @note This function doesn't change the buffer returned by the hid_error(dev). + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. + Call hid_read_error(dev) to get the failure reason. + If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + + @note This function doesn't change the buffer returned by the hid_error(dev). + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Get a string describing the last error which occurred during hid_read/hid_read_timeout. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL for a valid @ref dev. + If there was no error in the last call to hid_read/hid_read_error - + the returned string clearly indicates that. + + Strings returned from hid_read_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + + @ingroup API + @param dev A device handle. Shall never be NULL. + + @returns + A string describing the hid_read/hid_read_timeout error (if any). + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_read_error(hid_device *dev); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Send a Output report to the device. + + Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + + Output reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_output_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_output_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + This function sets the return value of hid_error(). + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + + @see @ref hid_write + */ + int HID_API_EXPORT HID_API_CALL hid_send_output_report(hid_device* dev, const unsigned char* data, size_t length); + + /** @brief Get a input report from a HID device. + + Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0) + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); + + /** @brief Get The struct #hid_device_info from a HID device. + + Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0) + + @ingroup API + @param dev A device handle returned from hid_open(). + + @returns + This function returns a pointer to the struct #hid_device_info + for this hid_device, or NULL in the case of failure. + Call hid_error(dev) to get the failure reason. + This struct is valid until the device is closed with hid_close(). + + @note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user. + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + Call hid_error(dev) to get the failure reason. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a report descriptor from a HID device. + + Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0) + + User has to provide a preallocated buffer where descriptor will be copied to. + The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param buf The buffer to copy descriptor into. + @param buf_size The size of the buffer in bytes. + + @returns + This function returns non-negative number of bytes actually copied, or -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size); + + /** @brief Get a string describing the last error which occurred. + + This function is intended for logging/debugging purposes. + + This function guarantees to never return NULL. + If there was no error in the last function call - + the returned string clearly indicates that. + + Any HIDAPI function that can explicitly indicate an execution failure + (e.g. by an error code, or by returning NULL) - may set the error string, + to be returned by this function. + + Strings returned from hid_error() must not be freed by the user, + i.e. owned by HIDAPI library. + Device-specific error string may remain allocated at most until hid_close() is called. + Global error string may remain allocated at most until hid_exit() is called. + + @ingroup API + @param dev A device handle returned from hid_open(), + or NULL to get the last non-device-specific error + (e.g. for errors in hid_open() or hid_enumerate()). + + @returns + A string describing the last error (if any). + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); + + /** @brief Get a runtime version of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated struct, that contains version. + */ + HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); + + + /** @brief Get a runtime version string of the library. + + This function is thread-safe. + + @ingroup API + + @returns + Pointer to statically allocated string, that contains version string. + */ + HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependencies/hidapi-hotplug-win/include/hidapi_winapi.h b/dependencies/hidapi-hotplug-win/include/hidapi_winapi.h new file mode 100644 index 000000000..d032ea8fe --- /dev/null +++ b/dependencies/hidapi-hotplug-win/include/hidapi_winapi.h @@ -0,0 +1,94 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + libusb/hidapi Team + + Copyright 2022, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + https://github.com/libusb/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + * + * Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + */ + +#ifndef HIDAPI_WINAPI_H__ +#define HIDAPI_WINAPI_H__ + +#include + +#include + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + + /** @brief Get the container ID for a HID device. + + Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) + + This function returns the `DEVPKEY_Device_ContainerId` property of + the given device. This can be used to correlate different + interfaces/ports on the same hardware device. + + @ingroup API + @param dev A device handle returned from hid_open(). + @param container_id The device's container ID on return. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_winapi_get_container_id(hid_device *dev, GUID *container_id); + + /** + * @brief Reconstructs a HID Report Descriptor from a Win32 HIDP_PREPARSED_DATA structure. + * This reconstructed report descriptor is logical identical to the real report descriptor, + * but not byte wise identical. + * + * @param[in] hidp_preparsed_data Pointer to the HIDP_PREPARSED_DATA to read, i.e.: the value of PHIDP_PREPARSED_DATA, + * as returned by HidD_GetPreparsedData WinAPI function. + * @param buf Pointer to the buffer where the report descriptor should be stored. + * @param[in] buf_size Size of the buffer. The recommended size for the buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes. + * + * @return Returns size of reconstructed report descriptor if successful, -1 for error. + */ + int HID_API_EXPORT_CALL hid_winapi_descriptor_reconstruct_pp_data(void *hidp_preparsed_data, unsigned char *buf, size_t buf_size); + + /** + * @brief Sets the timeout for hid_write operation. + * + * Since version 0.15.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 15, 0) + * + * The default timeout is 1sec for winapi backend. + * + * In case if 1sec is not enough, on in case of multi-platform development, + * the recommended value is 5sec, e.g. to match (unconfigurable) 5sec timeout + * set for hidraw (linux kernel) implementation. + * + * When the timeout is set to 0, hid_write function becomes non-blocking and would exit immediately. + * When the timeout is set to INFINITE ((DWORD)-1), the function will not exit, + * until the write operation is performed or an error occurred. + * See dwMilliseconds parameter documentation of WaitForSingleObject function. + * + * @param timeout New timeout value in milliseconds. + */ + void HID_API_EXPORT_CALL hid_winapi_set_write_timeout(hid_device *dev, unsigned long timeout); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.dll b/dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.dll new file mode 100644 index 0000000000000000000000000000000000000000..6112781e605948d84aaa227b6adabf4706414811 GIT binary patch literal 45056 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 b3ok5uvGBvf9}5{4aV#1bXO7xRn-Bm1X?y(0 literal 0 HcmV?d00001 diff --git a/dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.lib b/dependencies/hidapi-hotplug-win/x64/hidapi-hotplug.lib new file mode 100644 index 0000000000000000000000000000000000000000..f6d8865afc3fd81101698ea1ce7ee9fd9d7ef0ec GIT binary patch literal 8500 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%0DEDdLB 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 literal 0 HcmV?d00001 diff --git a/dependencies/hidapi-hotplug-win/x86/hidapi-hotplug.lib b/dependencies/hidapi-hotplug-win/x86/hidapi-hotplug.lib new file mode 100644 index 0000000000000000000000000000000000000000..082017d1ec34099a4bac15fcb9762b20a0edd4f2 GIT binary patch literal 8640 zcmcIpU2IfE6h3XCtp!T||9`fC0Rj|Sx?4yL6vc+hQkqhNZ@1gsyX_6_?t1qw5EH`- zK}mdtH{S>e>XVW9f=N>psWB0u4}=hugs5O_Lc&8e8a(IDot^tL-R+g-CTH)R^E2O> znKN@{X1m^q=+UWt6*~jYXGf2-%uBnhkDmTL*5`Ny0PF{-oCByX2dEhYXt~9x?m2)- zb@)cApJ3ENI!MiEL)03w=rX>Mx<(nbEdiL+fN!F6j5^`~MC}Pi4PyXA?N1psh5?9b zuP|!7iDjgwK}O93h>KMJfKkJ3v`1<}Ux@0?GwOJVI7pqti~=J7CZTQssS|ZO=M-h= z$jH&rG3DUf$KDzp8W}q}s!VCINJ`r=l{8Y*nMqKT;p5@3R}aoQ6`9)Dq2t4cm4gSy z4xphnlTxTUMN4P~*uI2BJ;NpJ%O*)=W>h_5sLW6mMWGYdRaM~sg}p=%PCP(5o3yZMPjQcbWZCU?;Gn= z$aFOlQw(iJO=hfqcXFJX41_f7V`f^Yx}Ma9T(Qc+KCK!`BAJ+&PDW2^35*sRZV%J0 zpNJ`OHDY9R6$c@e)GY_osze~{CJ{$wCgTe$vr_DXRL1avx%k40>eLc$8!uN_M5mK! zD^9IXAtSq)NFo!DM5)obk~Z{gPc18S+NbnnEE6?6rf$B|Dy`~TWLm+AGa-SMOPw{u z)Kgki#mEd? zI=dib3&Pf7k*BO>4u;S>ttD_VRLGZTGGRot1a_r{6NTb@S)s?CW`sFQowL%aKqB?j zAbJNo1zkbMDI!uSB^EIv4uA5f`p=hpv-z?M6XskcKzTL5U=6^n7J%nghMcHJ9+u~t z0Ya?+myt%%V5to=6w*1Qcn3hD9bgPI;!}Kw8v(A=0^G#+U=zRq+CIQ~Zlin-%jdD~ zhiEh02`~~sKGGb1W738qD293{g*I3XWzY;Y&;oVP0FBTJP4F6&!z!qNHBbrbVI5RM zEmT1}bU+t$LIBpjlx+#Fl>a|-+A^bOx!~ChmlF{Kp73@j%Umfh&zyCyssfb^bI)Ao zD(SS8u6a(C+{;(axlg|3TfulT&y$9^nk1PEci9d`lyhWkp_D77I<>^IAozJQqAa)B zja_+Ji1Mw5LepBF@~kJeIDM%F(S|FwHnz-an;EqD+E@SVCek|(NYyg~IMW^bobQf$ z!I$j{q>;`|+%eAilIZ3ld0dl}hpIH9?Fy1uCb8IuJNKTpy(WuUQkHH29h*vNgp#Q& zHW}X)mBh@(qpcwF@(N>+Y?qBbn^2N!O{p1iXYtc^^zoc%cDhn$-jn8De)b%%a8}(N zo1TV>o%~2@spYFL*xPGqj#q?>O5pBa@BP|RgxMIgAtl7_m^zV}+;of;0S@8E$Gl*@ z2cYWBZfvFz+0@HLm}FrA>354!g6{#?HEZdGEH-9WfKtTcdc+@qz-%$Tn=#|kQ{!#u zW~i+yLlIR=PbF-JOBZU6Xi+_xPR5PEmeH+&@X+vKCU*X=#UdF}1%MfyzN+wdwfW@* zLyw}#nH0Ky=|WM_ix(W0Ea@R%i-GsR0D$TJ6z7<2CA<>O=_4IvKvpLxIK+doRZv3|bv!MmEhs`(vAfeVk8m ze2*8b_p9jTmGtZ;#4mJo2D8yjw{tXq!$oJVeY1#%0DQ{XL5J-MB$MCwirObuXpZ)L z@YBZ|5UYrziH%1g5GC#V;KE10QTvLsh~~9R0nqwv8Hl&hf-M>8b0;1y(MXp&v28o%f*|kl_V~=kuJ3mt zGM)L&iy0be*{)pTKp{q4`6WD0y^!y@mz<2xmdFl;_^+kX8=ev{~*KrSWoslF?`0a&$$x2f;a&dt+ z)0J)?yvMKtcpzKT?>`!RLTmbZ?tyvIlYsf|a~ax~UA6ZKt%{?Wr^!N~1tt0Tn(^%& z%E#4qG8g|S^M60P(zZD94b4#upLqBIjZaeK?3e~Ol4X56Ya;pX^n1hU_1W< zl0f?&;gDo~xbyg{+q4sD6xhyt5DBo~HU}kZW50UhRseCEI4-|xDFmJ*7j3_Obdk;k z%`QCiHm3lXdn9fA@cPKJeTdn@F;{kBQ~+;6lAXwR#~1&gok*+88`_XK{y!=Nz9b(f mMrNN-K5ld2d#}AD4)~6B*;(z2uQ%MFvrK!|1M_N40{0(EjUeLy literal 0 HcmV?d00001 diff --git a/hidapi_wrapper/hidapi_wrapper.h b/hidapi_wrapper/hidapi_wrapper.h index b04246e05..d7d1b2d59 100644 --- a/hidapi_wrapper/hidapi_wrapper.h +++ b/hidapi_wrapper/hidapi_wrapper.h @@ -19,31 +19,40 @@ #include #endif -/*-----------------------------------------------------*\ -| Type definitions for libhidapi function pointers | -\*-----------------------------------------------------*/ -typedef int (*hidapi_wrapper_send_feature_report) (hid_device*, const unsigned char*, size_t); -typedef int (*hidapi_wrapper_get_feature_report) (hid_device*, unsigned char*, size_t); -typedef int (*hidapi_wrapper_get_serial_number_string) (hid_device*, wchar_t*, size_t); -typedef hid_device* (*hidapi_wrapper_open_path) (const char*); -typedef hid_device_info* (*hidapi_wrapper_enumerate) (unsigned short, unsigned short); -typedef void (*hidapi_wrapper_free_enumeration) (hid_device_info*); -typedef void (*hidapi_wrapper_close) (hid_device*); -typedef const wchar_t* (*hidapi_wrapper_error) (hid_device*); +/*---------------------------------------------------------*\ +| Type definitions for libhidapi function pointers | +\*---------------------------------------------------------*/ +typedef int (*hidapi_wrapper_send_feature_report) (hid_device*, const unsigned char*, size_t); +typedef int (*hidapi_wrapper_get_feature_report) (hid_device*, unsigned char*, size_t); +typedef int (*hidapi_wrapper_get_serial_number_string) (hid_device*, wchar_t*, size_t); +typedef hid_device* (*hidapi_wrapper_open_path) (const char*); +typedef hid_device_info* (*hidapi_wrapper_enumerate) (unsigned short, unsigned short); +typedef void (*hidapi_wrapper_free_enumeration) (hid_device_info*); +typedef void (*hidapi_wrapper_close) (hid_device*); +typedef const wchar_t* (*hidapi_wrapper_error) (hid_device*); -/*-----------------------------------------------------*\ -| See comment at top of HyperXQuadcastSDetect.cpp for | -| details about the hidapi wrapper for this device | -\*-----------------------------------------------------*/ +#if(HID_HOTPLUG_ENABLED) +typedef int (*hidapi_wrapper_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); +typedef int (*hidapi_wrapper_hotplug_deregister_callback) (hid_hotplug_callback_handle callback_handle); +#endif + +/*---------------------------------------------------------*\ +| See comment at top of HyperXQuadcastSDetect.cpp for | +| details about the hidapi wrapper for this device | +\*---------------------------------------------------------*/ struct hidapi_wrapper { - void* dyn_handle; - hidapi_wrapper_send_feature_report hid_send_feature_report; - hidapi_wrapper_get_feature_report hid_get_feature_report; - hidapi_wrapper_get_serial_number_string hid_get_serial_number_string; - hidapi_wrapper_open_path hid_open_path; - hidapi_wrapper_enumerate hid_enumerate; - hidapi_wrapper_free_enumeration hid_free_enumeration; - hidapi_wrapper_close hid_close; - hidapi_wrapper_error hid_error; + void* dyn_handle; + hidapi_wrapper_send_feature_report hid_send_feature_report; + hidapi_wrapper_get_feature_report hid_get_feature_report; + hidapi_wrapper_get_serial_number_string hid_get_serial_number_string; + hidapi_wrapper_open_path hid_open_path; + hidapi_wrapper_enumerate hid_enumerate; + hidapi_wrapper_free_enumeration hid_free_enumeration; + hidapi_wrapper_close hid_close; + hidapi_wrapper_error hid_error; +#if(HID_HOTPLUG_ENABLED) + hidapi_wrapper_hotplug_register_callback hid_hotplug_register_callback; + hidapi_wrapper_hotplug_deregister_callback hid_hotplug_deregister_callback; +#endif }; diff --git a/scripts/build-appimage.sh b/scripts/build-appimage.sh index 211e78225..55f837164 100755 --- a/scripts/build-appimage.sh +++ b/scripts/build-appimage.sh @@ -98,7 +98,16 @@ make install INSTALL_ROOT=AppDir #-----------------------------------------------------------------------# export QML_SOURCES_PATHS="$REPO_ROOT"/src -linuxdeploy-"$ARCH_LINUXDEPLOY".AppImage --appdir AppDir -e "$TARGET" -i "$REPO_ROOT"/qt/org.openrgb.OpenRGB.png -d "$REPO_ROOT"/qt/org.openrgb.OpenRGB.desktop +#-----------------------------------------------------------------------# +# We need to bundle the libhidapi-libusb library for wrapped HID # +# devices, find its path using ldconfig # +#-----------------------------------------------------------------------# +LIBHIDAPI_LIBUSB_PATH=$(find /usr/lib -name "libhidapi-hotplug-libusb.so") +if [ ! -f $LIBHIDAPI_LIBUSB_PATH ]; then + LIBHIDAPI_LIBUSB_PATH=$(find /usr/lib -name "libhidapi-libusb.so") +fi + +linuxdeploy-"$ARCH_LINUXDEPLOY".AppImage --appdir AppDir -e "$TARGET" -i "$REPO_ROOT"/qt/org.openrgb.OpenRGB.png -d "$REPO_ROOT"/qt/org.openrgb.OpenRGB.desktop --library "$LIBHIDAPI_LIBUSB_PATH" linuxdeploy-plugin-qt-"$ARCH_LINUXDEPLOY".AppImage --appdir AppDir linuxdeploy-"$ARCH_LINUXDEPLOY".AppImage --appdir AppDir --output appimage