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);