Bladeren bron

Added Steam Input API support for game controllers

Added support for getting the real controller info, as well as the function SDL_GetGamepadSteamHandle() to get the Steam Input API handle, from the virtual gamepads provided by Steam.

Also added an event SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED which is triggered when a controller's API handle changes, e.g. the controllers were reassigned slots in the Steam UI.
Sam Lantinga 1 jaar geleden
bovenliggende
commit
c981a597dc
42 gewijzigde bestanden met toevoegingen van 779 en 40 verwijderingen
  1. 2 0
      VisualC-GDK/SDL/SDL.vcxproj
  2. 6 0
      VisualC-GDK/SDL/SDL.vcxproj.filters
  3. 2 0
      VisualC-WinRT/SDL-UWP.vcxproj
  4. 6 0
      VisualC-WinRT/SDL-UWP.vcxproj.filters
  5. 2 0
      VisualC/SDL/SDL.vcxproj
  6. 6 0
      VisualC/SDL/SDL.vcxproj.filters
  7. 18 2
      Xcode/SDL/SDL.xcodeproj/project.pbxproj
  8. 2 1
      include/SDL3/SDL_events.h
  9. 13 0
      include/SDL3/SDL_gamepad.h
  10. 1 0
      src/dynapi/SDL_dynapi.sym
  11. 1 0
      src/dynapi/SDL_dynapi_overrides.h
  12. 1 0
      src/dynapi/SDL_dynapi_procs.h
  13. 3 0
      src/events/SDL_events.c
  14. 41 4
      src/joystick/SDL_gamepad.c
  15. 129 11
      src/joystick/SDL_joystick.c
  16. 4 0
      src/joystick/SDL_joystick_c.h
  17. 248 0
      src/joystick/SDL_steam_virtual_gamepad.c
  18. 36 0
      src/joystick/SDL_steam_virtual_gamepad.h
  19. 4 0
      src/joystick/SDL_sysjoystick.h
  20. 6 0
      src/joystick/android/SDL_sysjoystick.c
  21. 11 0
      src/joystick/apple/SDL_mfijoystick.m
  22. 6 0
      src/joystick/bsd/SDL_bsdjoystick.c
  23. 21 0
      src/joystick/darwin/SDL_iokitjoystick.c
  24. 1 0
      src/joystick/darwin/SDL_iokitjoystick_c.h
  25. 6 0
      src/joystick/dummy/SDL_sysjoystick.c
  26. 6 0
      src/joystick/emscripten/SDL_sysjoystick.c
  27. 6 0
      src/joystick/haiku/SDL_haikujoystick.cc
  28. 6 0
      src/joystick/hidapi/SDL_hidapijoystick.c
  29. 23 15
      src/joystick/linux/SDL_sysjoystick.c
  30. 6 0
      src/joystick/n3ds/SDL_sysjoystick.c
  31. 7 0
      src/joystick/ps2/SDL_sysjoystick.c
  32. 6 0
      src/joystick/psp/SDL_sysjoystick.c
  33. 6 0
      src/joystick/virtual/SDL_virtualjoystick.c
  34. 6 0
      src/joystick/vita/SDL_sysjoystick.c
  35. 13 2
      src/joystick/windows/SDL_dinputjoystick.c
  36. 21 0
      src/joystick/windows/SDL_rawinputjoystick.c
  37. 36 0
      src/joystick/windows/SDL_windows_gaming_input.c
  38. 18 1
      src/joystick/windows/SDL_windowsjoystick.c
  39. 1 0
      src/joystick/windows/SDL_windowsjoystick_c.h
  40. 22 4
      src/joystick/windows/SDL_xinputjoystick.c
  41. 1 0
      src/joystick/windows/SDL_xinputjoystick_c.h
  42. 19 0
      test/testcontroller.c

+ 2 - 0
VisualC-GDK/SDL/SDL.vcxproj

@@ -407,6 +407,7 @@
     <ClInclude Include="..\..\src\joystick\SDL_gamepad_c.h" />
     <ClInclude Include="..\..\src\joystick\SDL_gamepad_db.h" />
     <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
+    <ClInclude Include="..\..\src\joystick\SDL_steam_virtual_gamepad.h" />
     <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h" />
     <ClInclude Include="..\..\src\joystick\usb_ids.h" />
     <ClInclude Include="..\..\src\joystick\virtual\SDL_virtualjoystick_c.h" />
@@ -634,6 +635,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
+    <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\virtual\SDL_virtualjoystick.c" />
     <ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c" />
     <ClCompile Include="..\..\src\joystick\windows\SDL_rawinputjoystick.c" />

+ 6 - 0
VisualC-GDK/SDL/SDL.vcxproj.filters

@@ -507,6 +507,9 @@
     <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h">
       <Filter>joystick</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\joystick\SDL_steam_virtual_gamepad.h">
+      <Filter>joystick</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h">
       <Filter>joystick</Filter>
     </ClInclude>
@@ -964,6 +967,9 @@
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c">
       <Filter>joystick</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c">
+      <Filter>joystick</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\libm\e_atan2.c">
       <Filter>libm</Filter>
     </ClCompile>

+ 2 - 0
VisualC-WinRT/SDL-UWP.vcxproj

@@ -127,6 +127,7 @@
     <ClInclude Include="..\src\joystick\SDL_gamepad_c.h" />
     <ClInclude Include="..\src\joystick\SDL_gamepad_db.h" />
     <ClInclude Include="..\src\joystick\SDL_joystick_c.h" />
+    <ClInclude Include="..\src\joystick\SDL_steam_virtual_gamepad.h" />
     <ClInclude Include="..\src\joystick\SDL_sysjoystick.h" />
     <ClInclude Include="..\src\joystick\virtual\SDL_virtualjoystick_c.h" />
     <ClInclude Include="..\src\joystick\windows\SDL_dinputjoystick_c.h" />
@@ -331,6 +332,7 @@
     <ClCompile Include="..\src\joystick\controller_type.c" />
     <ClCompile Include="..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\src\joystick\SDL_joystick.c" />
+    <ClCompile Include="..\src\joystick\SDL_steam_virtual_gamepad.c" />
     <ClCompile Include="..\src\joystick\virtual\SDL_virtualjoystick.c" />
     <ClCompile Include="..\src\joystick\windows\SDL_dinputjoystick.c" />
     <ClCompile Include="..\src\joystick\windows\SDL_windowsjoystick.c" />

+ 6 - 0
VisualC-WinRT/SDL-UWP.vcxproj.filters

@@ -261,6 +261,9 @@
     <ClInclude Include="..\src\joystick\SDL_joystick_c.h">
       <Filter>Source Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\joystick\SDL_steam_virtual_gamepad.h">
+      <Filter>Source Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\joystick\SDL_sysjoystick.h">
       <Filter>Source Files</Filter>
     </ClInclude>
@@ -585,6 +588,9 @@
     <ClCompile Include="..\src\joystick\SDL_joystick.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\joystick\SDL_steam_virtual_gamepad.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\joystick\virtual\SDL_virtualjoystick.c">
       <Filter>Source Files</Filter>
     </ClCompile>

+ 2 - 0
VisualC/SDL/SDL.vcxproj

@@ -357,6 +357,7 @@
     <ClInclude Include="..\..\src\joystick\SDL_gamepad_c.h" />
     <ClInclude Include="..\..\src\joystick\SDL_gamepad_db.h" />
     <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
+    <ClInclude Include="..\..\src\joystick\SDL_steam_virtual_gamepad.h" />
     <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h" />
     <ClInclude Include="..\..\src\joystick\usb_ids.h" />
     <ClInclude Include="..\..\src\joystick\virtual\SDL_virtualjoystick_c.h" />
@@ -537,6 +538,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
+    <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
     <ClCompile Include="..\..\src\joystick\virtual\SDL_virtualjoystick.c" />
     <ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c" />
     <ClCompile Include="..\..\src\joystick\windows\SDL_rawinputjoystick.c" />

+ 6 - 0
VisualC/SDL/SDL.vcxproj.filters

@@ -501,6 +501,9 @@
     <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h">
       <Filter>joystick</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\joystick\SDL_steam_virtual_gamepad.h">
+      <Filter>joystick</Filter>
+    </ClInclude>
     <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h">
       <Filter>joystick</Filter>
     </ClInclude>
@@ -945,6 +948,9 @@
     <ClCompile Include="..\..\src\joystick\SDL_joystick.c">
       <Filter>joystick</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c">
