hidapi/libusb: allow building on Windows, using the SDL C runtime

Signed-off-by: Sam Lantinga <slouken@libsdl.org>
main
Sam Lantinga 2023-05-25 12:43:09 -07:00
parent feb7178e66
commit 57b33aec01
2 changed files with 344 additions and 58 deletions

View File

@ -19,6 +19,28 @@
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
/* Define standard library functions in terms of SDL */
#define HIDAPI_USING_SDL_RUNTIME
#define free SDL_free
#define iconv_t SDL_iconv_t
#define ICONV_CONST
#define iconv(a,b,c,d,e) SDL_iconv(a, (const char **)b, c, d, e)
#define iconv_open SDL_iconv_open
#define iconv_close SDL_iconv_close
#define malloc SDL_malloc
#define realloc SDL_realloc
#define setlocale(X, Y) NULL
#define snprintf SDL_snprintf
#define strcmp SDL_strcmp
#define strdup SDL_strdup
#define strncpy SDL_strlcpy
#ifdef tolower
#undef tolower
#endif
#define tolower SDL_tolower
#define wcsdup SDL_wcsdup
#ifndef __FreeBSD__ #ifndef __FreeBSD__
/* this is awkwardly inlined, so we need to re-implement it here /* this is awkwardly inlined, so we need to re-implement it here
* so we can override the libusb_control_transfer call */ * so we can override the libusb_control_transfer call */
@ -32,5 +54,185 @@ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev,
#define libusb_get_string_descriptor SDL_libusb_get_string_descriptor #define libusb_get_string_descriptor SDL_libusb_get_string_descriptor
#endif /* __FreeBSD__ */ #endif /* __FreeBSD__ */
#define HIDAPI_THREAD_STATE_DEFINED
/* Barrier implementation because Android/Bionic don't have pthread_barrier.
This implementation came from Brent Priddy and was posted on
StackOverflow. It is used with his permission. */
typedef struct _SDL_ThreadBarrier
{
SDL_Mutex *mutex;
SDL_Condition *cond;
Uint32 count;
Uint32 trip_count;
} SDL_ThreadBarrier;
static int SDL_CreateThreadBarrier(SDL_ThreadBarrier *barrier, Uint32 count)
{
SDL_assert(barrier != NULL);
SDL_assert(count != 0);
barrier->mutex = SDL_CreateMutex();
if (barrier->mutex == NULL) {
return -1; /* Error set by CreateMutex */
}
barrier->cond = SDL_CreateCondition();
if (barrier->cond == NULL) {
return -1; /* Error set by CreateCond */
}
barrier->trip_count = count;
barrier->count = 0;
return 0;
}
static void SDL_DestroyThreadBarrier(SDL_ThreadBarrier *barrier)
{
SDL_DestroyCondition(barrier->cond);
SDL_DestroyMutex(barrier->mutex);
}
static int SDL_WaitThreadBarrier(SDL_ThreadBarrier *barrier)
{
SDL_LockMutex(barrier->mutex);
barrier->count += 1;
if (barrier->count >= barrier->trip_count) {
barrier->count = 0;
SDL_BroadcastCondition(barrier->cond);
SDL_UnlockMutex(barrier->mutex);
return 1;
}
SDL_WaitCondition(barrier->cond, barrier->mutex);
SDL_UnlockMutex(barrier->mutex);
return 0;
}
#include "../thread/SDL_systhread.h"
#define THREAD_STATE_WAIT_TIMED_OUT SDL_MUTEX_TIMEDOUT
typedef struct
{
SDL_Thread *thread;
SDL_Mutex *mutex; /* Protects input_reports */
SDL_Condition *condition;
SDL_ThreadBarrier barrier; /* Ensures correct startup sequence */
} hid_device_thread_state;
static void thread_state_init(hid_device_thread_state *state)
{
state->mutex = SDL_CreateMutex();
state->condition = SDL_CreateCondition();
SDL_CreateThreadBarrier(&state->barrier, 2);
}
static void thread_state_free(hid_device_thread_state *state)
{
SDL_DestroyThreadBarrier(&state->barrier);
SDL_DestroyCondition(state->condition);
SDL_DestroyMutex(state->mutex);
}
static void thread_state_push_cleanup(void (*routine)(void *), void *arg)
{
/* There isn't an equivalent in SDL, and it's only useful for threads calling hid_read_timeout() */
}
static void thread_state_pop_cleanup(int execute)
{
}
static void thread_state_lock(hid_device_thread_state *state)
{
SDL_LockMutex(state->mutex);
}
static void thread_state_unlock(hid_device_thread_state *state)
{
SDL_UnlockMutex(state->mutex);
}
static void thread_state_wait_condition(hid_device_thread_state *state)
{
SDL_WaitCondition(state->condition, state->mutex);
}
static int thread_state_wait_condition_timeout(hid_device_thread_state *state, struct timespec *ts)
{
Uint64 end_time;
Sint64 timeout_ns;
Sint32 timeout_ms;
end_time = ts->tv_sec;
end_time *= 1000000000L;
end_time += ts->tv_nsec;
timeout_ns = (Sint64)(end_time - SDL_GetTicksNS());
if (timeout_ns <= 0) {
timeout_ms = 0;
} else {
timeout_ms = (Sint32)SDL_NS_TO_MS(timeout_ns);
}
return SDL_WaitConditionTimeout(state->condition, state->mutex, timeout_ms);
}
static void thread_state_signal_condition(hid_device_thread_state *state)
{
SDL_SignalCondition(state->condition);
}
static void thread_state_broadcast_condition(hid_device_thread_state *state)
{
SDL_BroadcastCondition(state->condition);
}
static void thread_state_wait_barrier(hid_device_thread_state *state)
{
SDL_WaitThreadBarrier(&state->barrier);
}
typedef struct
{
void *(*func)(void*);
void *func_arg;
} RunInputThreadParam;
static int RunInputThread(void *param)
{
RunInputThreadParam *data = (RunInputThreadParam *)param;
void *(*func)(void*) = data->func;
void *func_arg = data->func_arg;
SDL_free(data);
func(func_arg);
return 0;
}
static void thread_state_create_thread(hid_device_thread_state *state, void *(*func)(void*), void *func_arg)
{
RunInputThreadParam *param = (RunInputThreadParam *)malloc(sizeof(*param));
/* Note that the hidapi code didn't check for thread creation failure.
* We'll crash if malloc() fails
*/
param->func = func;
param->func_arg = func_arg;
state->thread = SDL_CreateThreadInternal(RunInputThread, "libusb", 0, param);
}
static void thread_state_join_thread(hid_device_thread_state *state)
{
SDL_WaitThread(state->thread, NULL);
}
static void thread_state_get_current_time(struct timespec *ts)
{
Uint64 ns = SDL_GetTicksNS();
ts->tv_sec = ns / 1000000000L;
ts->tv_nsec = ns % 1000000000L;
}
#undef HIDAPI_H__ #undef HIDAPI_H__
#include "libusb/hid.c" #include "libusb/hid.c"

