From 0dfc829a6b75b5a3c4c1d49fb943b622c12d67bc Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 10 Nov 2022 19:16:53 -0800 Subject: [PATCH] Added simple BLE Steam Controller support on all platforms This is still disabled by default via the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM --- Makefile.os2 | 2 +- Makefile.w32 | 2 +- VisualC-GDK/SDL/SDL.vcxproj | 1 + VisualC-GDK/SDL/SDL.vcxproj.filters | 3 ++ VisualC/SDL/SDL.vcxproj | 1 + VisualC/SDL/SDL.vcxproj.filters | 3 ++ Xcode/SDL/SDL.xcodeproj/project.pbxproj | 6 +++ include/SDL_hints.h | 2 +- src/joystick/hidapi/SDL_hidapi_steam.c | 48 +++++++++++++++------- src/joystick/hidapi/SDL_hidapijoystick_c.h | 6 +-- test/testgamecontroller.c | 1 + 11 files changed, 53 insertions(+), 22 deletions(-) diff --git a/Makefile.os2 b/Makefile.os2 index 76b2b398d..6ec172b37 100644 --- a/Makefile.os2 +++ b/Makefile.os2 @@ -94,7 +94,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_os2joystick.c SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_steam.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/Makefile.w32 b/Makefile.w32 index 68c3a3731..407bfd2a0 100644 --- a/Makefile.w32 +++ b/Makefile.w32 @@ -73,7 +73,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_steam.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index e3966e693..c8208b41f 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -600,6 +600,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 0c02e0796..02dc97ef3 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -1078,6 +1078,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index e3b66d1cb..2c85790e2 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -491,6 +491,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 167e40d29..082e257b4 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1069,6 +1069,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 9c086f1ff..a0741e06c 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -3393,6 +3393,9 @@ F323060528939F6400E66D30 /* SDL_hidapi_combined.c in Sources */ = {isa = PBXBuildFile; fileRef = F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */; }; F323060628939F6400E66D30 /* SDL_hidapi_combined.c in Sources */ = {isa = PBXBuildFile; fileRef = F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */; }; F323060728939F6400E66D30 /* SDL_hidapi_combined.c in Sources */ = {isa = PBXBuildFile; fileRef = F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */; }; + F34B9895291DEFF500AAC96E /* SDL_hidapi_steam.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */; }; + F34B9896291DEFF700AAC96E /* SDL_hidapi_steam.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */; }; + F34B9897291DEFFA00AAC96E /* SDL_hidapi_steam.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */; }; F3631C6424884ACF004F28EA /* SDL_locale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26792462701100718109 /* SDL_locale.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3631C652488534E004F28EA /* SDL_locale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26792462701100718109 /* SDL_locale.h */; settings = {ATTRIBUTES = (Public, ); }; }; F376F6192559B29300CFC0BC /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F376F6182559B29300CFC0BC /* OpenGLES.framework */; platformFilter = ios; }; @@ -8909,6 +8912,7 @@ A7D8BBD923E2574800DCD162 /* SDL_uikitmessagebox.m in Sources */, A7D8AD2923E2514100DCD162 /* SDL_vulkan_utils.c in Sources */, A7D8A95123E2514000DCD162 /* SDL_spinlock.c in Sources */, + F34B9895291DEFF500AAC96E /* SDL_hidapi_steam.c in Sources */, A7D8BAAF23E2514400DCD162 /* s_atan.c in Sources */, A7D8B75223E2514300DCD162 /* SDL_sysloadso.c in Sources */, A7D8BBE123E2574800DCD162 /* SDL_uikitopenglview.m in Sources */, @@ -9103,6 +9107,7 @@ A7D8B41F23E2514300DCD162 /* SDL_systls.c in Sources */, A7D8AD2C23E2514100DCD162 /* SDL_vulkan_utils.c in Sources */, A7D8A95423E2514000DCD162 /* SDL_spinlock.c in Sources */, + F34B9896291DEFF700AAC96E /* SDL_hidapi_steam.c in Sources */, A7D8BAB223E2514400DCD162 /* s_atan.c in Sources */, F3A490A12554D38600E92A8B /* SDL_hidapi_ps5.c in Sources */, A7D8B75523E2514300DCD162 /* SDL_sysloadso.c in Sources */, @@ -9297,6 +9302,7 @@ A7D8AD2E23E2514100DCD162 /* SDL_vulkan_utils.c in Sources */, A7D8A95623E2514000DCD162 /* SDL_spinlock.c in Sources */, A7D8BAB423E2514400DCD162 /* s_atan.c in Sources */, + F34B9897291DEFFA00AAC96E /* SDL_hidapi_steam.c in Sources */, A7D8B75723E2514300DCD162 /* SDL_sysloadso.c in Sources */, F3A490A42554D38600E92A8B /* SDL_hidapi_ps5.c in Sources */, A7D8B98B23E2514400DCD162 /* SDL_render_metal.m in Sources */, diff --git a/include/SDL_hints.h b/include/SDL_hints.h index ce2225440..d51525936 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -817,7 +817,7 @@ extern "C" { #define SDL_HINT_JOYSTICK_HIDAPI_STADIA "SDL_JOYSTICK_HIDAPI_STADIA" /** - * \brief A variable controlling whether the HIDAPI driver for Steam Controllers should be used. + * \brief A variable controlling whether the HIDAPI driver for Bluetooth Steam Controllers should be used. * * This variable can be set to the following values: * "0" - HIDAPI driver is not used diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c index 7b497fa95..9221d12e8 100644 --- a/src/joystick/hidapi/SDL_hidapi_steam.c +++ b/src/joystick/hidapi/SDL_hidapi_steam.c @@ -356,30 +356,41 @@ static int GetFeatureReport( SDL_hid_device *dev, unsigned char uBuffer[65] ) if ( bBle ) { int nRetries = 0; - uint8_t uSegmentBuffer[ MAX_REPORT_SEGMENT_SIZE ]; + uint8_t uSegmentBuffer[ MAX_REPORT_SEGMENT_SIZE + 1 ]; + uint8_t ucBytesToRead = MAX_REPORT_SEGMENT_SIZE; + uint8_t ucDataStartOffset = 0; SteamControllerPacketAssembler assembler; InitializeSteamControllerPacketAssembler( &assembler ); + // On Windows and macOS, BLE devices get 2 copies of the feature report ID, one that is removed by ReadFeatureReport, + // and one that's included in the buffer we receive. We pad the bytes to read and skip over the report ID + // if necessary. +#if defined(__WIN32__) || defined(__MACOSX__) + ++ucBytesToRead; + ++ucDataStartOffset; +#endif + while( nRetries < BLE_MAX_READ_RETRIES ) { SDL_memset( uSegmentBuffer, 0, sizeof( uSegmentBuffer ) ); uSegmentBuffer[ 0 ] = BLE_REPORT_NUMBER; - nRet = SDL_hid_get_feature_report( dev, uSegmentBuffer, sizeof( uSegmentBuffer ) ); + nRet = SDL_hid_get_feature_report( dev, uSegmentBuffer, ucBytesToRead ); + DPRINTF( "GetFeatureReport ble ret=%d\n", nRet ); HEXDUMP( uSegmentBuffer, nRet ); // Zero retry counter if we got data - if ( nRet > 2 && ( uSegmentBuffer[ 1 ] & REPORT_SEGMENT_DATA_FLAG ) ) + if ( nRet > 2 && ( uSegmentBuffer[ ucDataStartOffset + 1 ] & REPORT_SEGMENT_DATA_FLAG ) ) nRetries = 0; else nRetries++; - + if ( nRet > 0 ) { int nPacketLength = WriteSegmentToSteamControllerPacketAssembler( &assembler, - uSegmentBuffer, - nRet ); + uSegmentBuffer + ucDataStartOffset, + nRet - ucDataStartOffset ); if ( nPacketLength > 0 && nPacketLength < 65 ) { @@ -424,7 +435,8 @@ static bool ResetSteamController( SDL_hid_device *dev, bool bSuppressErrorSpew, { // Firmware quirk: Set Feature and Get Feature requests always require a 65-byte buffer. unsigned char buf[65]; - int res = -1, i; + unsigned int i; + int res = -1; int nSettings = 0; int nAttributesLength; FeatureReportMsg *msg; @@ -803,8 +815,8 @@ static void FormatStatePacketUntilGyro( SteamControllerStateInternal_t *pState, pState->sRightPadX = clamp(nRightPadX + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); pState->sRightPadY = clamp(nRightPadY + nPadOffset, SDL_MIN_SINT16, SDL_MAX_SINT16); - pState->sTriggerL = (unsigned short)RemapValClamped( (pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft, 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); - pState->sTriggerR = (unsigned short)RemapValClamped( (pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight, 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); + pState->sTriggerL = (unsigned short)RemapValClamped( (float)((pStatePacket->ButtonTriggerData.Triggers.nLeft << 7) | pStatePacket->ButtonTriggerData.Triggers.nLeft), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); + pState->sTriggerR = (unsigned short)RemapValClamped( (float)((pStatePacket->ButtonTriggerData.Triggers.nRight << 7) | pStatePacket->ButtonTriggerData.Triggers.nRight), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); } @@ -827,8 +839,8 @@ static bool UpdateBLESteamControllerState( const uint8_t *pData, int nDataSize, if ( ucOptionDataMask & k_EBLEButtonChunk2 ) { // The middle 2 bytes of the button bits over the wire are triggers when over the wire and non-SC buttons in the internal controller state packet - pState->sTriggerL = (unsigned short)RemapValClamped( ( pData[ 0 ] << 7 ) | pData[ 0 ], 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); - pState->sTriggerR = (unsigned short)RemapValClamped( ( pData[ 1 ] << 7 ) | pData[ 1 ], 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); + pState->sTriggerL = (unsigned short)RemapValClamped( (float)(( pData[ 0 ] << 7 ) | pData[ 0 ]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); + pState->sTriggerR = (unsigned short)RemapValClamped( (float)(( pData[ 1 ] << 7 ) | pData[ 1 ]), 0, STEAMCONTROLLER_TRIGGER_MAX_ANALOG, 0, SDL_MAX_SINT16 ); pData += 2; } if ( ucOptionDataMask & k_EBLEButtonChunk3 ) @@ -1035,6 +1047,14 @@ HIDAPI_DriverSteam_InitDevice(SDL_HIDAPI_Device *device) } device->context = ctx; +#if defined(__WIN32__) + if (device->serial) { + /* We get a garbage serial number on Windows */ + SDL_free(device->serial); + device->serial = NULL; + } +#endif /* __WIN32__ */ + HIDAPI_SetDeviceName(device, "Steam Controller"); return HIDAPI_JoystickConnected(device, NULL); @@ -1240,9 +1260,9 @@ HIDAPI_DriverSteam_UpdateDevice(SDL_HIDAPI_Device *device) ctx->timestamp_us += ctx->update_rate_in_us; - values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * (M_PI / 180.0f)); - values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * (M_PI / 180.0f)); - values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * (M_PI / 180.0f)); + values[0] = (ctx->m_state.sGyroX / 32768.0f) * (2000.0f * ((float)M_PI / 180.0f)); + values[1] = (ctx->m_state.sGyroZ / 32768.0f) * (2000.0f * ((float)M_PI / 180.0f)); + values[2] = (ctx->m_state.sGyroY / 32768.0f) * (2000.0f * ((float)M_PI / 180.0f)); SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, ctx->timestamp_us, values, 3); values[0] = (ctx->m_state.sAccelX / 32768.0f) * 2.0f * SDL_STANDARD_GRAVITY; diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index a03221c1e..4ebadede9 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -38,17 +38,13 @@ #define SDL_JOYSTICK_HIDAPI_PS4 #define SDL_JOYSTICK_HIDAPI_PS5 #define SDL_JOYSTICK_HIDAPI_STADIA +#define SDL_JOYSTICK_HIDAPI_STEAM /* Simple support for BLE Steam Controller, hint is disabled by default */ #define SDL_JOYSTICK_HIDAPI_SWITCH #define SDL_JOYSTICK_HIDAPI_WII #define SDL_JOYSTICK_HIDAPI_XBOX360 #define SDL_JOYSTICK_HIDAPI_XBOXONE #define SDL_JOYSTICK_HIDAPI_SHIELD -#if defined(__IPHONEOS__) || defined(__TVOS__) || defined(__ANDROID__) -/* Very basic Steam Controller support on mobile devices */ -#define SDL_JOYSTICK_HIDAPI_STEAM -#endif - /* Whether HIDAPI is enabled by default */ #define SDL_HIDAPI_DEFAULT SDL_TRUE diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c index 5d87b7a2c..053bea023 100644 --- a/test/testgamecontroller.c +++ b/test/testgamecontroller.c @@ -793,6 +793,7 @@ main(int argc, char *argv[]) SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_LINUX_JOYSTICK_DEADZONES, "1");