+      <Filter>joystick</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\libm\e_atan2.c">
       <Filter>libm</Filter>
     </ClCompile>

+ 18 - 2
Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -384,6 +384,10 @@
 		F32DDAD32AB795A30041EAA5 /* SDL_audioqueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F32DDACD2AB795A30041EAA5 /* SDL_audioqueue.h */; };
 		F32DDAD42AB795A30041EAA5 /* SDL_audioresample.c in Sources */ = {isa = PBXBuildFile; fileRef = F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */; };
 		F34B9895291DEFF500AAC96E /* SDL_hidapi_steam.c in Sources */ = {isa = PBXBuildFile; fileRef = A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */; };
+		F362B9192B3349E200D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9152B3349E200D30B94 /* controller_list.h */; };
+		F362B91A2B3349E200D30B94 /* SDL_gamepad_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */; };
+		F362B91B2B3349E200D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B91C2B3349E200D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */; };
 		F36C7AD1294BA009004D61C3 /* SDL_runapp.c in Sources */ = {isa = PBXBuildFile; fileRef = F36C7AD0294BA009004D61C3 /* SDL_runapp.c */; };
 		F376F6552559B4E300CFC0BC /* SDL_hidapi.c in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A81423E2513F00DCD162 /* SDL_hidapi.c */; };
 		F37A8E1A28405AA100C38E95 /* CMake in Resources */ = {isa = PBXBuildFile; fileRef = F37A8E1928405AA100C38E95 /* CMake */; };
@@ -879,6 +883,10 @@
 		F32DDACC2AB795A30041EAA5 /* SDL_audio_resampler_filter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_audio_resampler_filter.h; sourceTree = "<group>"; };
 		F32DDACD2AB795A30041EAA5 /* SDL_audioqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_audioqueue.h; sourceTree = "<group>"; };
 		F32DDACE2AB795A30041EAA5 /* SDL_audioresample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_audioresample.c; sourceTree = "<group>"; };
+		F362B9152B3349E200D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; sourceTree = "<group>"; };
+		F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamepad_c.h; sourceTree = "<group>"; };
+		F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_steam_virtual_gamepad.h; sourceTree = "<group>"; };
+		F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_steam_virtual_gamepad.c; sourceTree = "<group>"; };
 		F36C7AD0294BA009004D61C3 /* SDL_runapp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_runapp.c; sourceTree = "<group>"; };
 		F376F6182559B29300CFC0BC /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.1.sdk/System/Library/Frameworks/OpenGLES.framework; sourceTree = DEVELOPER_DIR; };
 		F376F61A2559B2AF00CFC0BC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
@@ -1670,12 +1678,16 @@
 				A7D8A7BE23E2513E00DCD162 /* hidapi */,
 				A7D8A7A123E2513E00DCD162 /* steam */,
 				75E09157241EA924004729E1 /* virtual */,
-				A7D8A7AD23E2513E00DCD162 /* SDL_gamepad.c */,
-				A7D8A7A923E2513E00DCD162 /* SDL_joystick.c */,
+				F362B9152B3349E200D30B94 /* controller_list.h */,
 				F3820712284F3609004DD584 /* controller_type.c */,
 				A7D8A7D923E2513E00DCD162 /* controller_type.h */,
+				F362B9162B3349E200D30B94 /* SDL_gamepad_c.h */,
 				A7D8A79E23E2513E00DCD162 /* SDL_gamepad_db.h */,
+				A7D8A7AD23E2513E00DCD162 /* SDL_gamepad.c */,
 				A7D8A7D023E2513E00DCD162 /* SDL_joystick_c.h */,
+				A7D8A7A923E2513E00DCD162 /* SDL_joystick.c */,
+				F362B9182B3349E200D30B94 /* SDL_steam_virtual_gamepad.c */,
+				F362B9172B3349E200D30B94 /* SDL_steam_virtual_gamepad.h */,
 				A7D8A7CF23E2513E00DCD162 /* SDL_sysjoystick.h */,
 				A7D8A7CB23E2513E00DCD162 /* usb_ids.h */,
 			);
@@ -2167,6 +2179,7 @@
 				F3F7D9B92933074E00816151 /* SDL_cpuinfo.h in Headers */,
 				F3990E062A788303000D8759 /* SDL_hidapi_ios.h in Headers */,
 				A7D8B98023E2514400DCD162 /* SDL_d3dmath.h in Headers */,
+				F362B91A2B3349E200D30B94 /* SDL_gamepad_c.h in Headers */,
 				A7D8B8A223E2514400DCD162 /* SDL_diskaudio.h in Headers */,
 				A7D8BB3F23E2514500DCD162 /* SDL_displayevents_c.h in Headers */,
 				A7D8BA1923E2514400DCD162 /* SDL_draw.h in Headers */,
@@ -2180,6 +2193,7 @@
 				A7D8AB1023E2514100DCD162 /* SDL_dynapi_overrides.h in Headers */,
 				A7D8AB1C23E2514100DCD162 /* SDL_dynapi_procs.h in Headers */,
 				F3F7D9252933074E00816151 /* SDL_egl.h in Headers */,
+				F362B9192B3349E200D30B94 /* controller_list.h in Headers */,
 				A7D8ABD923E2514100DCD162 /* SDL_egl_c.h in Headers */,
 				F3F7D93D2933074E00816151 /* SDL_endian.h in Headers */,
 				F3F7D9352933074E00816151 /* SDL_error.h in Headers */,
@@ -2219,6 +2233,7 @@
 				F3F7D91D2933074E00816151 /* SDL_messagebox.h in Headers */,
 				F3F7D98D2933074E00816151 /* SDL_metal.h in Headers */,
 				F395C1BA2569C6A000942BFF /* SDL_mfijoystick_c.h in Headers */,
+				F362B91B2B3349E200D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				F3F7D9992933074E00816151 /* SDL_misc.h in Headers */,
 				F3F7D9AD2933074E00816151 /* SDL_mouse.h in Headers */,
 				A7D8BB1B23E2514500DCD162 /* SDL_mouse_c.h in Headers */,
@@ -2583,6 +2598,7 @@
 				A7D8BAD323E2514500DCD162 /* s_tan.c in Sources */,
 				A7D8AA6523E2514000DCD162 /* SDL_hints.c in Sources */,
 				A7D8B53F23E2514300DCD162 /* SDL_hidapi_ps4.c in Sources */,
+				F362B91C2B3349E200D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8AD6E23E2514100DCD162 /* SDL_pixels.c in Sources */,
 				A7D8B75E23E2514300DCD162 /* SDL_sysloadso.c in Sources */,
 				A7D8BBD723E2574800DCD162 /* SDL_uikitevents.m in Sources */,

+ 2 - 1
include/SDL3/SDL_events.h

@@ -170,6 +170,7 @@ typedef enum
     SDL_EVENT_GAMEPAD_TOUCHPAD_UP,          /**< Gamepad touchpad finger was lifted */
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,        /**< Gamepad sensor was updated */
     SDL_EVENT_GAMEPAD_UPDATE_COMPLETE,      /**< Gamepad update is complete */
+    SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED,  /**< Gamepad Steam handle has changed */
 
     /* Touch events */
     SDL_EVENT_FINGER_DOWN      = 0x700,
@@ -457,7 +458,7 @@ typedef struct SDL_GamepadButtonEvent
  */
 typedef struct SDL_GamepadDeviceEvent
 {
-    Uint32 type;        /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED or ::SDL_EVENT_GAMEPAD_UPDATE_COMPLETE */
+    Uint32 type;        /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED, ::SDL_EVENT_GAMEPAD_UPDATE_COMPLETE or ::SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED */
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
     SDL_JoystickID which;       /**< The joystick instance id */
 } SDL_GamepadDeviceEvent;

+ 13 - 0
include/SDL3/SDL_gamepad.h