View File

@ -20,6 +20,7 @@
https://github.com/libusb/hidapi . https://github.com/libusb/hidapi .
********************************************************/ ********************************************************/
#ifndef HIDAPI_USING_SDL_RUNTIME
#define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */ #define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */
/* C */ /* C */
@ -37,7 +38,6 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h>
#include <wchar.h> #include <wchar.h>
/* GNU / LibUSB */ /* GNU / LibUSB */
@ -48,9 +48,43 @@
#define ICONV_CONST #define ICONV_CONST
#endif #endif
#endif #endif
#endif /* HIDAPI_USING_SDL_RUNTIME */
#include "hidapi_libusb.h" #include "hidapi_libusb.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef DEBUG_PRINTF
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#else
#define LOG(...) do {} while (0)
#endif
#ifndef __FreeBSD__
#define DETACH_KERNEL_DRIVER
#endif
/* Uncomment to enable the retrieval of Usage and Usage Page in
hid_enumerate(). Warning, on platforms different from FreeBSD
this is very invasive as it requires the detach
and re-attach of the kernel driver. See comments inside hid_enumerate().
libusb HIDAPI programs are encouraged to use the interface number
instead to differentiate between interfaces on a composite HID device. */
/*#define INVASIVE_GET_USAGE*/
/* Linked List of input reports received from the device. */
struct input_report {
uint8_t *data;
size_t len;
struct input_report *next;
};
#ifndef HIDAPI_THREAD_STATE_DEFINED
#include <pthread.h>
#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ #if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__
/* Barrier implementation because Android/Bionic don't have pthread_barrier. /* Barrier implementation because Android/Bionic don't have pthread_barrier.
@ -112,35 +146,92 @@ static int pthread_barrier_wait(pthread_barrier_t *barrier)
#endif #endif
#ifdef __cplusplus #define THREAD_STATE_WAIT_TIMED_OUT ETIMEDOUT
extern "C" {
#endif
#ifdef DEBUG_PRINTF typdef struct
#define LOG(...) fprintf(stderr, __VA_ARGS__) {
#else pthread_t thread;
#define LOG(...) do {} while (0) pthread_mutex_t mutex; /* Protects input_reports */
#endif pthread_cond_t condition;
pthread_barrier_t barrier; /* Ensures correct startup sequence */
#ifndef __FreeBSD__ } hid_device_thread_state;
#define DETACH_KERNEL_DRIVER
#endif
/* Uncomment to enable the retrieval of Usage and Usage Page in static void thread_state_init(hid_device_thread_state *state)
hid_enumerate(). Warning, on platforms different from FreeBSD {
this is very invasive as it requires the detach pthread_mutex_init(&state->mutex, NULL);
and re-attach of the kernel driver. See comments inside hid_enumerate(). pthread_cond_init(&state->condition, NULL);
libusb HIDAPI programs are encouraged to use the interface number pthread_barrier_init(&state->barrier, NULL, 2);
instead to differentiate between interfaces on a composite HID device. */ }
/*#define INVASIVE_GET_USAGE*/
/* Linked List of input reports received from the device. */ static void thread_state_free(hid_device_thread_state *state)
struct input_report { {
uint8_t *data; pthread_barrier_destroy(&state->barrier);
size_t len; pthread_cond_destroy(&state->condition);
struct input_report *next; pthread_mutex_destroy(&state->mutex);
}; }
static void thread_state_push_cleanup(void (*routine)(void *), void *arg)
{
pthread_cleanup_push(&cleanup_mutex, dev);
}
static void thread_state_pop_cleanup(int execute)
{
pthread_cleanup_pop(execute);
}
static void thread_state_lock(hid_device_thread_state *state)
{
pthread_mutex_lock(&state->mutex);
}
static void thread_state_unlock(hid_device_thread_state *state)
{
pthread_mutex_unlock(&state->mutex);
}
static void thread_state_wait_condition(hid_device_thread_state *state)
{
pthread_cond_wait(&state->condition, &state->mutex);
}
static int thread_state_wait_condition_timeout(hid_device_thread_state *state, struct timespec *ts)
{
return pthread_cond_timedwait(&state->condition, &state->mutex, ts);
}
static void thread_state_signal_condition(hid_device_thread_state *state)
{
pthread_cond_signal(&state->condition);
}
static void thread_state_broadcast_condition(hid_device_thread_state *state)
{
pthread_cond_broadcast(&state->condition);
}
static void thread_state_wait_barrier(hid_device_thread_state *state)
{
pthread_barrier_wait(&state->barrier);
}
static void thread_state_create_thread(hid_device_thread_state *state, void *(*func)(void*), void *func_arg)
{
pthread_create(&dev->thread, NULL, func, param);
}
static void thread_state_join_thread(hid_device_thread_state *state)
{
pthread_join(state->thread, NULL);
}
static void thread_state_get_current_time(struct timespec *ts)
{
clock_gettime(CLOCK_REALTIME, ts);
}
#endif /* !HIDAPI_THREAD_STATE_DEFINED */
struct hid_device_ { struct hid_device_ {
/* Handle to the actual device. */ /* Handle to the actual device. */
@ -171,10 +262,7 @@ struct hid_device_ {
int blocking; /* boolean */ int blocking; /* boolean */
/* Read thread objects */ /* Read thread objects */
pthread_t thread; hid_device_thread_state thread_state;
pthread_mutex_t mutex; /* Protects input_reports */
pthread_cond_t condition;
pthread_barrier_t barrier; /* Ensures correct startup sequence */
int shutdown_thread; int shutdown_thread;
int transfer_loop_finished; int transfer_loop_finished;
struct libusb_transfer *transfer; struct libusb_transfer *transfer;
@ -208,9 +296,7 @@ static hid_device *new_hid_device(void)
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
dev->blocking = 1; dev->blocking = 1;
pthread_mutex_init(&dev->mutex, NULL); thread_state_init(&dev->thread_state);
pthread_cond_init(&dev->condition, NULL);
pthread_barrier_init(&dev->barrier, NULL, 2);
return dev; return dev;
} }
@ -218,9 +304,7 @@ static hid_device *new_hid_device(void)
static void free_hid_device(hid_device *dev) static void free_hid_device(hid_device *dev)
{ {
/* Clean up the thread objects */ /* Clean up the thread objects */
pthread_barrier_destroy(&dev->barrier); thread_state_free(&dev->thread_state);
pthread_cond_destroy(&dev->condition);
pthread_mutex_destroy(&dev->mutex);
hid_free_enumeration(dev->device_info); hid_free_enumeration(dev->device_info);
@ -1143,13 +1227,13 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
rpt->len = transfer->actual_length; rpt->len = transfer->actual_length;
rpt->next = NULL; rpt->next = NULL;
pthread_mutex_lock(&dev->mutex); thread_state_lock(&dev->thread_state);
/* Attach the new report object to the end of the list. */ /* Attach the new report object to the end of the list. */
if (dev->input_reports == NULL) { if (dev->input_reports == NULL) {
/* The list is empty. Put it at the root. */ /* The list is empty. Put it at the root. */
dev->input_reports = rpt; dev->input_reports = rpt;
pthread_cond_signal(&dev->condition); thread_state_signal_condition(&dev->thread_state);
} }
else { else {
/* Find the end of the list and attach. */ /* Find the end of the list and attach. */
@ -1168,7 +1252,7 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer)
return_data(dev, NULL, 0); return_data(dev, NULL, 0);
} }
} }
pthread_mutex_unlock(&dev->mutex); thread_state_unlock(&dev->thread_state);
} }
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
dev->shutdown_thread = 1; dev->shutdown_thread = 1;
@ -1227,7 +1311,7 @@ static void *read_thread(void *param)
} }
/* Notify the main thread that the read thread is up and running. */ /* Notify the main thread that the read thread is up and running. */
pthread_barrier_wait(&dev->barrier); thread_state_wait_barrier(&dev->thread_state);
/* Handle all the events. */ /* Handle all the events. */
while (!dev->shutdown_thread) { while (!dev->shutdown_thread) {
@ -1259,15 +1343,15 @@ static void *read_thread(void *param)
make sure that a thread which is about to go to sleep waiting on 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 the condition actually will go to sleep before the condition is
signaled. */ signaled. */
pthread_mutex_lock(&dev->mutex); thread_state_lock(&dev->thread_state);
pthread_cond_broadcast(&dev->condition); thread_state_broadcast_condition(&dev->thread_state);
pthread_mutex_unlock(&dev->mutex); thread_state_unlock(&dev->thread_state);
/* The dev->transfer->buffer and dev->transfer objects are cleaned up /* The dev->transfer->buffer and dev->transfer objects are cleaned up
in hid_close(). They are not cleaned up here because this thread in hid_close(). They are not cleaned up here because this thread
could end either due to a disconnect or due to a user could end either due to a disconnect or due to a user
call to hid_close(). In both cases the objects can be safely call to hid_close(). In both cases the objects can be safely
cleaned up after the call to pthread_join() (in hid_close()), but cleaned up after the call to thread_state_join() (in hid_close()), but
since hid_close() calls libusb_cancel_transfer(), on these objects, since hid_close() calls libusb_cancel_transfer(), on these objects,
they can not be cleaned up here. */ they can not be cleaned up here. */
@ -1445,15 +1529,15 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st
calculate_device_quirks(dev, desc.idVendor, desc.idProduct); calculate_device_quirks(dev, desc.idVendor, desc.idProduct);
pthread_create(&dev->thread, NULL, read_thread, dev); thread_state_create_thread(&dev->thread_state, read_thread, dev);
/* Wait here for the read thread to be initialized. */ /* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier); thread_state_wait_barrier(&dev->thread_state);
return 1; return 1;
} }
hid_device * HID_API_EXPORT hid_open_path(const char *path) HID_API_EXPORT hid_device *hid_open_path(const char *path)
{ {
hid_device *dev = NULL; hid_device *dev = NULL;
@ -1673,7 +1757,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length)
static void cleanup_mutex(void *param) static void cleanup_mutex(void *param)
{ {
hid_device *dev = param; hid_device *dev = param;
pthread_mutex_unlock(&dev->mutex); thread_state_unlock(&dev->thread_state);
} }
@ -1689,8 +1773,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
/* error: variable bytes_read might be clobbered by longjmp or vfork [-Werror=clobbered] */ /* error: variable bytes_read might be clobbered by longjmp or vfork [-Werror=clobbered] */
int bytes_read; /* = -1; */ int bytes_read; /* = -1; */
pthread_mutex_lock(&dev->mutex); thread_state_lock(&dev->thread_state);
pthread_cleanup_push(&cleanup_mutex, dev); thread_state_push_cleanup(cleanup_mutex, dev);
bytes_read = -1; bytes_read = -1;
@ -1711,7 +1795,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
if (milliseconds == -1) { if (milliseconds == -1) {
/* Blocking */ /* Blocking */
while (!dev->input_reports && !dev->shutdown_thread) { while (!dev->input_reports && !dev->shutdown_thread) {
pthread_cond_wait(&dev->condition, &dev->mutex); thread_state_wait_condition(&dev->thread_state);
} }
if (dev->input_reports) { if (dev->input_reports) {
bytes_read = return_data(dev, data, length); bytes_read = return_data(dev, data, length);
@ -1721,7 +1805,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
/* Non-blocking, but called with timeout. */ /* Non-blocking, but called with timeout. */
int res; int res;
struct timespec ts; struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts); thread_state_get_current_time(&ts);
ts.tv_sec += milliseconds / 1000; ts.tv_sec += milliseconds / 1000;
ts.tv_nsec += (milliseconds % 1000) * 1000000; ts.tv_nsec += (milliseconds % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000L) { if (ts.tv_nsec >= 1000000000L) {
@ -1730,7 +1814,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
} }
while (!dev->input_reports && !dev->shutdown_thread) { while (!dev->input_reports && !dev->shutdown_thread) {
res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts); res = thread_state_wait_condition_timeout(&dev->thread_state, &ts);
if (res == 0) { if (res == 0) {
if (dev->input_reports) { if (dev->input_reports) {
bytes_read = return_data(dev, data, length); bytes_read = return_data(dev, data, length);
@ -1741,7 +1825,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
or the read thread was shutdown. Run the or the read thread was shutdown. Run the
loop again (ie: don't break). */ loop again (ie: don't break). */
} }
else if (res == ETIMEDOUT) { else if (res == THREAD_STATE_WAIT_TIMED_OUT) {
/* Timed out. */ /* Timed out. */
bytes_read = 0; bytes_read = 0;
break; break;
@ -1759,8 +1843,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
} }
ret: ret:
pthread_mutex_unlock(&dev->mutex); thread_state_unlock(&dev->thread_state);
pthread_cleanup_pop(0); thread_state_pop_cleanup(0);
return bytes_read; return bytes_read;
} }
@ -1878,7 +1962,7 @@ void HID_API_EXPORT hid_close(hid_device *dev)
libusb_cancel_transfer(dev->transfer); libusb_cancel_transfer(dev->transfer);
/* Wait for read_thread() to end. */ /* Wait for read_thread() to end. */
pthread_join(dev->thread, NULL); thread_state_join_thread(&dev->thread_state);
/* Clean up the Transfer objects allocated in read_thread(). */ /* Clean up the Transfer objects allocated in read_thread(). */
free(dev->transfer->buffer); free(dev->transfer->buffer);
@ -1901,11 +1985,11 @@ void HID_API_EXPORT hid_close(hid_device *dev)
libusb_close(dev->device_handle); libusb_close(dev->device_handle);
/* Clear out the queue of received reports. */ /* Clear out the queue of received reports. */
pthread_mutex_lock(&dev->mutex); thread_state_lock(&dev->thread_state);
while (dev->input_reports) { while (dev->input_reports) {
return_data(dev, NULL, 0); return_data(dev, NULL, 0);
} }
pthread_mutex_unlock(&dev->mutex); thread_state_unlock(&dev->thread_state);
free_hid_device(dev); free_hid_device(dev);
} }