diff --git a/CMakeLists.txt b/CMakeLists.txt index ffa5cab34..6e36a8404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,11 +156,10 @@ if(UNIX OR MINGW OR MSYS) endif() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, -# so we'll just use libusb when it's available. Except that libusb -# requires root permissions to open devices, so that's not generally -# useful, and we'll disable this by default on Unix. Windows and macOS -# can use it without root access, though, so enable by default there. -if(WINDOWS OR APPLE OR ANDROID) +# so we'll just use libusb when it's available. libusb does not support iOS, +# so we default to yes on iOS. +# TODO: Windows can support libusb, the hid.c file just depends on Unix APIs +if(WINDOWS OR IOS OR ANDROID) set(HIDAPI_SKIP_LIBUSB TRUE) else() set(HIDAPI_SKIP_LIBUSB FALSE) @@ -169,6 +168,14 @@ if (HIDAPI_SKIP_LIBUSB) set(OPT_DEF_HIDAPI ON) endif() +# On the other hand, *BSD specifically uses libusb only, so we make a special +# case just for them. +if(FREEBSD OR NETBSD OR OPENBSD OR BSDI) + set(HIDAPI_ONLY_LIBUSB TRUE) +else() + set(HIDAPI_ONLY_LIBUSB FALSE) +endif() + # Compiler info if(CMAKE_COMPILER_IS_GNUCC) set(USE_GCC TRUE) @@ -1376,6 +1383,7 @@ elseif(WINDOWS) if(SDL_JOYSTICK) CheckHIDAPI() + # TODO: Remove this hid.c block when SDL_hidapi.c is supported on Windows! if(HAVE_HIDAPI) set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/windows/hid.c) endif() @@ -1466,8 +1474,6 @@ elseif(APPLE) if(HAVE_HIDAPI) if(IOS) set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/ios/hid.m) - else() - set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/mac/hid.c) endif() endif() set(SDL_JOYSTICK_IOKIT 1) diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake index 2df9e355b..3b7f82138 100644 --- a/cmake/sdlchecks.cmake +++ b/cmake/sdlchecks.cmake @@ -1088,8 +1088,15 @@ macro(CheckHIDAPI) set(SOURCE_FILES ${SOURCE_FILES} ${HIDAPI_SOURCES}) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIBUSB_CFLAGS} -I${SDL2_SOURCE_DIR}/src/hidapi/hidapi") if(NOT HIDAPI_SKIP_LIBUSB) - set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/libusb/hid.c) - list(APPEND EXTRA_LIBS ${LIBUSB_LIBS}) + if(HIDAPI_ONLY_LIBUSB) + set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/libusb/hid.c) + list(APPEND EXTRA_LIBS ${LIBUSB_LIBS}) + else() + set(SOURCE_FILES ${SOURCE_FILES} ${SDL2_SOURCE_DIR}/src/hidapi/SDL_hidapi.c) + # libusb is loaded dynamically, so don't add it to EXTRA_LIBS + FindLibraryAndSONAME("usb-1.0") + set(SDL_LIBUSB_DYNAMIC "\"${USB_LIB_SONAME}\"") + endif() endif() endif() endif() diff --git a/configure b/configure index ab7b65887..30e1b1079 100755 --- a/configure +++ b/configure @@ -24106,16 +24106,24 @@ CheckHIDAPI() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, # so we'll just use libusb when it's available. # - # Except that libusb requires root permissions to open devices, so that's not generally useful, and we'll disable this by default. - # - # On macOS and Windows, where you don't need libusb or root, we default to yes. + # libusb does not support iOS, so we default to yes on iOS. + # TODO: Windows can support libusb, the hid.c file just depends on Unix APIs skiplibusb=no case "$host" in - *-*-cygwin* | *-*-mingw32* | *-*-darwin* ) + *-*-cygwin* | *-*-mingw32* | arm*-apple-darwin* | *-ios-* ) skiplibusb=yes ;; esac + # On the other hand, *BSD specifically uses libusb only, so we make a + # special case just for them. + onlylibusb=no + case "$host" in + *-*-*bsd* ) + onlylibusb=yes + ;; + esac + # Check whether --enable-hidapi was given. if test "${enable_hidapi+set}" = set; then : enableval=$enable_hidapi; @@ -24224,9 +24232,35 @@ $as_echo "#define SDL_JOYSTICK_HIDAPI 1" >>confdefs.h SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" if test x$skiplibusb = xno; then - SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c" EXTRA_CFLAGS="$EXTRA_CFLAGS $LIBUSB_CFLAGS" - EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LIBS" + if test x$onlylibusb = xyes; then + SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c" + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LIBS" + else + if test x$have_loadso != xyes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: You must have SDL_LoadObject() support for dynamic libusb loading" >&5 +$as_echo "$as_me: WARNING: You must have SDL_LoadObject() support for dynamic libusb loading" >&2;} + fi + SOURCES="$SOURCES $srcdir/src/hidapi/SDL_hidapi.c" + # libusb is loaded dynamically, so don't add it to LDFLAGS + libusb_lib="" + case "$host" in + *-*-darwin* ) + libusb_lib="libusb-1.0.0.dylib" + ;; + *-*-cygwin* | *-*-mingw32* ) + libusb_lib="libusb-1.0.dll" + ;; + esac + if test x$libusb_lib = x; then + libusb_lib=`find_lib "libusb-1.0.so.*" "" | sed 's/.*\/\(.*\)/\1/; q'` + fi + +cat >>confdefs.h <<_ACEOF +#define SDL_LIBUSB_DYNAMIC "$libusb_lib" +_ACEOF + + fi fi fi @@ -24732,6 +24766,7 @@ $as_echo "#define SDL_JOYSTICK_WINMM 1" >>confdefs.h fi SOURCES="$SOURCES $srcdir/src/joystick/windows/*.c" have_joystick=yes + # TODO: Remove this block once SDL_hidapi.c supports Windows! if test x$hidapi_support = xyes; then SOURCES="$SOURCES $srcdir/src/hidapi/windows/hid.c" fi @@ -25077,9 +25112,6 @@ $as_echo "#define SDL_JOYSTICK_IOKIT 1" >>confdefs.h SOURCES="$SOURCES $srcdir/src/joystick/darwin/*.c" have_joystick=yes - if test x$hidapi_support = xyes; then - SOURCES="$SOURCES $srcdir/src/hidapi/mac/hid.c" - fi fi # Set up files for the haptic library if test x$enable_haptic = xyes; then diff --git a/configure.ac b/configure.ac index 9e782c653..101eb2d18 100644 --- a/configure.ac +++ b/configure.ac @@ -3198,16 +3198,24 @@ CheckHIDAPI() # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers, # so we'll just use libusb when it's available. # - # Except that libusb requires root permissions to open devices, so that's not generally useful, and we'll disable this by default. - # - # On macOS and Windows, where you don't need libusb or root, we default to yes. + # libusb does not support iOS, so we default to yes on iOS. + # TODO: Windows can support libusb, the hid.c file just depends on Unix APIs skiplibusb=no case "$host" in - *-*-cygwin* | *-*-mingw32* | *-*-darwin* ) + *-*-cygwin* | *-*-mingw32* | arm*-apple-darwin* | *-ios-* ) skiplibusb=yes ;; esac + # On the other hand, *BSD specifically uses libusb only, so we make a + # special case just for them. + onlylibusb=no + case "$host" in + *-*-*bsd* ) + onlylibusb=yes + ;; + esac + AC_ARG_ENABLE(hidapi, AS_HELP_STRING([--enable-hidapi], [use HIDAPI for low level joystick drivers [[default=maybe]]]), , enable_hidapi=maybe) @@ -3237,9 +3245,30 @@ AS_HELP_STRING([--enable-hidapi], [use HIDAPI for low level joystick drivers [[d SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c" if test x$skiplibusb = xno; then - SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c" EXTRA_CFLAGS="$EXTRA_CFLAGS $LIBUSB_CFLAGS" - EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LIBS" + if test x$onlylibusb = xyes; then + SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c" + EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LIBS" + else + if test x$have_loadso != xyes; then + AC_MSG_WARN([You must have SDL_LoadObject() support for dynamic libusb loading]) + fi + SOURCES="$SOURCES $srcdir/src/hidapi/SDL_hidapi.c" + # libusb is loaded dynamically, so don't add it to LDFLAGS + libusb_lib="" + case "$host" in + *-*-darwin* ) + libusb_lib="libusb-1.0.0.dylib" + ;; + *-*-cygwin* | *-*-mingw32* ) + libusb_lib="libusb-1.0.dll" + ;; + esac + if test x$libusb_lib = x; then + libusb_lib=[`find_lib "libusb-1.0.so.*" "" | sed 's/.*\/\(.*\)/\1/; q'`] + fi + AC_DEFINE_UNQUOTED(SDL_LIBUSB_DYNAMIC, "$libusb_lib", [ ]) + fi fi fi @@ -3599,6 +3628,7 @@ AS_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau fi SOURCES="$SOURCES $srcdir/src/joystick/windows/*.c" have_joystick=yes + # TODO: Remove this block once SDL_hidapi.c supports Windows! if test x$hidapi_support = xyes; then SOURCES="$SOURCES $srcdir/src/hidapi/windows/hid.c" fi @@ -3842,9 +3872,6 @@ AS_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau AC_DEFINE(SDL_JOYSTICK_IOKIT, 1, [ ]) SOURCES="$SOURCES $srcdir/src/joystick/darwin/*.c" have_joystick=yes - if test x$hidapi_support = xyes; then - SOURCES="$SOURCES $srcdir/src/hidapi/mac/hid.c" - fi fi # Set up files for the haptic library if test x$enable_haptic = xyes; then diff --git a/include/SDL_config.h.cmake b/include/SDL_config.h.cmake index 5e9e5bcea..75ff0bb7a 100644 --- a/include/SDL_config.h.cmake +++ b/include/SDL_config.h.cmake @@ -294,6 +294,7 @@ #cmakedefine SDL_HAPTIC_DINPUT @SDL_HAPTIC_DINPUT@ #cmakedefine SDL_HAPTIC_XINPUT @SDL_HAPTIC_XINPUT@ #cmakedefine SDL_HAPTIC_ANDROID @SDL_HAPTIC_ANDROID@ +#cmakedefine SDL_LIBUSB_DYNAMIC @SDL_LIBUSB_DYNAMIC@ /* Enable various sensor drivers */ #cmakedefine SDL_SENSOR_ANDROID @SDL_SENSOR_ANDROID@ diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index a5e9d1328..e4eb6f648 100644 --- a/include/SDL_config.h.in +++ b/include/SDL_config.h.in @@ -412,6 +412,9 @@ /* Enable dynamic udev support */ #undef SDL_UDEV_DYNAMIC +/* Enable dynamic libusb support */ +#undef SDL_LIBUSB_DYNAMIC + /* Enable dynamic libsamplerate support */ #undef SDL_LIBSAMPLERATE_DYNAMIC diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c new file mode 100644 index 000000000..782638f88 --- /dev/null +++ b/src/hidapi/SDL_hidapi.c @@ -0,0 +1,744 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2019 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* Original hybrid wrapper for Linux by Valve Software. Their original notes: + * + * The libusb version doesn't support Bluetooth, but not all Linux + * distributions allow access to /dev/hidraw* + * + * This merges the two, at a small performance cost, until distributions + * have granted access to /dev/hidraw* + */ + +#include "../SDL_internal.h" +#include "SDL_loadso.h" + +#ifdef SDL_JOYSTICK_HIDAPI + +/* Platform HIDAPI Implementation */ + +#define hid_device_ PLATFORM_hid_device_ +#define hid_device PLATFORM_hid_device +#define hid_device_info PLATFORM_hid_device_info +#define hid_init PLATFORM_hid_init +#define hid_exit PLATFORM_hid_exit +#define hid_enumerate PLATFORM_hid_enumerate +#define hid_free_enumeration PLATFORM_hid_free_enumeration +#define hid_open PLATFORM_hid_open +#define hid_open_path PLATFORM_hid_open_path +#define hid_write PLATFORM_hid_write +#define hid_read_timeout PLATFORM_hid_read_timeout +#define hid_read PLATFORM_hid_read +#define hid_set_nonblocking PLATFORM_hid_set_nonblocking +#define hid_send_feature_report PLATFORM_hid_send_feature_report +#define hid_get_feature_report PLATFORM_hid_get_feature_report +#define hid_close PLATFORM_hid_close +#define hid_get_manufacturer_string PLATFORM_hid_get_manufacturer_string +#define hid_get_product_string PLATFORM_hid_get_product_string +#define hid_get_serial_number_string PLATFORM_hid_get_serial_number_string +#define hid_get_indexed_string PLATFORM_hid_get_indexed_string +#define hid_error PLATFORM_hid_error +#define new_hid_device PLATFORM_new_hid_device +#define free_hid_device PLATFORM_free_hid_device +#define input_report PLATFORM_input_report +#define return_data PLATFORM_return_data +#define make_path PLATFORM_make_path +#define read_thread PLATFORM_read_thread + +#if __LINUX__ + +#include "../../core/linux/SDL_udev.h" +#if SDL_USE_LIBUDEV +static const SDL_UDEV_Symbols *udev_ctx = NULL; + +#define udev_device_get_sysattr_value udev_ctx->udev_device_get_sysattr_value +#define udev_new udev_ctx->udev_new +#define udev_unref udev_ctx->udev_unref +#define udev_device_new_from_devnum udev_ctx->udev_device_new_from_devnum +#define udev_device_get_parent_with_subsystem_devtype udev_ctx->udev_device_get_parent_with_subsystem_devtype +#define udev_device_unref udev_ctx->udev_device_unref +#define udev_enumerate_new udev_ctx->udev_enumerate_new +#define udev_enumerate_add_match_subsystem udev_ctx->udev_enumerate_add_match_subsystem +#define udev_enumerate_scan_devices udev_ctx->udev_enumerate_scan_devices +#define udev_enumerate_get_list_entry udev_ctx->udev_enumerate_get_list_entry +#define udev_list_entry_get_name udev_ctx->udev_list_entry_get_name +#define udev_device_new_from_syspath udev_ctx->udev_device_new_from_syspath +#define udev_device_get_devnode udev_ctx->udev_device_get_devnode +#define udev_list_entry_get_next udev_ctx->udev_list_entry_get_next +#define udev_enumerate_unref udev_ctx->udev_enumerate_unref + +#include "linux/hid.c" +#define HAVE_PLATFORM_BACKEND 1 +#endif /* SDL_USE_LIBUDEV */ + +#elif __MACOSX__ +#include "mac/hid.c" +#define HAVE_PLATFORM_BACKEND 1 +#define udev_ctx 1 +#elif __WINDOWS__ +#include "windows/hid.c" +#define HAVE_PLATFORM_BACKEND 1 +#define udev_ctx 1 +#else +#error Need a hid.c for this platform! +#endif + +#undef hid_device_ +#undef hid_device +#undef hid_device_info +#undef hid_init +#undef hid_exit +#undef hid_enumerate +#undef hid_free_enumeration +#undef hid_open +#undef hid_open_path +#undef hid_write +#undef hid_read_timeout +#undef hid_read +#undef hid_set_nonblocking +#undef hid_send_feature_report +#undef hid_get_feature_report +#undef hid_close +#undef hid_get_manufacturer_string +#undef hid_get_product_string +#undef hid_get_serial_number_string +#undef hid_get_indexed_string +#undef hid_error +#undef new_hid_device +#undef free_hid_device +#undef input_report +#undef return_data +#undef make_path +#undef read_thread + +#ifndef SDL_LIBUSB_DYNAMIC +#if __WINDOWS__ +#define SDL_LIBUSB_DYNAMIC "libusb-1.0.dll" +#endif /* __WINDOWS__ */ +#endif /* SDL_LIBUSB_DYNAMIC */ + +#ifdef SDL_LIBUSB_DYNAMIC +/* libusb HIDAPI Implementation */ + +/* Include this now, for our dynamically-loaded libusb context */ +#include + +static struct +{ + void* libhandle; + + int (*init)(libusb_context **ctx); + void (*exit)(libusb_context *ctx); + ssize_t (*get_device_list)(libusb_context *ctx, libusb_device ***list); + void (*free_device_list)(libusb_device **list, int unref_devices); + int (*get_device_descriptor)(libusb_device *dev, struct libusb_device_descriptor *desc); + int (*get_active_config_descriptor)(libusb_device *dev, struct libusb_config_descriptor **config); + int (*get_config_descriptor)( + libusb_device *dev, + uint8_t config_index, + struct libusb_config_descriptor **config + ); + void (*free_config_descriptor)(struct libusb_config_descriptor *config); + uint8_t (*get_bus_number)(libusb_device *dev); + uint8_t (*get_device_address)(libusb_device *dev); + int (*open)(libusb_device *dev, libusb_device_handle **dev_handle); + void (*close)(libusb_device_handle *dev_handle); + int (*claim_interface)(libusb_device_handle *dev_handle, int interface_number); + int (*release_interface)(libusb_device_handle *dev_handle, int interface_number); + int (*kernel_driver_active)(libusb_device_handle *dev_handle, int interface_number); + int (*detach_kernel_driver)(libusb_device_handle *dev_handle, int interface_number); + struct libusb_transfer * (*alloc_transfer)(int iso_packets); + int (*submit_transfer)(struct libusb_transfer *transfer); + int (*cancel_transfer)(struct libusb_transfer *transfer); + void (*free_transfer)(struct libusb_transfer *transfer); + int (*control_transfer)( + libusb_device_handle *dev_handle, + uint8_t request_type, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + unsigned char *data, + uint16_t wLength, + unsigned int timeout + ); + int (*interrupt_transfer)( + libusb_device_handle *dev_handle, + unsigned char endpoint, + unsigned char *data, + int length, + int *actual_length, + unsigned int timeout + ); + int (*handle_events)(libusb_context *ctx); + int (*handle_events_completed)(libusb_context *ctx, int *completed); +} libusb_ctx; + +#define libusb_init libusb_ctx.init +#define libusb_exit libusb_ctx.exit +#define libusb_get_device_list libusb_ctx.get_device_list +#define libusb_free_device_list libusb_ctx.free_device_list +#define libusb_get_device_descriptor libusb_ctx.get_device_descriptor +#define libusb_get_active_config_descriptor libusb_ctx.get_active_config_descriptor +#define libusb_get_config_descriptor libusb_ctx.get_config_descriptor +#define libusb_free_config_descriptor libusb_ctx.free_config_descriptor +#define libusb_get_bus_number libusb_ctx.get_bus_number +#define libusb_get_device_address libusb_ctx.get_device_address +#define libusb_open libusb_ctx.open +#define libusb_close libusb_ctx.close +#define libusb_claim_interface libusb_ctx.claim_interface +#define libusb_release_interface libusb_ctx.release_interface +#define libusb_kernel_driver_active libusb_ctx.kernel_driver_active +#define libusb_detach_kernel_driver libusb_ctx.detach_kernel_driver +#define libusb_alloc_transfer libusb_ctx.alloc_transfer +#define libusb_submit_transfer libusb_ctx.submit_transfer +#define libusb_cancel_transfer libusb_ctx.cancel_transfer +#define libusb_free_transfer libusb_ctx.free_transfer +#define libusb_control_transfer libusb_ctx.control_transfer +#define libusb_interrupt_transfer libusb_ctx.interrupt_transfer +#define libusb_handle_events libusb_ctx.handle_events +#define libusb_handle_events_completed libusb_ctx.handle_events_completed + +#define hid_device_ LIBUSB_hid_device_ +#define hid_device LIBUSB_hid_device +#define hid_device_info LIBUSB_hid_device_info +#define hid_init LIBUSB_hid_init +#define hid_exit LIBUSB_hid_exit +#define hid_enumerate LIBUSB_hid_enumerate +#define hid_free_enumeration LIBUSB_hid_free_enumeration +#define hid_open LIBUSB_hid_open +#define hid_open_path LIBUSB_hid_open_path +#define hid_write LIBUSB_hid_write +#define hid_read_timeout LIBUSB_hid_read_timeout +#define hid_read LIBUSB_hid_read +#define hid_set_nonblocking LIBUSB_hid_set_nonblocking +#define hid_send_feature_report LIBUSB_hid_send_feature_report +#define hid_get_feature_report LIBUSB_hid_get_feature_report +#define hid_close LIBUSB_hid_close +#define hid_get_manufacturer_string LIBUSB_hid_get_manufacturer_string +#define hid_get_product_string LIBUSB_hid_get_product_string +#define hid_get_serial_number_string LIBUSB_hid_get_serial_number_string +#define hid_get_indexed_string LIBUSB_hid_get_indexed_string +#define hid_error LIBUSB_hid_error +#define new_hid_device LIBUSB_new_hid_device +#define free_hid_device LIBUSB_free_hid_device +#define input_report LIBUSB_input_report +#define return_data LIBUSB_return_data +#define make_path LIBUSB_make_path +#define read_thread LIBUSB_read_thread + +#ifndef __FreeBSD__ +/* this is awkwardly inlined, so we need to re-implement it here + * so we can override the libusb_control_transfer call */ +static int +SDL_libusb_get_string_descriptor(libusb_device_handle *dev, + uint8_t descriptor_index, uint16_t lang_id, + unsigned char *data, int length) +{ + return libusb_control_transfer(dev, + LIBUSB_ENDPOINT_IN | 0x0, /* Endpoint 0 IN */ + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_STRING << 8) | descriptor_index, + lang_id, + data, + (uint16_t) length, + 1000); +} +#define libusb_get_string_descriptor SDL_libusb_get_string_descriptor +#endif /* __FreeBSD__ */ + +#undef HIDAPI_H__ +#include "libusb/hid.c" + +#undef hid_device_ +#undef hid_device +#undef hid_device_info +#undef hid_init +#undef hid_exit +#undef hid_enumerate +#undef hid_free_enumeration +#undef hid_open +#undef hid_open_path +#undef hid_write +#undef hid_read_timeout +#undef hid_read +#undef hid_set_nonblocking +#undef hid_send_feature_report +#undef hid_get_feature_report +#undef hid_close +#undef hid_get_manufacturer_string +#undef hid_get_product_string +#undef hid_get_serial_number_string +#undef hid_get_indexed_string +#undef hid_error +#undef new_hid_device +#undef free_hid_device +#undef input_report +#undef return_data +#undef make_path +#undef read_thread + +#endif /* SDL_LIBUSB_DYNAMIC */ + +/* Shared HIDAPI Implementation */ + +#undef HIDAPI_H__ +#include "hidapi.h" + +struct hidapi_backend { +#define F(x) typeof(x) *x + F(hid_write); + F(hid_read_timeout); + F(hid_read); + F(hid_set_nonblocking); + F(hid_send_feature_report); + F(hid_get_feature_report); + F(hid_close); + F(hid_get_manufacturer_string); + F(hid_get_product_string); + F(hid_get_serial_number_string); + F(hid_get_indexed_string); + F(hid_error); +#undef F +}; + +#if HAVE_PLATFORM_BACKEND +static const struct hidapi_backend PLATFORM_Backend = { + (void*)PLATFORM_hid_write, + (void*)PLATFORM_hid_read_timeout, + (void*)PLATFORM_hid_read, + (void*)PLATFORM_hid_set_nonblocking, + (void*)PLATFORM_hid_send_feature_report, + (void*)PLATFORM_hid_get_feature_report, + (void*)PLATFORM_hid_close, + (void*)PLATFORM_hid_get_manufacturer_string, + (void*)PLATFORM_hid_get_product_string, + (void*)PLATFORM_hid_get_serial_number_string, + (void*)PLATFORM_hid_get_indexed_string, + (void*)PLATFORM_hid_error +}; +#endif /* HAVE_PLATFORM_BACKEND */ + +#ifdef SDL_LIBUSB_DYNAMIC +static const struct hidapi_backend LIBUSB_Backend = { + (void*)LIBUSB_hid_write, + (void*)LIBUSB_hid_read_timeout, + (void*)LIBUSB_hid_read, + (void*)LIBUSB_hid_set_nonblocking, + (void*)LIBUSB_hid_send_feature_report, + (void*)LIBUSB_hid_get_feature_report, + (void*)LIBUSB_hid_close, + (void*)LIBUSB_hid_get_manufacturer_string, + (void*)LIBUSB_hid_get_product_string, + (void*)LIBUSB_hid_get_serial_number_string, + (void*)LIBUSB_hid_get_indexed_string, + (void*)LIBUSB_hid_error +}; +#endif /* SDL_LIBUSB_DYNAMIC */ + +typedef struct _HIDDeviceWrapper HIDDeviceWrapper; +struct _HIDDeviceWrapper +{ + hid_device *device; /* must be first field */ + const struct hidapi_backend *backend; +}; + +static HIDDeviceWrapper * +CreateHIDDeviceWrapper(hid_device *device, const struct hidapi_backend *backend) +{ + HIDDeviceWrapper *ret = SDL_malloc(sizeof(*ret)); + ret->device = device; + ret->backend = backend; + return ret; +} + +static hid_device * +WrapHIDDevice(HIDDeviceWrapper *wrapper) +{ + return (hid_device *)wrapper; +} + +static HIDDeviceWrapper * +UnwrapHIDDevice(hid_device *device) +{ + return (HIDDeviceWrapper *)device; +} + +static void +DeleteHIDDeviceWrapper(HIDDeviceWrapper *device) +{ + SDL_free(device); +} + +#define COPY_IF_EXISTS(var) \ + if (pSrc->var != NULL) { \ + pDst->var = SDL_strdup(pSrc->var); \ + } else { \ + pDst->var = NULL; \ + } +#define WCOPY_IF_EXISTS(var) \ + if (pSrc->var != NULL) { \ + pDst->var = SDL_wcsdup(pSrc->var); \ + } else { \ + pDst->var = NULL; \ + } + +#ifdef SDL_LIBUSB_DYNAMIC +static void +LIBUSB_CopyHIDDeviceInfo(struct LIBUSB_hid_device_info *pSrc, + struct hid_device_info *pDst) +{ + COPY_IF_EXISTS(path) + pDst->vendor_id = pSrc->vendor_id; + pDst->product_id = pSrc->product_id; + WCOPY_IF_EXISTS(serial_number) + pDst->release_number = pSrc->release_number; + WCOPY_IF_EXISTS(manufacturer_string) + WCOPY_IF_EXISTS(product_string) + pDst->usage_page = pSrc->usage_page; + pDst->usage = pSrc->usage; + pDst->interface_number = pSrc->interface_number; + pDst->next = NULL; +} +#endif /* SDL_LIBUSB_DYNAMIC */ + +#if HAVE_PLATFORM_BACKEND +static void +PLATFORM_CopyHIDDeviceInfo(struct PLATFORM_hid_device_info *pSrc, + struct hid_device_info *pDst) +{ + COPY_IF_EXISTS(path) + pDst->vendor_id = pSrc->vendor_id; + pDst->product_id = pSrc->product_id; + WCOPY_IF_EXISTS(serial_number) + pDst->release_number = pSrc->release_number; + WCOPY_IF_EXISTS(manufacturer_string) + WCOPY_IF_EXISTS(product_string) + pDst->usage_page = pSrc->usage_page; + pDst->usage = pSrc->usage; + pDst->interface_number = pSrc->interface_number; + pDst->next = NULL; +} +#endif /* HAVE_PLATFORM_BACKEND */ + +#undef COPY_IF_EXISTS +#undef WCOPY_IF_EXISTS + +static SDL_bool SDL_hidapi_wasinit = SDL_FALSE; + +int HID_API_EXPORT HID_API_CALL hid_init(void) +{ + int err; + + if (SDL_hidapi_wasinit == SDL_TRUE) { + return 0; + } + +#ifdef SDL_LIBUSB_DYNAMIC + libusb_ctx.libhandle = SDL_LoadObject(SDL_LIBUSB_DYNAMIC); + if (libusb_ctx.libhandle != NULL) { + #define LOAD_LIBUSB_SYMBOL(func) \ + libusb_ctx.func = SDL_LoadFunction(libusb_ctx.libhandle, "libusb_" #func); + LOAD_LIBUSB_SYMBOL(init) + LOAD_LIBUSB_SYMBOL(exit) + LOAD_LIBUSB_SYMBOL(get_device_list) + LOAD_LIBUSB_SYMBOL(free_device_list) + LOAD_LIBUSB_SYMBOL(get_device_descriptor) + LOAD_LIBUSB_SYMBOL(get_active_config_descriptor) + LOAD_LIBUSB_SYMBOL(get_config_descriptor) + LOAD_LIBUSB_SYMBOL(free_config_descriptor) + LOAD_LIBUSB_SYMBOL(get_bus_number) + LOAD_LIBUSB_SYMBOL(get_device_address) + LOAD_LIBUSB_SYMBOL(open) + LOAD_LIBUSB_SYMBOL(close) + LOAD_LIBUSB_SYMBOL(claim_interface) + LOAD_LIBUSB_SYMBOL(release_interface) + LOAD_LIBUSB_SYMBOL(kernel_driver_active) + LOAD_LIBUSB_SYMBOL(detach_kernel_driver) + LOAD_LIBUSB_SYMBOL(alloc_transfer) + LOAD_LIBUSB_SYMBOL(submit_transfer) + LOAD_LIBUSB_SYMBOL(cancel_transfer) + LOAD_LIBUSB_SYMBOL(free_transfer) + LOAD_LIBUSB_SYMBOL(control_transfer) + LOAD_LIBUSB_SYMBOL(interrupt_transfer) + LOAD_LIBUSB_SYMBOL(handle_events) + LOAD_LIBUSB_SYMBOL(handle_events_completed) + #undef LOAD_LIBUSB_SYMBOL + + if ((err = LIBUSB_hid_init()) < 0) { + SDL_UnloadObject(libusb_ctx.libhandle); + return err; + } + } +#endif /* SDL_LIBUSB_DYNAMIC */ + +#if HAVE_PLATFORM_BACKEND +#if __LINUX__ + udev_ctx = SDL_UDEV_GetUdevSyms(); +#endif /* __LINUX __ */ + if (udev_ctx && (err = PLATFORM_hid_init()) < 0) { +#ifdef SDL_LIBUSB_DYNAMIC + if (libusb_ctx.libhandle) { + SDL_UnloadObject(libusb_ctx.libhandle); + } +#endif /* SDL_LIBUSB_DYNAMIC */ + return err; + } +#endif /* HAVE_PLATFORM_BACKEND */ + + return 0; +} + +int HID_API_EXPORT HID_API_CALL hid_exit(void) +{ + int err = 0; + + if (SDL_hidapi_wasinit == SDL_FALSE) { + return 0; + } + +#if HAVE_PLATFORM_BACKEND + if (udev_ctx) { + err = PLATFORM_hid_exit(); + } +#endif /* HAVE_PLATFORM_BACKEND */ +#ifdef SDL_LIBUSB_DYNAMIC + if (libusb_ctx.libhandle) { + err |= LIBUSB_hid_exit(); /* Ehhhhh */ + SDL_UnloadObject(libusb_ctx.libhandle); + } +#endif /* SDL_LIBUSB_DYNAMIC */ + return err; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ +#if HAVE_PLATFORM_BACKEND + struct PLATFORM_hid_device_info *raw_devs = NULL; + struct PLATFORM_hid_device_info *raw_dev; +#endif /* HAVE_PLATFORM_BACKEND */ + struct hid_device_info *devs = NULL, *last = NULL, *new_dev; + SDL_bool bFound; + + if (SDL_hidapi_wasinit == SDL_FALSE) { + hid_init(); + } + +#if HAVE_PLATFORM_BACKEND + if (udev_ctx) { + raw_devs = PLATFORM_hid_enumerate(vendor_id, product_id); + } +#endif /* HAVE_PLATFORM_BACKEND */ + +#ifdef SDL_LIBUSB_DYNAMIC + if (libusb_ctx.libhandle) { + struct LIBUSB_hid_device_info *usb_devs = LIBUSB_hid_enumerate(vendor_id, product_id); + struct LIBUSB_hid_device_info *usb_dev; + for (usb_dev = usb_devs; usb_dev; usb_dev = usb_dev->next) { + bFound = SDL_FALSE; +#if HAVE_PLATFORM_BACKEND + for (raw_dev = raw_devs; raw_dev; raw_dev = raw_dev->next) { + if (usb_dev->vendor_id == raw_dev->vendor_id && + usb_dev->product_id == raw_dev->product_id) { + + bFound = SDL_TRUE; + break; + } + } +#endif + + if (!bFound) { + new_dev = (struct hid_device_info*) SDL_malloc(sizeof(struct hid_device_info)); + LIBUSB_CopyHIDDeviceInfo(usb_dev, new_dev); + + if (last != NULL) { + last->next = new_dev; + } else { + devs = new_dev; + } + last = new_dev; + } + } + LIBUSB_hid_free_enumeration(usb_devs); + } +#endif /* SDL_LIBUSB_DYNAMIC */ + +#if HAVE_PLATFORM_BACKEND + if (udev_ctx) { + for (raw_dev = raw_devs; raw_dev; raw_dev = raw_dev->next) { + new_dev = (struct hid_device_info*) SDL_malloc(sizeof(struct hid_device_info)); + PLATFORM_CopyHIDDeviceInfo(raw_dev, new_dev); + new_dev->next = NULL; + + if (last != NULL) { + last->next = new_dev; + } else { + devs = new_dev; + } + last = new_dev; + } + PLATFORM_hid_free_enumeration(raw_devs); + } +#endif /* HAVE_PLATFORM_BACKEND */ + + return devs; +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + while (devs) { + struct hid_device_info *next = devs->next; + SDL_free(devs->path); + SDL_free(devs->serial_number); + SDL_free(devs->manufacturer_string); + SDL_free(devs->product_string); + SDL_free(devs); + devs = next; + } +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + hid_device *pDevice = NULL; + + if (SDL_hidapi_wasinit == SDL_FALSE) { + hid_init(); + } + +#if HAVE_PLATFORM_BACKEND + if (udev_ctx && + (pDevice = (hid_device*) PLATFORM_hid_open(vendor_id, product_id, serial_number)) != NULL) { + + HIDDeviceWrapper *wrapper = CreateHIDDeviceWrapper(pDevice, &PLATFORM_Backend); + return WrapHIDDevice(wrapper); + } +#endif /* HAVE_PLATFORM_BACKEND */ +#ifdef SDL_LIBUSB_DYNAMIC + if (libusb_ctx.libhandle && + (pDevice = (hid_device*) LIBUSB_hid_open(vendor_id, product_id, serial_number)) != NULL) { + + HIDDeviceWrapper *wrapper = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + return WrapHIDDevice(wrapper); + } +#endif /* SDL_LIBUSB_DYNAMIC */ + return NULL; +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, int bExclusive /* = false */) +{ + hid_device *pDevice = NULL; + + if (SDL_hidapi_wasinit == SDL_FALSE) { + hid_init(); + } + +#if HAVE_PLATFORM_BACKEND + if (udev_ctx && + (pDevice = (hid_device*) PLATFORM_hid_open_path(path, bExclusive)) != NULL) { + + HIDDeviceWrapper *wrapper = CreateHIDDeviceWrapper(pDevice, &PLATFORM_Backend); + return WrapHIDDevice(wrapper); + } +#endif /* HAVE_PLATFORM_BACKEND */ +#ifdef SDL_LIBUSB_DYNAMIC + if (libusb_ctx.libhandle && + (pDevice = (hid_device*) LIBUSB_hid_open_path(path, bExclusive)) != NULL) { + + HIDDeviceWrapper *wrapper = CreateHIDDeviceWrapper(pDevice, &LIBUSB_Backend); + return WrapHIDDevice(wrapper); + } +#endif /* SDL_LIBUSB_DYNAMIC */ + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_write(wrapper->device, data, length); +} + +int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *device, unsigned char *data, size_t length, int milliseconds) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_read_timeout(wrapper->device, data, length, milliseconds); +} + +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_read(wrapper->device, data, length); +} + +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_set_nonblocking(wrapper->device, nonblock); +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_send_feature_report(wrapper->device, data, length); +} + +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_get_feature_report(wrapper->device, data, length); +} + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + wrapper->backend->hid_close(wrapper->device); + DeleteHIDDeviceWrapper(wrapper); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_get_manufacturer_string(wrapper->device, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_get_product_string(wrapper->device, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_get_serial_number_string(wrapper->device, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_get_indexed_string(wrapper->device, string_index, string, maxlen); +} + +HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device) +{ + HIDDeviceWrapper *wrapper = UnwrapHIDDevice(device); + return wrapper->backend->hid_error(wrapper->device); +} + +#endif /* SDL_JOYSTICK_HIDAPI */