@@ -758,6 +758,19 @@ extern DECLSPEC Uint16 SDLCALL SDL_GetGamepadFirmwareVersion(SDL_Gamepad *gamepa
  */
 extern DECLSPEC const char * SDLCALL SDL_GetGamepadSerial(SDL_Gamepad *gamepad);
 
+/**
+ * Get the Steam Input handle of an opened gamepad, if available.
+ *
+ * Returns an InputHandle_t for the gamepad that can be used with Steam Input API:
+ * https://partner.steamgames.com/doc/api/ISteamInput
+ *
+ * \param gamepad the gamepad object to query.
+ * \returns the gamepad handle, or 0 if unavailable.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC Uint64 SDLCALL SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad);
+
 /**
  * Get the battery level of a gamepad, if available.
  *

+ 1 - 0
src/dynapi/SDL_dynapi.sym

@@ -965,6 +965,7 @@ SDL3_0.0.0 {
     SDL_SyncWindow;
     SDL_SetSurfaceScaleMode;
     SDL_GetSurfaceScaleMode;
+    SDL_GetGamepadSteamHandle;
     # extra symbols go here (don't modify this line)
   local: *;
 };

+ 1 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -990,3 +990,4 @@
 #define SDL_SyncWindow SDL_SyncWindow_REAL
 #define SDL_SetSurfaceScaleMode SDL_SetSurfaceScaleMode_REAL
 #define SDL_GetSurfaceScaleMode SDL_GetSurfaceScaleMode_REAL
+#define SDL_GetGamepadSteamHandle SDL_GetGamepadSteamHandle_REAL

+ 1 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -1015,3 +1015,4 @@ SDL_DYNAPI_PROC(wchar_t*,SDL_wcsnstr,(const wchar_t *a, const wchar_t *b, size_t
 SDL_DYNAPI_PROC(int,SDL_SyncWindow,(SDL_Window *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_SetSurfaceScaleMode,(SDL_Surface *a, SDL_ScaleMode b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GetSurfaceScaleMode,(SDL_Surface *a, SDL_ScaleMode *b),(a,b),return)
+SDL_DYNAPI_PROC(Uint64,SDL_GetGamepadSteamHandle,(SDL_Gamepad *a),(a),return)

+ 3 - 0
src/events/SDL_events.c

@@ -433,6 +433,9 @@ static void SDL_LogEvent(const SDL_Event *event)
         SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_REMAPPED)
         PRINT_GAMEPADDEV_EVENT(event);
         break;
+        SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED)
+        PRINT_GAMEPADDEV_EVENT(event);
+        break;
 #undef PRINT_GAMEPADDEV_EVENT
 
 #define PRINT_CTOUCHPAD_EVENT(event)                                                                                     \

+ 41 - 4
src/joystick/SDL_gamepad.c

@@ -25,6 +25,7 @@
 #include "../SDL_utils_c.h"
 #include "SDL_sysjoystick.h"
 #include "SDL_joystick_c.h"
+#include "SDL_steam_virtual_gamepad.h"
 #include "SDL_gamepad_c.h"
 #include "SDL_gamepad_db.h"
 #include "controller_type.h"
@@ -2411,7 +2412,21 @@ SDL_GamepadType SDL_GetGamepadInstanceType(SDL_JoystickID instance_id)
 
 SDL_GamepadType SDL_GetRealGamepadInstanceType(SDL_JoystickID instance_id)
 {
-    return SDL_GetGamepadTypeFromGUID(SDL_GetJoystickInstanceGUID(instance_id), SDL_GetJoystickInstanceName(instance_id));
+    SDL_GamepadType type = SDL_GAMEPAD_TYPE_UNKNOWN;
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    {
+        info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+        if (info) {
+            type = info->type;
+        } else {
+            type = SDL_GetGamepadTypeFromGUID(SDL_GetJoystickInstanceGUID(instance_id), SDL_GetJoystickInstanceName(instance_id));
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return type;
 }
 
 char *SDL_GetGamepadInstanceMapping(SDL_JoystickID instance_id)
@@ -2518,7 +2533,7 @@ SDL_bool SDL_ShouldIgnoreGamepad(const char *name, SDL_JoystickGUID guid)
 #ifdef __LINUX__
         bSteamVirtualGamepad = (vendor == USB_VENDOR_VALVE && product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD);
 #elif defined(__MACOS__)
-        bSteamVirtualGamepad = (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 1);
+        bSteamVirtualGamepad = (vendor == USB_VENDOR_MICROSOFT && product == USB_PRODUCT_XBOX360_WIRED_CONTROLLER && version == 0);
 #elif defined(__WIN32__)
         /* We can't tell on Windows, but Steam will block others in input hooks */
         bSteamVirtualGamepad = SDL_TRUE;
@@ -3190,7 +3205,8 @@ const char *SDL_GetGamepadName(SDL_Gamepad *gamepad)
     {
         CHECK_GAMEPAD_MAGIC(gamepad, NULL);
 
-        if (SDL_strcmp(gamepad->name, "*") == 0) {
+        if (SDL_strcmp(gamepad->name, "*") == 0 ||
+            gamepad->joystick->steam_handle != 0) {
             retval = SDL_GetJoystickName(gamepad->joystick);
         } else {
             retval = gamepad->name;
@@ -3214,12 +3230,18 @@ const char *SDL_GetGamepadPath(SDL_Gamepad *gamepad)
 SDL_GamepadType SDL_GetGamepadType(SDL_Gamepad *gamepad)
 {
     SDL_GamepadType type;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
     {
         CHECK_GAMEPAD_MAGIC(gamepad, SDL_GAMEPAD_TYPE_UNKNOWN);
 
-        type = gamepad->type;
+        info = SDL_GetJoystickInstanceVirtualGamepadInfo(gamepad->joystick->instance_id);
+        if (info) {
+            type = info->type;
+        } else {
+            type = gamepad->type;
+        }
     }
     SDL_UnlockJoysticks();
 
@@ -3310,6 +3332,21 @@ const char * SDL_GetGamepadSerial(SDL_Gamepad *gamepad)
     return SDL_GetJoystickSerial(joystick);
 }
 
+Uint64 SDL_GetGamepadSteamHandle(SDL_Gamepad *gamepad)
+{
+    Uint64 handle = 0;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_GAMEPAD_MAGIC(gamepad, 0);
+
+        handle = gamepad->joystick->steam_handle;
+    }
+    SDL_UnlockJoysticks();
+
+    return handle;
+}
+
 SDL_JoystickPowerLevel SDL_GetGamepadPowerLevel(SDL_Gamepad *gamepad)
 {
     SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);

+ 129 - 11
src/joystick/SDL_joystick.c

@@ -26,6 +26,7 @@
 #include "../SDL_hints_c.h"
 #include "SDL_gamepad_c.h"
 #include "SDL_joystick_c.h"
+#include "SDL_steam_virtual_gamepad.h"
 
 #ifndef SDL_EVENTS_DISABLED
 #include "../events/SDL_events_c.h"
@@ -624,6 +625,8 @@ int SDL_InitJoysticks(void)
     SDL_AddHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
                         SDL_JoystickAllowBackgroundEventsChanged, NULL);
 
+    SDL_InitSteamVirtualGamepadInfo();
+
     status = -1;
     for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
         if (SDL_joystick_drivers[i]->Init() >= 0) {
@@ -696,6 +699,19 @@ SDL_JoystickID *SDL_GetJoysticks(int *count)
     return joysticks;
 }
 
+const SDL_SteamVirtualGamepadInfo *SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickID instance_id)
+{
+    SDL_JoystickDriver *driver;
+    int device_index;
+    const SDL_SteamVirtualGamepadInfo *info = NULL;
+
+    if (SDL_SteamVirtualGamepadEnabled() &&
+        SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
+        info = SDL_GetSteamVirtualGamepadInfo(driver->GetDeviceSteamVirtualGamepadSlot(device_index));
+    }
+    return info;
+}
+
 /*
  * Get the implementation dependent name of a joystick
  */
@@ -704,9 +720,13 @@ const char *SDL_GetJoystickInstanceName(SDL_JoystickID instance_id)
     SDL_JoystickDriver *driver;
     int device_index;
     const char *name = NULL;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
-    if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+    if (info) {
+        name = info->name;
+    } else if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
         name = driver->GetDeviceName(device_index);
     }
     SDL_UnlockJoysticks();
@@ -993,6 +1013,7 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
     const char *joystickpath = NULL;
     SDL_JoystickPowerLevel initial_power_level;
     SDL_bool invert_sensors = SDL_FALSE;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
 
@@ -1076,6 +1097,12 @@ SDL_Joystick *SDL_OpenJoystick(SDL_JoystickID instance_id)
 
     joystick->is_gamepad = SDL_IsGamepad(instance_id);
 
