[WIP] Add optional HID device hotplug support (requires hidapi hotplug support)

This commit is contained in:
Adam Honse
2026-01-14 19:33:18 -06:00
parent bd68d8801b
commit 19f4c0eb2b
9 changed files with 324 additions and 75 deletions

View File

@@ -37,7 +37,7 @@ HyperXMicrophoneController::~HyperXMicrophoneController()
std::string HyperXMicrophoneController::GetDeviceLocation()
{
return(location);
return("HID: " + location);
}
std::string HyperXMicrophoneController::GetNameString()

View File

@@ -37,7 +37,7 @@ HyperXMicrophoneV2Controller::~HyperXMicrophoneV2Controller()
std::string HyperXMicrophoneV2Controller::GetDeviceLocation()
{
return(location);
return("HID: " + location);
}
std::string HyperXMicrophoneV2Controller::GetNameString()

View File

@@ -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()

View File

@@ -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::WrappedUnplugCallbackFunction, 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,138 @@ 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;
}
int DetectionManager::WrappedUnplugCallbackFunction(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] libusb 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;
}
#endif
#endif
#endif
/*---------------------------------------------------------*\
| Function for signalling DetectionManager updates to |
| registered callbacks |

View File

@@ -228,6 +228,28 @@ private:
std::vector<std::string> dynamic_detector_strings;
std::vector<PreDetectionHookFunction> 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,27 @@ 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);
static int WrappedUnplugCallbackFunction(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 |

View File

@@ -501,22 +501,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
}
}
}

2
debian/control vendored
View File

@@ -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

View File

@@ -19,31 +19,40 @@
#include <dlfcn.h>
#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
};

View File

@@ -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