Added hotplug detection when using libusb

Also switched to a single thread for libusb read operations instead of one thread per device
This commit is contained in:
Sam Lantinga
2026-06-10 10:04:57 -07:00
parent 18fc4d931a
commit 1475239328
6 changed files with 142 additions and 21 deletions

View File

@@ -1272,7 +1272,7 @@ macro(CheckHIDAPI)
#include <stddef.h>
#include <libusb.h>
int main(int argc, char **argv) {
libusb_close(NULL);
libusb_interrupt_event_handler(NULL);
return 0;
}" HAVE_LIBUSB_H)
cmake_pop_check_state()

View File

@@ -734,6 +734,10 @@ static SDL_LibUSBContext *libusb_ctx;
#define libusb_bulk_transfer libusb_ctx->bulk_transfer
#define libusb_handle_events libusb_ctx->handle_events
#define libusb_handle_events_completed libusb_ctx->handle_events_completed
#define libusb_interrupt_event_handler libusb_ctx->interrupt_event_handler
#define libusb_has_capability libusb_ctx->has_capability
#define libusb_hotplug_register_callback libusb_ctx->hotplug_register_callback
#define libusb_hotplug_deregister_callback libusb_ctx->hotplug_deregister_callback
#define libusb_error_name libusb_ctx->error_name
struct LIBUSB_hid_device_;

View File