+    /* Get the Steam Input API handle */
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+    if (info) {
+        joystick->steam_handle = info->handle;
+    }
+
     /* Use system gyro and accelerometer if the gamepad doesn't have built-in sensors */
     if (ShouldAttemptSensorFusion(joystick, &invert_sensors)) {
         AttemptSensorFusion(joystick, invert_sensors);
@@ -1491,15 +1518,20 @@ SDL_PropertiesID SDL_GetJoystickProperties(SDL_Joystick *joystick)
 const char *SDL_GetJoystickName(SDL_Joystick *joystick)
 {
     const char *retval;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
-    {
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+    if (info) {
+        retval = info->name;
+    } else {
         CHECK_JOYSTICK_MAGIC(joystick, NULL);
 
         retval = joystick->name;
     }
     SDL_UnlockJoysticks();
 
+    /* FIXME: Really we should reference count this name so it doesn't go away after unlock */
     return retval;
 }
 
@@ -1823,6 +1855,8 @@ void SDL_QuitJoysticks(void)
     SDL_QuitSubSystem(SDL_INIT_EVENTS);
 #endif
 
+    SDL_QuitSteamVirtualGamepadInfo();
+
     SDL_DelHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
                         SDL_JoystickAllowBackgroundEventsChanged, NULL);
 
@@ -1921,7 +1955,10 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id)
     SDL_joystick_being_added = SDL_TRUE;
 
     if (SDL_GetDriverAndJoystickIndex(instance_id, &driver, &device_index)) {
-        player_index = driver->GetDevicePlayerIndex(device_index);
+        player_index = driver->GetDeviceSteamVirtualGamepadSlot(device_index);
+        if (player_index < 0) {
+            player_index = driver->GetDevicePlayerIndex(device_index);
+        }
     }
     if (player_index < 0 && SDL_IsGamepad(instance_id)) {
         player_index = SDL_FindFreePlayerIndex();
@@ -2200,6 +2237,43 @@ int SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 butto
     return posted;
 }
 
