diff --git a/WhatsNew.txt b/WhatsNew.txt index e6076c552..732f1a51d 100644 --- a/WhatsNew.txt +++ b/WhatsNew.txt @@ -11,9 +11,11 @@ Linux: Android: * Added support for audio output and capture using AAudio on Android 8.1 and newer +* Steam Controller support is disabled by default, and can be enabled by setting the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM to "1" before calling SDL_Init() iOS: * Added documentation that the UIApplicationSupportsIndirectInputEvents key must be set to true in your application's Info.plist in order to get real Bluetooth mouse events. +* Steam Controller support is disabled by default, and can be enabled by setting the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM to "1" before calling SDL_Init() --------------------------------------------------------------------------- diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml index 857253141..d997afe4a 100644 --- a/android-project/app/src/main/AndroidManifest.xml +++ b/android-project/app/src/main/AndroidManifest.xml @@ -39,9 +39,13 @@ android:required="false" /> --> - + + - + + + + diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java index bf868aea6..5ebbb9729 100644 --- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java @@ -119,9 +119,6 @@ public class HIDDeviceManager { { mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); } - - initializeUSB(); - initializeBluetooth(); } public Context getContext() { @@ -349,7 +346,8 @@ public class HIDDeviceManager { private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); - if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT <= 30 && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); return; } @@ -541,6 +539,18 @@ public class HIDDeviceManager { ////////// JNI interface functions ////////////////////////////////////////////////////////////////////////////////////////////////////// + public boolean initialize(boolean usb, boolean bluetooth) { + Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); + + if (usb) { + initializeUSB(); + } + if (bluetooth) { + initializeBluetooth(); + } + return true; + } + public boolean openDevice(int deviceID) { Log.v(TAG, "openDevice deviceID=" + deviceID); HIDDevice device = getDevice(deviceID); diff --git a/include/SDL_hints.h b/include/SDL_hints.h index d7782591b..87242879b 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -720,9 +720,10 @@ extern "C" { * * This variable can be set to the following values: * "0" - HIDAPI driver is not used - * "1" - HIDAPI driver is used + * "1" - HIDAPI driver is used for Steam Controllers, which requires Bluetooth access + * and may prompt the user for permission on iOS and Android. * - * The default is the value of SDL_HINT_JOYSTICK_HIDAPI + * The default is "0" */ #define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM" diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp index 9113a4d89..bac7bc499 100644 --- a/src/hidapi/android/hid.cpp +++ b/src/hidapi/android/hid.cpp @@ -18,12 +18,17 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ +#include "../../SDL_internal.h" + // Purpose: A wrapper implementing "HID" API for Android // // This layer glues the hidapi API to Android's USB and BLE stack. #if !SDL_HIDAPI_DISABLED +#include "SDL_hints.h" +#include "../../core/android/SDL_android.h" + #define hid_init PLATFORM_hid_init #define hid_exit PLATFORM_hid_exit #define hid_enumerate PLATFORM_hid_enumerate @@ -371,17 +376,49 @@ static void FreeHIDDeviceInfo( hid_device_info *pInfo ) static jclass g_HIDDeviceManagerCallbackClass; static jobject g_HIDDeviceManagerCallbackHandler; +static jmethodID g_midHIDDeviceManagerInitialize; static jmethodID g_midHIDDeviceManagerOpen; static jmethodID g_midHIDDeviceManagerSendOutputReport; static jmethodID g_midHIDDeviceManagerSendFeatureReport; static jmethodID g_midHIDDeviceManagerGetFeatureReport; static jmethodID g_midHIDDeviceManagerClose; +static bool g_initialized = false; static uint64_t get_timespec_ms( const struct timespec &ts ) { return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } +static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *pszMethodName ) +{ + if ( env->ExceptionCheck() ) + { + // Get our exception + jthrowable jExcept = env->ExceptionOccurred(); + + // Clear the exception so we can call JNI again + env->ExceptionClear(); + + // Get our exception message + jclass jExceptClass = env->GetObjectClass( jExcept ); + jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" ); + jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) ); + const char *pszMessage = env->GetStringUTFChars( jMessage, NULL ); + + // ...and log it. + LOGE( "%s%s%s threw an exception: %s", + pszClassName ? pszClassName : "", + pszClassName ? "::" : "", + pszMethodName, pszMessage ); + + // Cleanup + env->ReleaseStringUTFChars( jMessage, pszMessage ); + env->DeleteLocalRef( jMessage ); + env->DeleteLocalRef( jExceptClass ); + env->DeleteLocalRef( jExcept ); + } +} + class CHIDDevice { public: @@ -441,29 +478,7 @@ public: void ExceptionCheck( JNIEnv *env, const char *pszMethodName ) { - if ( env->ExceptionCheck() ) - { - // Get our exception - jthrowable jExcept = env->ExceptionOccurred(); - - // Clear the exception so we can call JNI again - env->ExceptionClear(); - - // Get our exception message - jclass jExceptClass = env->GetObjectClass( jExcept ); - jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" ); - jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) ); - const char *pszMessage = env->GetStringUTFChars( jMessage, NULL ); - - // ...and log it. - LOGE( "CHIDDevice::%s threw an exception: %s", pszMethodName, pszMessage ); - - // Cleanup - env->ReleaseStringUTFChars( jMessage, pszMessage ); - env->DeleteLocalRef( jMessage ); - env->DeleteLocalRef( jExceptClass ); - env->DeleteLocalRef( jExcept ); - } + ::ExceptionCheck( env, "CHIDDevice", pszMethodName ); } bool BOpen() @@ -852,6 +867,11 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallba if ( objClass ) { g_HIDDeviceManagerCallbackClass = reinterpret_cast< jclass >( env->NewGlobalRef( objClass ) ); + g_midHIDDeviceManagerInitialize = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "initialize", "(ZZ)Z" ); + if ( !g_midHIDDeviceManagerInitialize ) + { + __android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing initialize" ); + } g_midHIDDeviceManagerOpen = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "openDevice", "(I)Z" ); if ( !g_midHIDDeviceManagerOpen ) { @@ -891,6 +911,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac g_HIDDeviceManagerCallbackClass = NULL; env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler ); g_HIDDeviceManagerCallbackHandler = NULL; + g_initialized = false; } } @@ -1025,6 +1046,33 @@ extern "C" int hid_init(void) { + if ( !g_initialized ) + { + // Make sure thread is attached to JVM/env + JNIEnv *env; + g_JVM->AttachCurrentThread( &env, NULL ); + pthread_setspecific( g_ThreadKey, (void*)env ); + + if ( !g_HIDDeviceManagerCallbackHandler ) + { + LOGV( "hid_init() without callback handler" ); + return -1; + } + + // Bluetooth is currently only used for Steam Controllers, so check that hint + // before initializing Bluetooth, which will prompt the user for permission. + bool init_usb = true; + bool init_bluetooth = false; + if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE)) { + if (SDL_GetAndroidSDKVersion() < 31 || + Android_JNI_RequestPermission("android.permission.BLUETOOTH_CONNECT")) { + init_bluetooth = true; + } + } + env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerInitialize, init_usb, init_bluetooth ); + ExceptionCheck( env, NULL, "hid_init" ); + g_initialized = true; // Regardless of result, so it's only called once + } return 0; } diff --git a/src/hidapi/ios/hid.m b/src/hidapi/ios/hid.m index e193a727c..111b8f236 100644 --- a/src/hidapi/ios/hid.m +++ b/src/hidapi/ios/hid.m @@ -22,6 +22,8 @@ #if !SDL_HIDAPI_DISABLED +#include "SDL_hints.h" + #define hid_init PLATFORM_hid_init #define hid_exit PLATFORM_hid_exit #define hid_enumerate PLATFORM_hid_enumerate @@ -227,24 +229,29 @@ typedef enum sharedInstance = [HIDBLEManager new]; sharedInstance.nPendingScans = 0; sharedInstance.nPendingPairs = 0; - - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; - // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical - // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means - // that we can still screw this up. - // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really - // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY - // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery. - // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed - sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL ); - dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) ); + // Bluetooth is currently only used for Steam Controllers, so check that hint + // before initializing Bluetooth, which will prompt the user for permission. + if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE ) ) + { + [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; - // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState: - // where any scanning gets started or connecting to existing peripherals happens, it's never already in a - // powered-on state for a newly launched application. - sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue]; + // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical + // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means + // that we can still screw this up. + // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really + // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY + // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery. + // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed + sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL ); + dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) ); + + // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState: + // where any scanning gets started or connecting to existing peripherals happens, it's never already in a + // powered-on state for a newly launched application. + sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue]; + } sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4]; }); return sharedInstance; @@ -284,6 +291,11 @@ typedef enum static uint64_t s_unLastUpdateTick = 0; static mach_timebase_info_data_t s_timebase_info; + if ( self.centralManager == nil ) + { + return 0; + } + if (s_timebase_info.denom == 0) { mach_timebase_info( &s_timebase_info ); @@ -329,6 +341,11 @@ typedef enum // manual API for folks to start & stop scanning - (void)startScan:(int)duration { + if ( self.centralManager == nil ) + { + return; + } + NSLog( @"BLE: requesting scan for %d seconds", duration ); @synchronized (self) { @@ -348,6 +365,11 @@ typedef enum - (void)stopScan { + if ( self.centralManager == nil ) + { + return; + } + NSLog( @"BLE: stopping scan" ); @synchronized (self) { diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c index 2b6f7b9d8..cd91feb8a 100644 --- a/src/joystick/hidapi/SDL_hidapi_gamecube.c +++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c @@ -539,6 +539,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube = { SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverGameCube_IsSupportedDevice, HIDAPI_DriverGameCube_GetDeviceName, HIDAPI_DriverGameCube_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c index 90d50d41e..fc814d0a0 100644 --- a/src/joystick/hidapi/SDL_hidapi_luna.c +++ b/src/joystick/hidapi/SDL_hidapi_luna.c @@ -438,6 +438,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna = { SDL_HINT_JOYSTICK_HIDAPI_LUNA, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverLuna_IsSupportedDevice, HIDAPI_DriverLuna_GetDeviceName, HIDAPI_DriverLuna_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index f0f70d80c..08f524ab1 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -933,6 +933,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = { SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverPS4_IsSupportedDevice, HIDAPI_DriverPS4_GetDeviceName, HIDAPI_DriverPS4_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index 5ba404775..2e3d3c6eb 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -1096,6 +1096,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = { SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverPS5_IsSupportedDevice, HIDAPI_DriverPS5_GetDeviceName, HIDAPI_DriverPS5_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c index 0df431555..b6b633175 100644 --- a/src/joystick/hidapi/SDL_hidapi_stadia.c +++ b/src/joystick/hidapi/SDL_hidapi_stadia.c @@ -306,6 +306,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia = { SDL_HINT_JOYSTICK_HIDAPI_STADIA, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverStadia_IsSupportedDevice, HIDAPI_DriverStadia_GetDeviceName, HIDAPI_DriverStadia_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c index 1b0e76d68..d89bd0834 100644 --- a/src/joystick/hidapi/SDL_hidapi_steam.c +++ b/src/joystick/hidapi/SDL_hidapi_steam.c @@ -1284,6 +1284,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam = { SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_TRUE, + SDL_FALSE, HIDAPI_DriverSteam_IsSupportedDevice, HIDAPI_DriverSteam_GetDeviceName, HIDAPI_DriverSteam_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 51c44ef2b..056845f16 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -1555,6 +1555,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch = { SDL_HINT_JOYSTICK_HIDAPI_SWITCH, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverSwitch_IsSupportedDevice, HIDAPI_DriverSwitch_GetDeviceName, HIDAPI_DriverSwitch_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c index 39cbbfa99..caa8c53dc 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -337,6 +337,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 = { SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverXbox360_IsSupportedDevice, HIDAPI_DriverXbox360_GetDeviceName, HIDAPI_DriverXbox360_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c index ae8bbdcaf..b58aab3c7 100644 --- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c +++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c @@ -334,6 +334,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W = { SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverXbox360W_IsSupportedDevice, HIDAPI_DriverXbox360W_GetDeviceName, HIDAPI_DriverXbox360W_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index 097deac5b..553467269 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -1127,6 +1127,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne = { SDL_HINT_JOYSTICK_HIDAPI_XBOX, SDL_TRUE, + SDL_TRUE, HIDAPI_DriverXboxOne_IsSupportedDevice, HIDAPI_DriverXboxOne_GetDeviceName, HIDAPI_DriverXboxOne_InitDevice, diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 282b14940..3adfb95f2 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -88,6 +88,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver { const char *hint; SDL_bool enabled; + SDL_bool enabled_default; SDL_bool (*IsSupportedDevice)(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol); const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id); SDL_bool (*InitDevice)(SDL_HIDAPI_Device *device);