@@ -600,6 +600,66 @@ HID_API_EXPORT const char* HID_API_CALL hid_version_str(void)
return HID_API_VERSION_STR;
}
#ifdef HIDAPI_USING_SDL_RUNTIME
static libusb_hotplug_callback_handle hotplug_callback_handle;
static int shutdown_event_thread;
static hidapi_thread_state event_thread_state;
static int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event, void *user_data)
{
switch (event) {
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
++SDL_HIDAPI_discovery.m_unDeviceChangeCounter;
break;
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
++SDL_HIDAPI_discovery.m_unDeviceChangeCounter;
break;
default:
break;
}
return 0;
}
static void *event_thread(void *param)
{
while (!shutdown_event_thread) {
int res = libusb_handle_events(usb_context);
if (res < 0) {
/* There was an error. */
LOG("event_thread(): (%d) %s\n", res, libusb_error_name(res));
}
}
return NULL;
}
static void start_event_thread()
{
if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
int res = libusb_hotplug_register_callback(usb_context, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, &hotplug_callback_handle);
if (res < 0) {
LOG("Couldn't register hotplug: (%d) %s\n", res, libusb_error_name(res));
}
}
hidapi_thread_create(&event_thread_state, event_thread, NULL);
}
static void stop_event_thread()
{
shutdown_event_thread = 1;
libusb_interrupt_event_handler(usb_context);
hidapi_thread_join(&event_thread_state);
shutdown_event_thread = 0;
if (hotplug_callback_handle) {
libusb_hotplug_deregister_callback(usb_context, hotplug_callback_handle);
hotplug_callback_handle = 0;
}
}
#endif /* HIDAPI_USING_SDL_RUNTIME */
int HID_API_EXPORT hid_init(void)
{
if (!usb_context) {
@@ -613,6 +673,10 @@ int HID_API_EXPORT hid_init(void)
locale = setlocale(LC_CTYPE, NULL);
if (!locale)
(void) setlocale(LC_CTYPE, "");
#ifdef HIDAPI_USING_SDL_RUNTIME
start_event_thread();
#endif
}
return 0;
@@ -622,6 +686,10 @@ int HID_API_EXPORT hid_exit(void)
{
usb_string_cache_destroy();
#ifdef HIDAPI_USING_SDL_RUNTIME
stop_event_thread();
#endif
if (usb_context) {
libusb_exit(usb_context);
usb_context = NULL;
@@ -1186,10 +1254,9 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
}
static void *read_thread(void *param)
static void start_read_operations(hid_device *dev)
{
int res;
hid_device *dev = param;
uint8_t *buf;
const size_t length = dev->input_ep_max_packet_size;
@@ -1209,10 +1276,37 @@ static void *read_thread(void *param)
from inside read_callback() */
res = libusb_submit_transfer(dev->transfer);
if(res < 0) {
LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res));
dev->shutdown_thread = 1;
dev->transfer_loop_finished = 1;
LOG("libusb_submit_transfer failed: %d %s. Stopping read_thread from running\n", res, libusb_error_name(res));
dev->shutdown_thread = 1;
dev->transfer_loop_finished = 1;
}
}
static void stop_read_operations(hid_device *dev)
{
while (!dev->transfer_loop_finished)
libusb_handle_events_completed(usb_context, &dev->transfer_loop_finished);
/* Now that the read operations are stopping, Wake any threads which are
waiting on data (in hid_read_timeout()). Do this under a mutex to
make sure that a thread which is about to go to sleep waiting on
the condition actually will go to sleep before the condition is
signaled. */
hidapi_thread_mutex_lock(&dev->thread_state);
hidapi_thread_cond_broadcast(&dev->thread_state);
hidapi_thread_mutex_unlock(&dev->thread_state);
}
#ifdef HIDAPI_USING_SDL_RUNTIME
/* We have a separate thread handling all the events */
#else
static void *read_thread(void *param)
{
hid_device *dev = param;
start_read_operations(dev);
/* Notify the main thread that the read thread is up and running. */
hidapi_thread_barrier_wait(&dev->thread_state);
@@ -1239,17 +1333,7 @@ static void *read_thread(void *param)
if no transfers are pending, but that's OK. */
libusb_cancel_transfer(dev->transfer);
while (!dev->transfer_loop_finished)
libusb_handle_events_completed(usb_context, &dev->transfer_loop_finished);
/* Now that the read thread is stopping, Wake any threads which are
waiting on data (in hid_read_timeout()). Do this under a mutex to
make sure that a thread which is about to go to sleep waiting on
the condition actually will go to sleep before the condition is
signaled. */
hidapi_thread_mutex_lock(&dev->thread_state);
hidapi_thread_cond_broadcast(&dev->thread_state);
hidapi_thread_mutex_unlock(&dev->thread_state);
stop_read_operations(dev);
/* The dev->transfer->buffer and dev->transfer objects are cleaned up
in hid_close(). They are not cleaned up here because this thread
@@ -1261,6 +1345,7 @@ static void *read_thread(void *param)
return NULL;
}
#endif /* HIDAPI_USING_SDL_RUNTIME */
static void init_xbox360(libusb_device_handle *device_handle, unsigned short idVendor, unsigned short idProduct, const struct libusb_config_descriptor *conf_desc)
{
@@ -1441,10 +1526,14 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa
calculate_device_quirks(dev, desc.idVendor, desc.idProduct);
#ifdef HIDAPI_USING_SDL_RUNTIME
start_read_operations(dev);
#else
hidapi_thread_create(&dev->thread_state, read_thread, dev);
/* Wait here for the read thread to be initialized. */
hidapi_thread_barrier_wait(&dev->thread_state);
#endif
return 1;
}
@@ -1868,8 +1957,12 @@ void HID_API_EXPORT hid_close(hid_device *dev)
dev->shutdown_thread = 1;
libusb_cancel_transfer(dev->transfer);
#ifdef HIDAPI_USING_SDL_RUNTIME
stop_read_operations(dev);
#else
/* Wait for read_thread() to end. */
hidapi_thread_join(&dev->thread_state);
#endif
/* Clean up the Transfer objects allocated in read_thread(). */
free(dev->transfer->buffer);

View File

@@ -53,10 +53,17 @@ static int SDL_CreateThreadBarrier(SDL_ThreadBarrier *barrier, Uint32 count)
static void SDL_DestroyThreadBarrier(SDL_ThreadBarrier *barrier)
{
SDL_DestroyCondition(barrier->cond);
SDL_DestroyMutex(barrier->mutex);
if (barrier->cond) {
SDL_DestroyCondition(barrier->cond);
barrier->cond = NULL;
}
if (barrier->mutex) {
SDL_DestroyMutex(barrier->mutex);
barrier->mutex = NULL;
}
}
#if 0 // Not used
static int SDL_WaitThreadBarrier(SDL_ThreadBarrier *barrier)
{
SDL_LockMutex(barrier->mutex);
@@ -71,6 +78,7 @@ static int SDL_WaitThreadBarrier(SDL_ThreadBarrier *barrier)
SDL_UnlockMutex(barrier->mutex);
return 0;
}
#endif
#include "../../thread/SDL_systhread.h"
@@ -97,8 +105,14 @@ static void hidapi_thread_state_init(hidapi_thread_state *state)
static void hidapi_thread_state_destroy(hidapi_thread_state *state)
{
SDL_DestroyThreadBarrier(&state->barrier);
SDL_DestroyCondition(state->condition);
SDL_DestroyMutex(state->mutex);
if (state->condition) {
SDL_DestroyCondition(state->condition);
state->condition = NULL;
}
if (state->mutex) {
SDL_DestroyMutex(state->mutex);
state->mutex = NULL;
}
}
static void hidapi_thread_cleanup_push(void (*routine)(void *), void *arg)
@@ -153,10 +167,12 @@ static void hidapi_thread_cond_broadcast(hidapi_thread_state *state)
SDL_BroadcastCondition(state->condition);
}
#if 0 // Not used
static void hidapi_thread_barrier_wait(hidapi_thread_state *state)
{
SDL_WaitThreadBarrier(&state->barrier);
}
#endif
typedef struct
{

View File

@@ -90,6 +90,10 @@ bool SDL_InitLibUSB(SDL_LibUSBContext **ctx)
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_device_handle *, unsigned char, unsigned char *, int, int *, unsigned int), bulk_transfer)
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *), handle_events)
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int *), handle_events_completed)
LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *ctx), interrupt_event_handler)
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(uint32_t), has_capability)
LOAD_LIBUSB_SYMBOL(int (LIBUSB_CALL *)(libusb_context *, int, int, int, int, int, libusb_hotplug_callback_fn, void *, libusb_hotplug_callback_handle *), hotplug_register_callback)
LOAD_LIBUSB_SYMBOL(void (LIBUSB_CALL *)(libusb_context *, libusb_hotplug_callback_handle), hotplug_deregister_callback)
LOAD_LIBUSB_SYMBOL(const char * (LIBUSB_CALL *)(int), error_name)
#undef LOAD_LIBUSB_SYMBOL
}

View File

@@ -91,6 +91,10 @@ typedef struct SDL_LibUSBContext
);
int (LIBUSB_CALL *handle_events)(libusb_context *ctx);
int (LIBUSB_CALL *handle_events_completed)(libusb_context *ctx, int *completed);
void (LIBUSB_CALL *interrupt_event_handler)(libusb_context *ctx);
int (LIBUSB_CALL *has_capability)(uint32_t capability);
int (LIBUSB_CALL *hotplug_register_callback)(libusb_context *ctx, int events, int flags, int vendor_id, int product_id, int dev_class, libusb_hotplug_callback_fn cb_fn, void *user_data, libusb_hotplug_callback_handle *callback_handle);
void (LIBUSB_CALL *hotplug_deregister_callback)(libusb_context *ctx, libusb_hotplug_callback_handle callback_handle);
const char * (LIBUSB_CALL *error_name)(int errcode);
/* *INDENT-ON* */ // clang-format on