+static void SendSteamHandleUpdateEvents(void)
+{
+    SDL_Joystick *joystick;
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    /* Check to see if any Steam handles changed */
+    for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
+        SDL_bool changed = SDL_FALSE;
+
+        if (!joystick->is_gamepad) {
+            continue;
+        }
+
+        info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+        if (info) {
+            if (joystick->steam_handle != info->handle) {
+                joystick->steam_handle = info->handle;
+                changed = SDL_TRUE;
+            }
+        } else {
+            if (joystick->steam_handle != 0) {
+                joystick->steam_handle = 0;
+                changed = SDL_TRUE;
+            }
+        }
+        if (changed) {
+            SDL_Event event;
+
+            SDL_zero(event);
+            event.type = SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED;
+            event.common.timestamp = 0;
+            event.gdevice.which = joystick->instance_id;
+            SDL_PushEvent(&event);
+        }
+    }
+}
+
 void SDL_UpdateJoysticks(void)
 {
     int i;
@@ -2212,6 +2286,10 @@ void SDL_UpdateJoysticks(void)
 
     SDL_LockJoysticks();
 
+    if (SDL_UpdateSteamVirtualGamepadInfo()) {
+        SendSteamHandleUpdateEvents();
+    }
+
 #ifdef SDL_JOYSTICK_HIDAPI
     /* Special function for HIDAPI devices, as a single device can provide multiple SDL_Joysticks */
     HIDAPI_UpdateDevices();
@@ -3083,18 +3161,38 @@ SDL_JoystickGUID SDL_GetJoystickInstanceGUID(SDL_JoystickID instance_id)
 Uint16 SDL_GetJoystickInstanceVendor(SDL_JoystickID instance_id)
 {
     Uint16 vendor;
-    SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+    if (info) {
+        vendor = info->vendor_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id);
+
+        SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
     return vendor;
 }
 
 Uint16 SDL_GetJoystickInstanceProduct(SDL_JoystickID instance_id)
 {
     Uint16 product;
-    SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+    if (info) {
+        product = info->product_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID(instance_id);
+
+        SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
     return product;
 }
 
@@ -3141,18 +3239,38 @@ SDL_JoystickGUID SDL_GetJoystickGUID(SDL_Joystick *joystick)
 Uint16 SDL_GetJoystickVendor(SDL_Joystick *joystick)
 {
     Uint16 vendor;
-    SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+    if (info) {
+        vendor = info->vendor_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick);
+
+        SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
     return vendor;
 }
 
 Uint16 SDL_GetJoystickProduct(SDL_Joystick *joystick)
 {
     Uint16 product;
-    SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+    if (info) {
+        product = info->product_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_GetJoystickGUID(joystick);
+
+        SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
     return product;
 }
 

+ 4 - 0
src/joystick/SDL_joystick_c.h

@@ -32,6 +32,7 @@ extern "C" {
 #endif
 
 struct SDL_JoystickDriver;
+struct SDL_SteamVirtualGamepadInfo;
 extern char SDL_joystick_magic;
 
 /* Initialization and shutdown functions */
@@ -170,6 +171,9 @@ extern int SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick,
 extern void SDL_SendJoystickBatteryLevel(SDL_Joystick *joystick,
                                             SDL_JoystickPowerLevel ePowerLevel);
 
+/* Function to get the Steam virtual gamepad info for a joystick */
+extern const struct SDL_SteamVirtualGamepadInfo *SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickID instance_id);
+
 /* Internal sanity checking functions */
 extern SDL_bool SDL_IsJoystickValid(SDL_Joystick *joystick);
 

+ 248 - 0
src/joystick/SDL_steam_virtual_gamepad.c

@@ -0,0 +1,248 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  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.
+*/
+#include "SDL_internal.h"
+
+#include "SDL_joystick_c.h"
+#include "SDL_steam_virtual_gamepad.h"
+
+#ifdef __WIN32__
+#include "../core/windows/SDL_windows.h"
+#else
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE    "SteamVirtualGamepadInfo"
+
+static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
+static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0;
+static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0;
+static SDL_SteamVirtualGamepadInfo **SDL_steam_virtual_gamepad_info SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
+static int SDL_steam_virtual_gamepad_info_count SDL_GUARDED_BY(SDL_joystick_lock) = 0;
+
+
+static Uint64 GetFileModificationTime(const char *file)
+{
+    Uint64 modification_time = 0;
+
+#ifdef __WIN32__
+    WCHAR *wFile = WIN_UTF8ToStringW(file);
+    if (wFile) {
+        HANDLE hFile = CreateFileW(wFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+        if (hFile != INVALID_HANDLE_VALUE) {
+            FILETIME last_write_time;
+            if (GetFileTime(hFile, NULL, NULL, &last_write_time)) {
+                modification_time = last_write_time.dwHighDateTime;
+                modification_time <<= 32;
+                modification_time |= last_write_time.dwLowDateTime;
+            }
+            CloseHandle(hFile);
+        }
+        SDL_free(wFile);
+    }
+#else
+    struct stat sb;
+
+    if (stat(file, &sb) == 0) {
+        modification_time = (Uint64)sb.st_mtime;
+    }
+#endif
+    return modification_time;
+}
+
+static void SDL_FreeSteamVirtualGamepadInfo(void)
+{
+    int i;
+
+    SDL_AssertJoysticksLocked();
+
+    for (i = 0; i < SDL_steam_virtual_gamepad_info_count; ++i) {
+        SDL_SteamVirtualGamepadInfo *entry = SDL_steam_virtual_gamepad_info[i];
+        if (entry) {
+            SDL_free(entry->name);
+            SDL_free(entry);
+        }
+    }
+    SDL_free(SDL_steam_virtual_gamepad_info);
+    SDL_steam_virtual_gamepad_info = NULL;
+    SDL_steam_virtual_gamepad_info_count = 0;
+}
+
+static void AddVirtualGamepadInfo(int slot, SDL_SteamVirtualGamepadInfo *info)
+{
+    SDL_SteamVirtualGamepadInfo *new_info;
+
+    SDL_AssertJoysticksLocked();
+
+    if (slot < 0) {
+        return;
+    }
+
+    if (slot >= SDL_steam_virtual_gamepad_info_count) {
+        SDL_SteamVirtualGamepadInfo **slots = (SDL_SteamVirtualGamepadInfo **)SDL_realloc(SDL_steam_virtual_gamepad_info, (slot + 1)*sizeof(*SDL_steam_virtual_gamepad_info));
+        if (!slots) {
+            return;
+        }
+        while (SDL_steam_virtual_gamepad_info_count <= slot) {
+            slots[SDL_steam_virtual_gamepad_info_count++] = NULL;
+        }
+        SDL_steam_virtual_gamepad_info = slots;
+    }
+
+    if (SDL_steam_virtual_gamepad_info[slot]) {
+        /* We already have this slot info */
+        return;
+    }
+
+    new_info = (SDL_SteamVirtualGamepadInfo *)SDL_malloc(sizeof(*new_info));
+    if (!new_info) {
+        return;
+    }
+    SDL_copyp(new_info, info);
+    SDL_steam_virtual_gamepad_info[slot] = new_info;
+    SDL_zerop(info);
+}
+
+void SDL_InitSteamVirtualGamepadInfo(void)
+{
+    const char *file;
+
+    SDL_AssertJoysticksLocked();
+
+    file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE);
+    if (file && *file) {
+        SDL_steam_virtual_gamepad_info_file = SDL_strdup(file);
+    }
+    SDL_UpdateSteamVirtualGamepadInfo();
+}
+
+SDL_bool SDL_SteamVirtualGamepadEnabled(void)
+{
+    SDL_AssertJoysticksLocked();
+
+    return (SDL_steam_virtual_gamepad_info != NULL);
+}
+
+SDL_bool SDL_UpdateSteamVirtualGamepadInfo(void)
+{
+    const int UPDATE_CHECK_INTERVAL_MS = 3000;
+    Uint64 now;
+    Uint64 mtime;
+    char *data, *end, *next, *line, *value;
+    size_t size;
+    int slot, new_slot;
+    SDL_SteamVirtualGamepadInfo info;
+
+    SDL_AssertJoysticksLocked();
+
+    if (!SDL_steam_virtual_gamepad_info_file) {
+        return SDL_FALSE;
+    }
+
+    now = SDL_GetTicks();
+    if (SDL_steam_virtual_gamepad_info_check_time &&
+        now < (SDL_steam_virtual_gamepad_info_check_time + UPDATE_CHECK_INTERVAL_MS)) {
+        return SDL_FALSE;
+    }
+    SDL_steam_virtual_gamepad_info_check_time = now;
+
+    mtime = GetFileModificationTime(SDL_steam_virtual_gamepad_info_file);
+    if (mtime == 0 || mtime == SDL_steam_virtual_gamepad_info_file_mtime) {
+        return SDL_FALSE;
+    }
+
+    data = (char *)SDL_LoadFile(SDL_steam_virtual_gamepad_info_file, &size);
+    if (!data) {
+        return SDL_FALSE;
+    }
+
+    SDL_FreeSteamVirtualGamepadInfo();
+
+    slot = -1;
+    SDL_zero(info);
+
+    for (next = data, end = data + size; next < end; ) {
+        while (next < end && (*next == '\0' || *next == '\r' || *next == '\n')) {
+            ++next;
+        }
+
+        line = next;
+
+        while (next < end && (*next != '\r' && *next != '\n')) {
+            ++next;
+        }
+        *next = '\0';
+
+        if (SDL_sscanf(line, "[slot %d]", &new_slot) == 1) {
+            if (slot >= 0) {
+                AddVirtualGamepadInfo(slot, &info);
+            }
+            slot = new_slot;
+        } else {
+            value = SDL_strchr(line, '=');
+            if (value) {
+                *value++ = '\0';
+
+                if (SDL_strcmp(line, "name") == 0) {
+                    SDL_free(info.name);
+                    info.name = SDL_strdup(value);
+                } else if (SDL_strcmp(line, "VID") == 0) {
+                    info.vendor_id = (Uint16)SDL_strtoul(value, NULL, 0);
+                } else if (SDL_strcmp(line, "PID") == 0) {
+                    info.product_id = (Uint16)SDL_strtoul(value, NULL, 0);
+                } else if (SDL_strcmp(line, "type") == 0) {
+                    info.type = SDL_GetGamepadTypeFromString(value);
+                } else if (SDL_strcmp(line, "handle") == 0) {
+                    info.handle = SDL_strtoull(value, NULL, 0);
+                }
+            }
+        }
+    }
+    if (slot >= 0) {
+        AddVirtualGamepadInfo(slot, &info);
+    }
+    SDL_free(data);
+
+    SDL_steam_virtual_gamepad_info_file_mtime = mtime;
+
+    return SDL_TRUE;
+}
+
+const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot)
+{
+    SDL_AssertJoysticksLocked();
+
+    if (slot < 0 || slot >= SDL_steam_virtual_gamepad_info_count) {
+        return NULL;
+    }
+    return SDL_steam_virtual_gamepad_info[slot];
+}
+
+void SDL_QuitSteamVirtualGamepadInfo(void)
+{
+    SDL_AssertJoysticksLocked();
+
+    if (SDL_steam_virtual_gamepad_info_file) {
+        SDL_FreeSteamVirtualGamepadInfo();
+        SDL_free(SDL_steam_virtual_gamepad_info_file);
+        SDL_steam_virtual_gamepad_info_file = NULL;
+    }
+}

+ 36 - 0
src/joystick/SDL_steam_virtual_gamepad.h

@@ -0,0 +1,36 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
+
+  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.
+*/
+#include "SDL_internal.h"
+
+typedef struct SDL_SteamVirtualGamepadInfo
+{
+    Uint64 handle;
+    char *name;
+    Uint16 vendor_id;
+    Uint16 product_id;
+    SDL_GamepadType type;
+} SDL_SteamVirtualGamepadInfo;
+
+void SDL_InitSteamVirtualGamepadInfo(void);
+SDL_bool SDL_SteamVirtualGamepadEnabled(void);
+SDL_bool SDL_UpdateSteamVirtualGamepadInfo(void);
+const SDL_SteamVirtualGamepadInfo *SDL_GetSteamVirtualGamepadInfo(int slot);
+void SDL_QuitSteamVirtualGamepadInfo(void);

+ 4 - 0
src/joystick/SDL_sysjoystick.h

@@ -77,6 +77,7 @@ struct SDL_Joystick
     char *serial _guarded;               /* Joystick serial */
     SDL_JoystickGUID guid _guarded;      /* Joystick guid */
     Uint16 firmware_version _guarded;    /* Firmware version, if available */
+    Uint64 steam_handle _guarded;        /* Steam controller API handle */
 
     int naxes _guarded; /* Number of axis controls on the joystick */
     SDL_JoystickAxisInfo *axes _guarded;
@@ -168,6 +169,9 @@ typedef struct SDL_JoystickDriver
     /* Function to get the device-dependent path of a joystick */
     const char *(*GetDevicePath)(int device_index);
 
+    /* Function to get the Steam virtual gamepad slot of a joystick */
+    int (*GetDeviceSteamVirtualGamepadSlot)(int device_index);
+
     /* Function to get the player index of a joystick */
     int (*GetDevicePlayerIndex)(int device_index);
 

+ 6 - 0
src/joystick/android/SDL_sysjoystick.c

@@ -541,6 +541,11 @@ static const char *ANDROID_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int ANDROID_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -681,6 +686,7 @@ SDL_JoystickDriver SDL_ANDROID_JoystickDriver = {
     ANDROID_JoystickDetect,
     ANDROID_JoystickGetDeviceName,
     ANDROID_JoystickGetDevicePath,
+    ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot,
     ANDROID_JoystickGetDevicePlayerIndex,
     ANDROID_JoystickSetDevicePlayerIndex,
     ANDROID_JoystickGetDeviceGUID,

+ 11 - 0
src/joystick/apple/SDL_mfijoystick.m

@@ -661,6 +661,10 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
     }
     device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, name, 'm', subtype);
 
+    if (SDL_ShouldIgnoreJoystick(name, device->guid)) {
+        return SDL_FALSE;
+    }
+
     /* This will be set when the first button press of the controller is
      * detected. */
     controller.playerIndex = -1;
@@ -705,6 +709,7 @@ static void IOS_AddJoystickDevice(GCController *controller, SDL_bool acceleromet
     } else if (controller) {
 #ifdef SDL_JOYSTICK_MFI
         if (!IOS_AddMFIJoystickDevice(device, controller)) {
+            SDL_free(device->name);
             SDL_free(device);
             return;
         }
@@ -898,6 +903,11 @@ static const char *IOS_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int IOS_JoystickGetDevicePlayerIndex(int device_index)
 {
 #ifdef SDL_JOYSTICK_MFI
@@ -2165,6 +2175,7 @@ SDL_JoystickDriver SDL_IOS_JoystickDriver = {
     IOS_JoystickDetect,
     IOS_JoystickGetDeviceName,
     IOS_JoystickGetDevicePath,
+    IOS_JoystickGetDeviceSteamVirtualGamepadSlot,
     IOS_JoystickGetDevicePlayerIndex,
     IOS_JoystickSetDevicePlayerIndex,
     IOS_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/bsd/SDL_bsdjoystick.c

@@ -543,6 +543,11 @@ static const char *BSD_JoystickGetDevicePath(int device_index)
     return GetJoystickByDevIndex(device_index)->path;
 }
 
+static int BSD_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int BSD_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -850,6 +855,7 @@ SDL_JoystickDriver SDL_BSD_JoystickDriver = {
     BSD_JoystickDetect,
     BSD_JoystickGetDeviceName,
     BSD_JoystickGetDevicePath,
+    BSD_JoystickGetDeviceSteamVirtualGamepadSlot,
     BSD_JoystickGetDevicePlayerIndex,
     BSD_JoystickSetDevicePlayerIndex,
     BSD_JoystickGetDeviceGUID,

+ 21 - 0
src/joystick/darwin/SDL_iokitjoystick.c

@@ -405,6 +405,19 @@ static void AddHIDElement(const void *value, void *parameter)
     }
 }
 
+static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string)
+{
+    int slot = -1;
+
+    if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {
+        /* Gamepad name is "GamePad-N", where N is slot + 1 */
+        if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) {
+            slot -= 1;
+        }
+    }
+    return slot;
+}
+
 static SDL_bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
 {
     Sint32 vendor = 0;
@@ -485,6 +498,7 @@ static SDL_bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
 #endif
 
     pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, pDevice->product, 0, 0);
+    pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string);
 
     array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
     if (array) {
@@ -711,6 +725,12 @@ const char *DARWIN_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    recDevice *device = GetDeviceForIndex(device_index);
+    return device ? device->steam_virtual_gamepad_slot : -1;
+}
+
 static int DARWIN_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -1056,6 +1076,7 @@ SDL_JoystickDriver SDL_DARWIN_JoystickDriver = {
     DARWIN_JoystickDetect,
     DARWIN_JoystickGetDeviceName,
     DARWIN_JoystickGetDevicePath,
+    DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot,
     DARWIN_JoystickGetDevicePlayerIndex,
     DARWIN_JoystickSetDevicePlayerIndex,
     DARWIN_JoystickGetDeviceGUID,

+ 1 - 0
src/joystick/darwin/SDL_iokitjoystick_c.h

@@ -71,6 +71,7 @@ struct joystick_hwdata
 
     int instance_id;
     SDL_JoystickGUID guid;
+    int steam_virtual_gamepad_slot;
 
     struct joystick_hwdata *pNext; /* next device */
 };

+ 6 - 0
src/joystick/dummy/SDL_sysjoystick.c

@@ -51,6 +51,11 @@ static const char *DUMMY_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int DUMMY_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -130,6 +135,7 @@ SDL_JoystickDriver SDL_DUMMY_JoystickDriver = {
     DUMMY_JoystickDetect,
     DUMMY_JoystickGetDeviceName,
     DUMMY_JoystickGetDevicePath,
+    DUMMY_JoystickGetDeviceSteamVirtualGamepadSlot,
     DUMMY_JoystickGetDevicePlayerIndex,
     DUMMY_JoystickSetDevicePlayerIndex,
     DUMMY_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/emscripten/SDL_sysjoystick.c

@@ -269,6 +269,11 @@ static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -416,6 +421,7 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = {
     EMSCRIPTEN_JoystickDetect,
     EMSCRIPTEN_JoystickGetDeviceName,
     EMSCRIPTEN_JoystickGetDevicePath,
+    EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot,
     EMSCRIPTEN_JoystickGetDevicePlayerIndex,
     EMSCRIPTEN_JoystickSetDevicePlayerIndex,
     EMSCRIPTEN_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/haiku/SDL_haikujoystick.cc

@@ -101,6 +101,11 @@ extern "C"
         return SDL_joyport[device_index];
     }
 
+    static int HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+    {
+        return -1;
+    }
+
     static int HAIKU_JoystickGetDevicePlayerIndex(int device_index)
     {
         return -1;
@@ -295,6 +300,7 @@ extern "C"
         HAIKU_JoystickDetect,
         HAIKU_JoystickGetDeviceName,
         HAIKU_JoystickGetDevicePath,
+        HAIKU_JoystickGetDeviceSteamVirtualGamepadSlot,
         HAIKU_JoystickGetDevicePlayerIndex,
         HAIKU_JoystickSetDevicePlayerIndex,
         HAIKU_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -1376,6 +1376,11 @@ static const char *HIDAPI_JoystickGetDevicePath(int device_index)
     return path;
 }
 
+static int HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int HIDAPI_JoystickGetDevicePlayerIndex(int device_index)
 {
     SDL_HIDAPI_Device *device;
@@ -1654,6 +1659,7 @@ SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = {
     HIDAPI_JoystickDetect,
     HIDAPI_JoystickGetDeviceName,
     HIDAPI_JoystickGetDevicePath,
+    HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot,
     HIDAPI_JoystickGetDevicePlayerIndex,
     HIDAPI_JoystickSetDevicePlayerIndex,
     HIDAPI_JoystickGetDeviceGUID,

+ 23 - 15
src/joystick/linux/SDL_sysjoystick.c

@@ -220,14 +220,18 @@ static SDL_bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version,
 }
 #endif /* SDL_JOYSTICK_HIDAPI */
 
-static SDL_bool GetVirtualGamepadSlot(const char *name, int *slot)
+static SDL_bool GetSteamVirtualGamepadSlot(int fd, int *slot)
 {
-    const char *digits = SDL_strstr(name, "pad ");
-    if (digits) {
-        digits += 4;
-        if (SDL_isdigit(*digits)) {
-            *slot = SDL_atoi(digits);
-            return SDL_TRUE;
+    char name[128];
+
+    if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0) {
+        const char *digits = SDL_strstr(name, "pad ");
+        if (digits) {
+            digits += 4;
+            if (SDL_isdigit(*digits)) {
+                *slot = SDL_atoi(digits);
+                return SDL_TRUE;
+            }
         }
     }
     return SDL_FALSE;
@@ -445,7 +449,6 @@ static void MaybeAddDevice(const char *path)
 #ifdef DEBUG_INPUT_EVENTS
         SDL_Log("found joystick: %s\n", path);
 #endif
-        close(fd);
         item = (SDL_joylist_item *)SDL_calloc(1, sizeof(SDL_joylist_item));
         if (!item) {
             SDL_free(name);
@@ -460,7 +463,7 @@ static void MaybeAddDevice(const char *path)
 
         if (vendor == USB_VENDOR_VALVE &&
             product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
-            GetVirtualGamepadSlot(item->name, &item->steam_virtual_gamepad_slot);
+            GetSteamVirtualGamepadSlot(fd, &item->steam_virtual_gamepad_slot);
         }
 
         if ((!item->path) || (!item->name)) {
@@ -487,7 +490,6 @@ static void MaybeAddDevice(const char *path)
 #ifdef DEBUG_INPUT_EVENTS
         SDL_Log("found sensor: %s\n", path);
 #endif
-        close(fd);
         item_sensor = (SDL_sensorlist_item *)SDL_calloc(1, sizeof(SDL_sensorlist_item));
         if (!item_sensor) {
             goto done;
@@ -505,8 +507,10 @@ static void MaybeAddDevice(const char *path)
         goto done;
     }
 
-    close(fd);
 done:
+    if (fd >= 0) {
+        close(fd);
+    }
     SDL_UnlockJoysticks();
 }
 
@@ -874,7 +878,6 @@ static void LINUX_ScanSteamVirtualGamepads(void)
     int fd;
     struct dirent **entries = NULL;
     char path[PATH_MAX];
-    char name[128];
     struct input_id inpid;
     int num_virtual_gamepads = 0;
     int virtual_gamepad_slot;
@@ -889,8 +892,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
             if (ioctl(fd, EVIOCGID, &inpid) == 0 &&
                 inpid.vendor == USB_VENDOR_VALVE &&
                 inpid.product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD &&
-                ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0 &&
-                GetVirtualGamepadSlot(name, &virtual_gamepad_slot)) {
+                GetSteamVirtualGamepadSlot(fd, &virtual_gamepad_slot)) {
                 VirtualGamepadEntry *new_virtual_gamepads = (VirtualGamepadEntry *)SDL_realloc(virtual_gamepads, (num_virtual_gamepads + 1) * sizeof(*virtual_gamepads));
                 if (new_virtual_gamepads) {
                     VirtualGamepadEntry *entry = &new_virtual_gamepads[num_virtual_gamepads];
@@ -1116,11 +1118,16 @@ static const char *LINUX_JoystickGetDevicePath(int device_index)
     return GetJoystickByDevIndex(device_index)->path;
 }
 
-static int LINUX_JoystickGetDevicePlayerIndex(int device_index)
+static int LINUX_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
 {
     return GetJoystickByDevIndex(device_index)->steam_virtual_gamepad_slot;
 }
 
+static int LINUX_JoystickGetDevicePlayerIndex(int device_index)
+{
+    return -1;
+}
+
 static void LINUX_JoystickSetDevicePlayerIndex(int device_index, int player_index)
 {
 }
@@ -2689,6 +2696,7 @@ SDL_JoystickDriver SDL_LINUX_JoystickDriver = {
     LINUX_JoystickDetect,
     LINUX_JoystickGetDeviceName,
     LINUX_JoystickGetDevicePath,
+    LINUX_JoystickGetDeviceSteamVirtualGamepadSlot,
     LINUX_JoystickGetDevicePlayerIndex,
     LINUX_JoystickSetDevicePlayerIndex,
     LINUX_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/n3ds/SDL_sysjoystick.c

@@ -231,6 +231,11 @@ static const char *N3DS_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int N3DS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int N3DS_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -271,6 +276,7 @@ SDL_JoystickDriver SDL_N3DS_JoystickDriver = {
     .Detect = N3DS_JoystickDetect,
     .GetDeviceName = N3DS_JoystickGetDeviceName,
     .GetDevicePath = N3DS_JoystickGetDevicePath,
+    .GetDeviceSteamVirtualGamepadSlot = N3DS_JoystickGetDeviceSteamVirtualGamepadSlot,
     .GetDevicePlayerIndex = N3DS_JoystickGetDevicePlayerIndex,
     .SetDevicePlayerIndex = N3DS_JoystickSetDevicePlayerIndex,
     .GetDeviceGUID = N3DS_JoystickGetDeviceGUID,

+ 7 - 0
src/joystick/ps2/SDL_sysjoystick.c

@@ -157,6 +157,12 @@ static const char *PS2_JoystickGetDevicePath(int index)
     return NULL;
 }
 
+/* Function to get the Steam virtual gamepad slot of a joystick */
+static int PS2_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 /* Function to get the player index of a joystick */
 static int PS2_JoystickGetDevicePlayerIndex(int device_index)
 {
@@ -341,6 +347,7 @@ SDL_JoystickDriver SDL_PS2_JoystickDriver = {
     PS2_JoystickDetect,
     PS2_JoystickGetDeviceName,
     PS2_JoystickGetDevicePath,
+    PS2_JoystickGetDeviceSteamVirtualGamepadSlot,
     PS2_JoystickGetDevicePlayerIndex,
     PS2_JoystickSetDevicePlayerIndex,
     PS2_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/psp/SDL_sysjoystick.c

@@ -121,6 +121,11 @@ static const char *PSP_JoystickGetDevicePath(int index)
     return NULL;
 }
 
+static int PSP_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int PSP_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -253,6 +258,7 @@ SDL_JoystickDriver SDL_PSP_JoystickDriver = {
     PSP_JoystickDetect,
     PSP_JoystickGetDeviceName,
     PSP_JoystickGetDevicePath,
+    PSP_JoystickGetDeviceSteamVirtualGamepadSlot,
     PSP_JoystickGetDevicePlayerIndex,
     PSP_JoystickSetDevicePlayerIndex,
     PSP_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/virtual/SDL_virtualjoystick.c

@@ -367,6 +367,11 @@ static const char *VIRTUAL_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int VIRTUAL_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -728,6 +733,7 @@ SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver = {
     VIRTUAL_JoystickDetect,
     VIRTUAL_JoystickGetDeviceName,
     VIRTUAL_JoystickGetDevicePath,
+    VIRTUAL_JoystickGetDeviceSteamVirtualGamepadSlot,
     VIRTUAL_JoystickGetDevicePlayerIndex,
     VIRTUAL_JoystickSetDevicePlayerIndex,
     VIRTUAL_JoystickGetDeviceGUID,

+ 6 - 0
src/joystick/vita/SDL_sysjoystick.c

@@ -181,6 +181,11 @@ const char *VITA_JoystickGetDevicePath(int index)
     return NULL;
 }
 
+static int VITA_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int VITA_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -378,6 +383,7 @@ SDL_JoystickDriver SDL_VITA_JoystickDriver = {
     VITA_JoystickDetect,
     VITA_JoystickGetDeviceName,
     VITA_JoystickGetDevicePath,
+    VITA_JoystickGetDeviceSteamVirtualGamepadSlot,
     VITA_JoystickGetDevicePlayerIndex,
     VITA_JoystickSetDevicePlayerIndex,
     VITA_JoystickGetDeviceGUID,

+ 13 - 2
src/joystick/windows/SDL_dinputjoystick.c

@@ -435,6 +435,17 @@ int SDL_DINPUT_JoystickInit(void)
     return 0;
 }
 
+static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
+{
+    int slot = -1;
+
+    if (vendor_id == USB_VENDOR_VALVE &&
+        product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
+        (void)SDL_sscanf(device_path, "\\\\?\\HID#VID_28DE&PID_11FF&IG_0%d", &slot);
+    }
+    return slot;
+}
+
 /* helper function for direct input, gets called for each connected joystick */
 static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInstance, LPVOID pContext)
 {
@@ -487,10 +498,10 @@ static BOOL CALLBACK EnumJoystickDetectCallback(LPCDIDEVICEINSTANCE pDeviceInsta
         pNewJoystick = pNewJoystick->pNext;
     }
 
-    pNewJoystick = (JoyStick_DeviceData *)SDL_malloc(sizeof(JoyStick_DeviceData));
+    pNewJoystick = (JoyStick_DeviceData *)SDL_calloc(1, sizeof(JoyStick_DeviceData));
     CHECK(pNewJoystick);
 
-    SDL_zerop(pNewJoystick);
+    pNewJoystick->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(vendor, product, hidPath);
     SDL_strlcpy(pNewJoystick->path, hidPath, SDL_arraysize(pNewJoystick->path));
     SDL_memcpy(&pNewJoystick->dxdevice, pDeviceInstance, sizeof(DIDEVICEINSTANCE));
 

+ 21 - 0
src/joystick/windows/SDL_rawinputjoystick.c

@@ -114,6 +114,7 @@ typedef struct SDL_RAWINPUT_Device
     SDL_JoystickGUID guid;
     SDL_bool is_xinput;
     SDL_bool is_xboxone;
+    int steam_virtual_gamepad_slot;
     PHIDP_PREPARSED_DATA preparsed_data;
 
     HANDLE hDevice;
@@ -841,6 +842,19 @@ static SDL_RAWINPUT_Device *RAWINPUT_DeviceFromHandle(HANDLE hDevice)
     return NULL;
 }
 
+static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *device_path)
+{
+    int slot = -1;
+
+    // The format for the raw input device path is documented here:
+    // https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices
+    if (vendor_id == USB_VENDOR_VALVE &&
+        product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
+        (void)SDL_sscanf(device_path, "\\\\.\\pipe\\HID#VID_045E&PID_028E&IG_00#%*X&%*X&%*X#%d#%*u", &slot);
+    }
+    return slot;
+}
+
 static void RAWINPUT_AddDevice(HANDLE hDevice)
 {
 #define CHECK(expression)  \
@@ -883,6 +897,7 @@ static void RAWINPUT_AddDevice(HANDLE hDevice)
     device->version = (Uint16)rdi.hid.dwVersionNumber;
     device->is_xinput = SDL_TRUE;
     device->is_xboxone = SDL_IsJoystickXboxOne(device->vendor_id, device->product_id);
+    device->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(device->vendor_id, device->product_id, dev_name);
 
     /* Get HID Top-Level Collection Preparsed Data */
     size = 0;
@@ -1189,6 +1204,11 @@ static const char *RAWINPUT_JoystickGetDevicePath(int device_index)
     return RAWINPUT_GetDeviceByIndex(device_index)->path;
 }
 
+static int RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return RAWINPUT_GetDeviceByIndex(device_index)->steam_virtual_gamepad_slot;
+}
+
 static int RAWINPUT_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -2198,6 +2218,7 @@ SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = {
     RAWINPUT_JoystickDetect,
     RAWINPUT_JoystickGetDeviceName,
     RAWINPUT_JoystickGetDevicePath,
+    RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
     RAWINPUT_JoystickGetDevicePlayerIndex,
     RAWINPUT_JoystickSetDevicePlayerIndex,
     RAWINPUT_JoystickGetDeviceGUID,

+ 36 - 0
src/joystick/windows/SDL_windows_gaming_input.c

@@ -58,6 +58,7 @@ typedef struct WindowsGamingInputControllerState
     char *name;
     SDL_JoystickGUID guid;
     SDL_JoystickType type;
+    int steam_virtual_gamepad_slot;
 } WindowsGamingInputControllerState;
 
 typedef HRESULT(WINAPI *CoIncrementMTAUsage_t)(PVOID *pCookie);
@@ -356,6 +357,34 @@ static ULONG STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_Release(__FI
     return rc;
 }
 
+static int GetSteamVirtualGamepadSlot(__x_ABI_CWindows_CGaming_CInput_CIRawGameController *controller, Uint16 vendor_id, Uint16 product_id)
+{
+    int slot = -1;
+
+    if (vendor_id == USB_VENDOR_VALVE &&
+        product_id == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
+        __x_ABI_CWindows_CGaming_CInput_CIRawGameController2 *controller2 = NULL;
+        HRESULT hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController_QueryInterface(controller, &IID___x_ABI_CWindows_CGaming_CInput_CIRawGameController2, (void **)&controller2);
+        if (SUCCEEDED(hr)) {
+            HSTRING hString;
+            hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString);
+            if (SUCCEEDED(hr)) {
+                PCWSTR string = wgi.WindowsGetStringRawBuffer(hString, NULL);
+                if (string) {
+                    char *id = WIN_StringToUTF8W(string);
+                    if (id) {
+                        (void)SDL_sscanf(id, "{wgi/nrid/:steam-%*X&%*X&%*X#%d#%*u}", &slot);
+                        SDL_free(id);
+                    }
+                }
+                wgi.WindowsDeleteString(hString);
+            }
+            __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_Release(controller2);
+        }
+    }
+    return slot;
+}
+
 static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController *This, IInspectable *sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController *e)
 {
     HRESULT hr;
@@ -464,6 +493,7 @@ static HRESULT STDMETHODCALLTYPE IEventHandler_CRawGameControllerVtbl_InvokeAdde
                 state->name = name;
                 state->guid = guid;
                 state->type = type;
+                state->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot(controller, vendor, product);
 
                 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_AddRef(controller);
 
@@ -664,6 +694,11 @@ static const char *WGI_JoystickGetDevicePath(int device_index)
     return NULL;
 }
 
+static int WGI_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return wgi.controllers[device_index].steam_virtual_gamepad_slot;
+}
+
 static int WGI_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -983,6 +1018,7 @@ SDL_JoystickDriver SDL_WGI_JoystickDriver = {
     WGI_JoystickDetect,
     WGI_JoystickGetDeviceName,
     WGI_JoystickGetDevicePath,
+    WGI_JoystickGetDeviceSteamVirtualGamepadSlot,
     WGI_JoystickGetDevicePlayerIndex,
     WGI_JoystickSetDevicePlayerIndex,
     WGI_JoystickGetDeviceGUID,

+ 18 - 1
src/joystick/windows/SDL_windowsjoystick.c

@@ -612,6 +612,23 @@ static const char *WINDOWS_JoystickGetDevicePath(int device_index)
     return device->path;
 }
 
+static int WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    JoyStick_DeviceData *device = SYS_Joystick;
+    int index;
+
+    for (index = device_index; index > 0; index--) {
+        device = device->pNext;
+    }
+
+    if (device->bXInputDevice) {
+        /* The slot for XInput devices can change as controllers are seated */
+        return SDL_XINPUT_GetSteamVirtualGamepadSlot(device->XInputUserId);
+    } else {
+        return device->steam_virtual_gamepad_slot;
+    }
+}
+
 static int WINDOWS_JoystickGetDevicePlayerIndex(int device_index)
 {
     JoyStick_DeviceData *device = SYS_Joystick;
@@ -669,7 +686,6 @@ static int WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
     }
 
     /* allocate memory for system specific hardware data */
-    joystick->instance_id = device->nInstanceID;
     joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata));
     if (!joystick->hwdata) {
         return -1;
@@ -792,6 +808,7 @@ SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = {
     WINDOWS_JoystickDetect,
     WINDOWS_JoystickGetDeviceName,
     WINDOWS_JoystickGetDevicePath,
+    WINDOWS_JoystickGetDeviceSteamVirtualGamepadSlot,
     WINDOWS_JoystickGetDevicePlayerIndex,
     WINDOWS_JoystickSetDevicePlayerIndex,
     WINDOWS_JoystickGetDeviceGUID,

+ 1 - 0
src/joystick/windows/SDL_windowsjoystick_c.h

@@ -42,6 +42,7 @@ typedef struct JoyStick_DeviceData
     Uint8 XInputUserId;
     DIDEVICEINSTANCE dxdevice;
     char path[MAX_PATH];
+    int steam_virtual_gamepad_slot;
     struct JoyStick_DeviceData *pNext;
 } JoyStick_DeviceData;
 

+ 22 - 4
src/joystick/windows/SDL_xinputjoystick.c

@@ -127,13 +127,31 @@ static SDL_bool GetXInputDeviceInfo(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Ui
         capabilities.ProductId = USB_PRODUCT_XBOX360_XUSB_CONTROLLER;
     }
 
-    *pVID = capabilities.VendorId;
-    *pPID = capabilities.ProductId;
-    *pVersion = capabilities.ProductVersion;
-
+    if (pVID) {
+        *pVID = capabilities.VendorId;
+    }
+    if (pPID) {
+        *pPID = capabilities.ProductId;
+    }
+    if (pVersion) {
+        *pVersion = capabilities.ProductVersion;
+    }
     return SDL_TRUE;
 }
 
+int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid)
+{
+    XINPUT_CAPABILITIES_EX capabilities;
+
+    if (XINPUTGETCAPABILITIESEX &&
+        XINPUTGETCAPABILITIESEX(1, userid, 0, &capabilities) == ERROR_SUCCESS &&
+        capabilities.VendorId == USB_VENDOR_VALVE &&
+        capabilities.ProductId == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
+        return (int)capabilities.unk2;
+    }
+    return -1;
+}
+
 static void AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
 {
     Uint16 vendor = 0;

+ 1 - 0
src/joystick/windows/SDL_xinputjoystick_c.h

@@ -36,6 +36,7 @@ extern Uint32 SDL_XINPUT_JoystickGetCapabilities(SDL_Joystick *joystick);
 extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick *joystick);
 extern void SDL_XINPUT_JoystickClose(SDL_Joystick *joystick);
 extern void SDL_XINPUT_JoystickQuit(void);
+extern int SDL_XINPUT_GetSteamVirtualGamepadSlot(Uint8 userid);
 
 /* Ends C function definitions when using C++ */
 #ifdef __cplusplus

+ 19 - 0
test/testcontroller.c

@@ -1292,6 +1292,13 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
         SDL_SetRenderDrawColor(renderer, r, g, b, a);
     }
 
+    if (controller->joystick) {
+        SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickInstanceID(controller->joystick));
+        x = (float)SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f;
+        y = 8.0f;
+        SDLTest_DrawString(renderer, x, y, text);
+    }
+
     if (controller_name && *controller_name) {
         x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
         y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
@@ -1311,6 +1318,14 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
     SDLTest_DrawString(renderer, x, y, type);
 
     if (display_mode == CONTROLLER_MODE_TESTING) {
+        Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad);
+        if (steam_handle) {
+            SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16llx", (unsigned long long)steam_handle);
+            y = (float)SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT);
+            x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
+            SDLTest_DrawString(renderer, x, y, text);
+        }
+
         SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
                      SDL_GetJoystickVendor(controller->joystick),
                      SDL_GetJoystickProduct(controller->joystick));
@@ -1595,6 +1610,10 @@ static void loop(void *arg)
             HandleGamepadRemapped(event.gdevice.which);
             break;
 
+        case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
+            RefreshControllerName();
+            break;
+
 #ifdef VERBOSE_TOUCHPAD
         case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
         case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: