Ver Fonte

Added Steam Input API support for game controllers

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

Also added an event SDL_CONTROLLERSTEAMHANDLEUPDATED which is triggered when a controller's API handle changes, e.g. the controllers were reassigned slots in the Steam UI.

(cherry picked from commit c981a597dc7c69e7532796b3a206071807479d35)
Sam Lantinga há 1 ano atrás
pai
commit
7bb0e839a6
41 ficheiros alterados com 827 adições e 49 exclusões
  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. 7 1
      VisualC-WinRT/SDL-UWP.vcxproj.filters
  5. 2 0
      VisualC/SDL/SDL.vcxproj
  6. 6 0
      VisualC/SDL/SDL.vcxproj.filters
  7. 64 3
      Xcode/SDL/SDL.xcodeproj/project.pbxproj
  8. 3 1
      include/SDL_events.h
  9. 14 0
      include/SDL_gamecontroller.h
  10. 1 0
      src/dynapi/SDL2.exports
  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. 38 9
      src/joystick/SDL_gamecontroller.c
  15. 129 11
      src/joystick/SDL_joystick.c
  16. 4 0
      src/joystick/SDL_joystick_c.h
  17. 252 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. 6 0
      src/joystick/bsd/SDL_bsdjoystick.c
  22. 21 0
      src/joystick/darwin/SDL_iokitjoystick.c
  23. 1 0
      src/joystick/darwin/SDL_iokitjoystick_c.h
  24. 6 0
      src/joystick/dummy/SDL_sysjoystick.c
  25. 6 0
      src/joystick/emscripten/SDL_sysjoystick.c
  26. 6 0
      src/joystick/haiku/SDL_haikujoystick.cc
  27. 6 0
      src/joystick/hidapi/SDL_hidapijoystick.c
  28. 11 0
      src/joystick/iphoneos/SDL_mfijoystick.m
  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. 52 0
      src/joystick/windows/SDL_windows_gaming_input.c
  38. 19 3
      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

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

@@ -412,6 +412,7 @@
     <ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h" />
     <ClInclude Include="..\..\src\joystick\SDL_gamecontrollerdb.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" />
@@ -609,6 +610,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamecontroller.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

@@ -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>
@@ -943,6 +946,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

@@ -125,6 +125,7 @@
     <ClInclude Include="..\src\joystick\controller_type.h" />
     <ClInclude Include="..\src\joystick\SDL_gamecontrollerdb.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" />
@@ -237,6 +238,7 @@
     <ClCompile Include="..\src\joystick\controller_type.c" />
     <ClCompile Include="..\src\joystick\SDL_gamecontroller.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" />

+ 7 - 1
VisualC-WinRT/SDL-UWP.vcxproj.filters

@@ -255,6 +255,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>
@@ -558,6 +561,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>
@@ -846,4 +852,4 @@
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>
-</Project>
+</Project>

+ 2 - 0
VisualC/SDL/SDL.vcxproj

@@ -336,6 +336,7 @@
     <ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h" />
     <ClInclude Include="..\..\src\joystick\SDL_gamecontrollerdb.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" />
@@ -501,6 +502,7 @@
     <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
     <ClCompile Include="..\..\src\joystick\SDL_gamecontroller.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>
@@ -934,6 +937,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>

+ 64 - 3
Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 52;
+	objectVersion = 54;
 	objects = {
 
 /* Begin PBXBuildFile section */
@@ -3396,6 +3396,33 @@
 		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 */; };
+		F362B9202B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9212B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9222B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9232B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9242B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9252B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9262B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9272B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9282B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */ = {isa = PBXBuildFile; fileRef = F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */; };
+		F362B9292B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92A2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92B2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92C2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92D2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B92F2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B9302B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B9312B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */; };
+		F362B9322B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9332B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9342B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9352B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9362B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9372B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9382B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B9392B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
+		F362B93A2B33916600D30B94 /* controller_list.h in Headers */ = {isa = PBXBuildFile; fileRef = F362B91F2B33916600D30B94 /* controller_list.h */; };
 		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; };
@@ -4107,6 +4134,9 @@
 		F31A92C628D4CB39003BFD6A /* SDL_offscreenopengles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_offscreenopengles.h; sourceTree = "<group>"; };
 		F31A92C728D4CB39003BFD6A /* SDL_offscreenopengles.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_offscreenopengles.c; sourceTree = "<group>"; };
 		F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_combined.c; sourceTree = "<group>"; };
+		F362B91D2B33916600D30B94 /* SDL_steam_virtual_gamepad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_steam_virtual_gamepad.c; sourceTree = "<group>"; };
+		F362B91E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_steam_virtual_gamepad.h; sourceTree = "<group>"; };
+		F362B91F2B33916600D30B94 /* controller_list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_list.h; 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; };
 		F376F6312559B31D00CFC0BC /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/iOSSupport/System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
@@ -4950,12 +4980,15 @@
 				A7D8A7AA23E2513E00DCD162 /* iphoneos */,
 				A7D8A7A123E2513E00DCD162 /* steam */,
 				75E09157241EA924004729E1 /* virtual */,
-				A7D8A7AD23E2513E00DCD162 /* SDL_gamecontroller.c */,
-				A7D8A7A923E2513E00DCD162 /* SDL_joystick.c */,
+				F362B91F2B33916600D30B94 /* controller_list.h */,
 				F3820712284F3609004DD584 /* controller_type.c */,
 				A7D8A7D923E2513E00DCD162 /* controller_type.h */,
+				A7D8A7AD23E2513E00DCD162 /* SDL_gamecontroller.c */,
 				A7D8A79E23E2513E00DCD162 /* SDL_gamecontrollerdb.h */,
 				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 */,
 			);
@@ -5456,6 +5489,7 @@
 				A75FCD4323E25AB700529352 /* SDL_keyboard.h in Headers */,
 				A75FCD4423E25AB700529352 /* SDL_uikitevents.h in Headers */,
 				A75FCD4523E25AB700529352 /* SDL_gesture_c.h in Headers */,
+				F362B9392B33916600D30B94 /* controller_list.h in Headers */,
 				A75FCD4623E25AB700529352 /* SDL_shaders_gl.h in Headers */,
 				A75FCD4723E25AB700529352 /* SDL_systhread_c.h in Headers */,
 				A1BB8B7327F6CF330057CFA8 /* SDL_list.h in Headers */,
@@ -5540,6 +5574,7 @@
 				A75FCD9E23E25AB700529352 /* controller_type.h in Headers */,
 				A75FCDA023E25AB700529352 /* SDL_uikitclipboard.h in Headers */,
 				A75FCDA123E25AB700529352 /* vulkan_xlib.h in Headers */,
+				F362B9302B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A75FCDA223E25AB700529352 /* SDL_uikitwindow.h in Headers */,
 				A75FCDA323E25AB700529352 /* vulkan_vi.h in Headers */,
 				A75FCDA423E25AB700529352 /* vulkan_mir.h in Headers */,
@@ -5685,6 +5720,7 @@
 				A75FCEFC23E25AC700529352 /* SDL_keyboard.h in Headers */,
 				A75FCEFD23E25AC700529352 /* SDL_uikitevents.h in Headers */,
 				A75FCEFE23E25AC700529352 /* SDL_gesture_c.h in Headers */,
+				F362B93A2B33916600D30B94 /* controller_list.h in Headers */,
 				A75FCEFF23E25AC700529352 /* SDL_shaders_gl.h in Headers */,
 				A75FCF0023E25AC700529352 /* SDL_systhread_c.h in Headers */,
 				A1BB8B7427F6CF330057CFA8 /* SDL_list.h in Headers */,
@@ -5769,6 +5805,7 @@
 				A75FCF5723E25AC700529352 /* controller_type.h in Headers */,
 				A75FCF5923E25AC700529352 /* SDL_uikitclipboard.h in Headers */,
 				A75FCF5A23E25AC700529352 /* vulkan_xlib.h in Headers */,
+				F362B9312B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A75FCF5B23E25AC700529352 /* SDL_uikitwindow.h in Headers */,
 				A75FCF5C23E25AC700529352 /* vulkan_vi.h in Headers */,
 				A75FCF5D23E25AC700529352 /* vulkan_mir.h in Headers */,
@@ -5933,6 +5970,7 @@
 				A769B10223E259AE00872273 /* math_private.h in Headers */,
 				A769B10323E259AE00872273 /* vulkan_wayland.h in Headers */,
 				A769B10523E259AE00872273 /* SDL_cocoashape.h in Headers */,
+				F362B92E2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A769B10723E259AE00872273 /* SDL_shaders_gles2.h in Headers */,
 				A769B10923E259AE00872273 /* SDL_glesfuncs.h in Headers */,
 				A769B10A23E259AE00872273 /* SDL_blendpoint.h in Headers */,
@@ -5997,6 +6035,7 @@
 				A769B15623E259AE00872273 /* SDL_syshaptic.h in Headers */,
 				A769B15723E259AE00872273 /* SDL_vulkan_internal.h in Headers */,
 				A769B15923E259AE00872273 /* SDL_cocoaevents.h in Headers */,
+				F362B9372B33916600D30B94 /* controller_list.h in Headers */,
 				A769B15A23E259AE00872273 /* vk_icd.h in Headers */,
 				A769B15B23E259AE00872273 /* SDL_nullframebuffer_c.h in Headers */,
 				A769B15D23E259AE00872273 /* SDL_dynapi_procs.h in Headers */,
@@ -6218,6 +6257,7 @@
 				A7D8BB2223E2514500DCD162 /* scancodes_windows.h in Headers */,
 				A7D8BBA023E2514500DCD162 /* scancodes_xfree86.h in Headers */,
 				A7D8B57023E2514300DCD162 /* usb_ids.h in Headers */,
+				F362B92A2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A1626A532617008D003F1973 /* SDL_triangle.h in Headers */,
 				A7D8B25523E2514200DCD162 /* vk_icd.h in Headers */,
 				A7D8B24F23E2514200DCD162 /* vk_layer.h in Headers */,
@@ -6234,6 +6274,7 @@
 				A7D8B25B23E2514200DCD162 /* vulkan_vi.h in Headers */,
 				A7D8B27923E2514200DCD162 /* vulkan_wayland.h in Headers */,
 				A7D8B27F23E2514200DCD162 /* vulkan_win32.h in Headers */,
+				F362B9332B33916600D30B94 /* controller_list.h in Headers */,
 				A7D8B29123E2514200DCD162 /* vulkan_xcb.h in Headers */,
 				A7D8B29D23E2514200DCD162 /* vulkan_xlib.h in Headers */,
 				A7D8B28B23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */,
@@ -6452,6 +6493,7 @@
 				A7D8BB2323E2514500DCD162 /* scancodes_windows.h in Headers */,
 				A7D8BBA123E2514500DCD162 /* scancodes_xfree86.h in Headers */,
 				A7D8B57123E2514300DCD162 /* usb_ids.h in Headers */,
+				F362B92B2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A1626A542617008D003F1973 /* SDL_triangle.h in Headers */,
 				A7D8B25623E2514200DCD162 /* vk_icd.h in Headers */,
 				A7D8B25023E2514200DCD162 /* vk_layer.h in Headers */,
@@ -6468,6 +6510,7 @@
 				A7D8B25C23E2514200DCD162 /* vulkan_vi.h in Headers */,
 				A7D8B27A23E2514200DCD162 /* vulkan_wayland.h in Headers */,
 				A7D8B28023E2514200DCD162 /* vulkan_win32.h in Headers */,
+				F362B9342B33916600D30B94 /* controller_list.h in Headers */,
 				A7D8B29223E2514200DCD162 /* vulkan_xcb.h in Headers */,
 				A7D8B29E23E2514200DCD162 /* vulkan_xlib.h in Headers */,
 				A7D8B28C23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */,
@@ -6570,6 +6613,7 @@
 				A7D8BAC523E2514500DCD162 /* math_private.h in Headers */,
 				A7D8B27C23E2514200DCD162 /* vulkan_wayland.h in Headers */,
 				A7D8AE8623E2514100DCD162 /* SDL_cocoashape.h in Headers */,
+				F362B92D2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A7D8BA5323E2514400DCD162 /* SDL_shaders_gles2.h in Headers */,
 				A7D8BA4723E2514400DCD162 /* SDL_glesfuncs.h in Headers */,
 				A7D8BA1123E2514400DCD162 /* SDL_blendpoint.h in Headers */,
@@ -6634,6 +6678,7 @@
 				A7D8AAD823E2514100DCD162 /* SDL_syshaptic.h in Headers */,
 				A7D8AD2123E2514100DCD162 /* SDL_vulkan_internal.h in Headers */,
 				A7D8AF1623E2514100DCD162 /* SDL_cocoaevents.h in Headers */,
+				F362B9362B33916600D30B94 /* controller_list.h in Headers */,
 				A7D8B25823E2514200DCD162 /* vk_icd.h in Headers */,
 				A7D8ABE923E2514100DCD162 /* SDL_nullframebuffer_c.h in Headers */,
 				A7D8AB2023E2514100DCD162 /* SDL_dynapi_procs.h in Headers */,
@@ -6855,6 +6900,7 @@
 				A7D8BB2123E2514500DCD162 /* scancodes_windows.h in Headers */,
 				A7D8BB9F23E2514500DCD162 /* scancodes_xfree86.h in Headers */,
 				A7D8B56F23E2514300DCD162 /* usb_ids.h in Headers */,
+				F362B9292B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A1626A522617008D003F1973 /* SDL_triangle.h in Headers */,
 				A7D8B25423E2514200DCD162 /* vk_icd.h in Headers */,
 				A7D8B24E23E2514200DCD162 /* vk_layer.h in Headers */,
@@ -6871,6 +6917,7 @@
 				A7D8B25A23E2514200DCD162 /* vulkan_vi.h in Headers */,
 				A7D8B27823E2514200DCD162 /* vulkan_wayland.h in Headers */,
 				A7D8B27E23E2514200DCD162 /* vulkan_win32.h in Headers */,
+				F362B9322B33916600D30B94 /* controller_list.h in Headers */,
 				A7D8B29023E2514200DCD162 /* vulkan_xcb.h in Headers */,
 				A7D8B29C23E2514200DCD162 /* vulkan_xlib.h in Headers */,
 				A7D8B28A23E2514200DCD162 /* vulkan_xlib_xrandr.h in Headers */,
@@ -6888,6 +6935,7 @@
 				A7D8AC0C23E2514100DCD162 /* SDL_shape_internals.h in Headers */,
 				A7D8BA7C23E2514400DCD162 /* SDL_glfuncs.h in Headers */,
 				A7D8AC0623E2514100DCD162 /* SDL_rect_c.h in Headers */,
+				F362B9352B33916600D30B94 /* controller_list.h in Headers */,
 				75E09166241EA924004729E1 /* SDL_virtualjoystick_c.h in Headers */,
 				A7D8B99E23E2514400DCD162 /* SDL_shaders_metal_osx.h in Headers */,
 				A7D8B98F23E2514400DCD162 /* SDL_shaders_metal_ios.h in Headers */,
@@ -6963,6 +7011,7 @@
 				A7D8BBFF23E2574800DCD162 /* SDL_uikitview.h in Headers */,
 				A7D8BBA823E2514500DCD162 /* SDL_events_c.h in Headers */,
 				A7D8BAC423E2514500DCD162 /* math_private.h in Headers */,
+				F362B92C2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A7D8B27B23E2514200DCD162 /* vulkan_wayland.h in Headers */,
 				A7D8BBF523E2574800DCD162 /* SDL_uikitmetalview.h in Headers */,
 				A7D8AE8523E2514100DCD162 /* SDL_cocoashape.h in Headers */,
@@ -7122,6 +7171,7 @@
 				DB313FDA17554B71006C0E22 /* SDL_keyboard.h in Headers */,
 				A7D8ACC223E2514100DCD162 /* SDL_uikitevents.h in Headers */,
 				A7D8BB3E23E2514500DCD162 /* SDL_gesture_c.h in Headers */,
+				F362B9382B33916600D30B94 /* controller_list.h in Headers */,
 				A7D8BA7823E2514400DCD162 /* SDL_shaders_gl.h in Headers */,
 				A7D8B42D23E2514300DCD162 /* SDL_systhread_c.h in Headers */,
 				A1BB8B7227F6CF330057CFA8 /* SDL_list.h in Headers */,
@@ -7206,6 +7256,7 @@
 				A7D8AC7A23E2514100DCD162 /* SDL_uikitclipboard.h in Headers */,
 				A7D8B2A123E2514200DCD162 /* vulkan_xlib.h in Headers */,
 				A7D8AC9E23E2514100DCD162 /* SDL_uikitwindow.h in Headers */,
+				F362B92F2B33916600D30B94 /* SDL_steam_virtual_gamepad.h in Headers */,
 				A7D8B25F23E2514200DCD162 /* vulkan_vi.h in Headers */,
 				A7D8B29B23E2514200DCD162 /* vulkan_mir.h in Headers */,
 				DB313FE817554B71006C0E22 /* SDL_quit.h in Headers */,
@@ -7797,6 +7848,7 @@
 				A75FCE2823E25AB700529352 /* SDL_dropevents.c in Sources */,
 				A75FCE2923E25AB700529352 /* e_atan2.c in Sources */,
 				A75FCE2A23E25AB700529352 /* s_sin.c in Sources */,
+				F362B9272B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A75FCE2B23E25AB700529352 /* SDL_power.c in Sources */,
 				A75FCE2C23E25AB700529352 /* SDL_cocoakeyboard.m in Sources */,
 				A75FCE2D23E25AB700529352 /* SDL_dynapi.c in Sources */,
@@ -7993,6 +8045,7 @@
 				A75FCFE123E25AC700529352 /* SDL_dropevents.c in Sources */,
 				A75FCFE223E25AC700529352 /* e_atan2.c in Sources */,
 				A75FCFE323E25AC700529352 /* s_sin.c in Sources */,
+				F362B9282B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A75FCFE423E25AC700529352 /* SDL_power.c in Sources */,
 				A75FCFE523E25AC700529352 /* SDL_cocoakeyboard.m in Sources */,
 				A75FCFE623E25AC700529352 /* SDL_dynapi.c in Sources */,
@@ -8303,6 +8356,7 @@
 				A769B22723E259AE00872273 /* e_sqrt.c in Sources */,
 				A769B22823E259AE00872273 /* SDL_cocoavideo.m in Sources */,
 				A769B22923E259AE00872273 /* SDL.c in Sources */,
+				F362B9252B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A769B22B23E259AE00872273 /* SDL_cocoavulkan.m in Sources */,
 				A769B22C23E259AE00872273 /* SDL_uikitappdelegate.m in Sources */,
 				A1626A432617006A003F1973 /* SDL_triangle.c in Sources */,
@@ -8386,6 +8440,7 @@
 				A7D8B5E823E2514300DCD162 /* SDL_power.c in Sources */,
 				A7D8AED723E2514100DCD162 /* SDL_cocoakeyboard.m in Sources */,
 				A7D8AB1723E2514100DCD162 /* SDL_dynapi.c in Sources */,
+				F362B9212B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8BA8623E2514400DCD162 /* SDL_shaders_gl.c in Sources */,
 				A7D8BAF223E2514500DCD162 /* e_log.c in Sources */,
 				A7D8AED123E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */,
@@ -8582,6 +8637,7 @@
 				A7D8B5E923E2514300DCD162 /* SDL_power.c in Sources */,
 				A7D8AED823E2514100DCD162 /* SDL_cocoakeyboard.m in Sources */,
 				A7D8AB1823E2514100DCD162 /* SDL_dynapi.c in Sources */,
+				F362B9222B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8BA8723E2514400DCD162 /* SDL_shaders_gl.c in Sources */,
 				A7D8BAF323E2514500DCD162 /* e_log.c in Sources */,
 				A7D8AED223E2514100DCD162 /* SDL_cocoamessagebox.m in Sources */,
@@ -8892,6 +8948,7 @@
 				A7D8BAFB23E2514500DCD162 /* e_sqrt.c in Sources */,
 				A7D8AEB023E2514100DCD162 /* SDL_cocoavideo.m in Sources */,
 				A7D8A94F23E2514000DCD162 /* SDL.c in Sources */,
+				F362B9242B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8AEA423E2514100DCD162 /* SDL_cocoavulkan.m in Sources */,
 				A7D8AC6723E2514100DCD162 /* SDL_uikitappdelegate.m in Sources */,
 				A1626A422617006A003F1973 /* SDL_triangle.c in Sources */,
@@ -8955,6 +9012,7 @@
 				A7D8BA1F23E2514400DCD162 /* SDL_blendline.c in Sources */,
 				A7D8BBE723E2574800DCD162 /* SDL_uikitviewcontroller.m in Sources */,
 				A7D8ADF223E2514100DCD162 /* SDL_blit_A.c in Sources */,
+				F362B9202B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8BBDD23E2574800DCD162 /* SDL_uikitmodes.m in Sources */,
 				A7D8BA3723E2514400DCD162 /* SDL_d3dmath.c in Sources */,
 				75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */,
@@ -8999,6 +9057,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 */,
@@ -9150,6 +9209,7 @@
 				A7D8B53C23E2514300DCD162 /* SDL_hidapi_xbox360.c in Sources */,
 				A7D8B8D523E2514400DCD162 /* SDL_coreaudio.m in Sources */,
 				A7D8BA2223E2514400DCD162 /* SDL_blendline.c in Sources */,
+				F362B9232B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8BC0623E2574800DCD162 /* SDL_uikitwindow.m in Sources */,
 				A7D8ADF523E2514100DCD162 /* SDL_blit_A.c in Sources */,
 				A7D8BA3A23E2514400DCD162 /* SDL_d3dmath.c in Sources */,
@@ -9345,6 +9405,7 @@
 				A7D8B8D723E2514400DCD162 /* SDL_coreaudio.m in Sources */,
 				A7D8BA2423E2514400DCD162 /* SDL_blendline.c in Sources */,
 				A7D8ADF723E2514100DCD162 /* SDL_blit_A.c in Sources */,
+				F362B9262B33916600D30B94 /* SDL_steam_virtual_gamepad.c in Sources */,
 				A7D8BA3C23E2514400DCD162 /* SDL_d3dmath.c in Sources */,
 				A7D8ABF023E2514100DCD162 /* SDL_nullvideo.c in Sources */,
 				A7D8AB6C23E2514100DCD162 /* SDL_offscreenevents.c in Sources */,

+ 3 - 1
include/SDL_events.h

@@ -131,6 +131,8 @@ typedef enum
     SDL_CONTROLLERTOUCHPADMOTION,      /**< Game controller touchpad finger was moved */
     SDL_CONTROLLERTOUCHPADUP,          /**< Game controller touchpad finger was lifted */
     SDL_CONTROLLERSENSORUPDATE,        /**< Game controller sensor was updated */
+    SDL_CONTROLLERUPDATECOMPLETE_RESERVED_FOR_SDL3,
+    SDL_CONTROLLERSTEAMHANDLEUPDATED,  /**< Game controller Steam handle has changed */
 
     /* Touch events */
     SDL_FINGERDOWN      = 0x700,
@@ -446,7 +448,7 @@ typedef struct SDL_ControllerButtonEvent
  */
 typedef struct SDL_ControllerDeviceEvent
 {
-    Uint32 type;        /**< ::SDL_CONTROLLERDEVICEADDED, ::SDL_CONTROLLERDEVICEREMOVED, or ::SDL_CONTROLLERDEVICEREMAPPED */
+    Uint32 type;        /**< ::SDL_CONTROLLERDEVICEADDED, ::SDL_CONTROLLERDEVICEREMOVED, ::SDL_CONTROLLERDEVICEREMAPPED, or ::SDL_CONTROLLERSTEAMHANDLEUPDATED */
     Uint32 timestamp;   /**< In milliseconds, populated using SDL_GetTicks() */
     Sint32 which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
 } SDL_ControllerDeviceEvent;

+ 14 - 0
include/SDL_gamecontroller.h

@@ -524,6 +524,20 @@ extern DECLSPEC Uint16 SDLCALL SDL_GameControllerGetFirmwareVersion(SDL_GameCont
  */
 extern DECLSPEC const char * SDLCALL SDL_GameControllerGetSerial(SDL_GameController *gamecontroller);
 
+/**
+ * Get the Steam Input handle of an opened controller, if available.
+ *
+ * Returns an InputHandle_t for the controller that can be used with Steam Input API:
+ * https://partner.steamgames.com/doc/api/ISteamInput
+ *
+ * \param gamecontroller the game controller object to query.
+ * \returns the gamepad handle, or 0 if unavailable.
+ *
+ * \since This function is available since SDL 2.30.0.
+ */
+extern DECLSPEC Uint64 SDLCALL SDL_GameControllerGetSteamHandle(SDL_GameController *gamecontroller);
+
+
 /**
  * Check if a controller has been opened and is currently connected.
  *

+ 1 - 0
src/dynapi/SDL2.exports

@@ -871,3 +871,4 @@
 ++'_SDL_HasWindowSurface'.'SDL2.dll'.'SDL_HasWindowSurface'
 ++'_SDL_DestroyWindowSurface'.'SDL2.dll'.'SDL_DestroyWindowSurface'
 # ++'_SDL_GDKGetDefaultUser'.'SDL2.dll'.'SDL_GDKGetDefaultUser'
+++'_SDL_GameControllerGetSteamHandle'.'SDL2.dll'.'SDL_GameControllerGetSteamHandle'

+ 1 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -897,3 +897,4 @@
 #define SDL_HasWindowSurface SDL_HasWindowSurface_REAL
 #define SDL_DestroyWindowSurface SDL_DestroyWindowSurface_REAL
 #define SDL_GDKGetDefaultUser SDL_GDKGetDefaultUser_REAL
+#define SDL_GameControllerGetSteamHandle SDL_GameControllerGetSteamHandle_REAL

+ 1 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -984,3 +984,4 @@ SDL_DYNAPI_PROC(int,SDL_DestroyWindowSurface,(SDL_Window *a),(a),return)
 #if defined(__GDK__)
 SDL_DYNAPI_PROC(int,SDL_GDKGetDefaultUser,(XUserHandle *a),(a),return)
 #endif
+SDL_DYNAPI_PROC(Uint64,SDL_GameControllerGetSteamHandle,(SDL_GameController *a),(a),return)

+ 3 - 0
src/events/SDL_events.c

@@ -425,6 +425,9 @@ static void SDL_LogEvent(const SDL_Event *event)
         SDL_EVENT_CASE(SDL_CONTROLLERDEVICEREMAPPED)
         PRINT_CONTROLLERDEV_EVENT(event);
         break;
+        SDL_EVENT_CASE(SDL_CONTROLLERSTEAMHANDLEUPDATED)
+        PRINT_CONTROLLERDEV_EVENT(event);
+        break;
 #undef PRINT_CONTROLLERDEV_EVENT
 
 #define PRINT_CTOUCHPAD_EVENT(event)                                                                                     \

+ 38 - 9
src/joystick/SDL_gamecontroller.c

@@ -27,6 +27,7 @@
 #include "SDL_timer.h"
 #include "SDL_sysjoystick.h"
 #include "SDL_joystick_c.h"
+#include "SDL_steam_virtual_gamepad.h"
 #include "SDL_gamecontrollerdb.h"
 #include "controller_type.h"
 #include "usb_ids.h"
@@ -822,7 +823,7 @@ SDL_COMPILE_TIME_ASSERT(map_StringForGameControllerType, SDL_arraysize(map_Strin
 /*
  * convert a string to its enum equivalent
  */
-static SDL_GameControllerType SDL_GetGameControllerTypeFromString(const char *str)
+SDL_GameControllerType SDL_GetGameControllerTypeFromString(const char *str)
 {
     int i;
 
@@ -2675,7 +2676,8 @@ const char *SDL_GameControllerName(SDL_GameController *gamecontroller)
     {
         CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
 
-        if (SDL_strcmp(gamecontroller->name, "*") == 0) {
+        if (SDL_strcmp(gamecontroller->name, "*") == 0 ||
+            gamecontroller->joystick->steam_handle != 0) {
             retval = SDL_JoystickName(gamecontroller->joystick);
         } else {
             retval = gamecontroller->name;
@@ -2698,15 +2700,27 @@ const char *SDL_GameControllerPath(SDL_GameController *gamecontroller)
 
 SDL_GameControllerType SDL_GameControllerGetType(SDL_GameController *gamecontroller)
 {
-    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+    SDL_GameControllerType type = SDL_CONTROLLER_TYPE_UNKNOWN;
+    SDL_Joystick *joystick;
+    const SDL_SteamVirtualGamepadInfo *info;
 
-    if (!joystick) {
-        return SDL_CONTROLLER_TYPE_UNKNOWN;
-    }
-    if (gamecontroller->type != SDL_CONTROLLER_TYPE_UNKNOWN) {
-        return gamecontroller->type;
+    SDL_LockJoysticks();
+    {
+        CHECK_GAMECONTROLLER_MAGIC(gamecontroller, SDL_CONTROLLER_TYPE_UNKNOWN);
+
+        joystick = gamecontroller->joystick;
+        info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+        if (info) {
+            type = info->type;
+        } else if (gamecontroller->type != SDL_CONTROLLER_TYPE_UNKNOWN) {
+            type = gamecontroller->type;
+        } else {
+            type = SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(joystick), SDL_JoystickName(joystick));
+        }
     }
-    return SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(joystick), SDL_JoystickName(joystick));
+    SDL_UnlockJoysticks();
+
+    return type;
 }
 
 int SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller)
@@ -2782,6 +2796,21 @@ const char * SDL_GameControllerGetSerial(SDL_GameController *gamecontroller)
     return SDL_JoystickGetSerial(joystick);
 }
 
+Uint64 SDL_GameControllerGetSteamHandle(SDL_GameController *gamecontroller)
+{
+    Uint64 handle = 0;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_GAMECONTROLLER_MAGIC(gamecontroller, 0);
+
+        handle = gamecontroller->joystick->steam_handle;
+    }
+    SDL_UnlockJoysticks();
+
+    return handle;
+}
+
 /*
  * Return if the controller in question is currently attached to the system,
  *  \return 0 if not plugged in, 1 if still present.

+ 129 - 11
src/joystick/SDL_joystick.c

@@ -28,6 +28,7 @@
 #include "SDL_sysjoystick.h"
 #include "SDL_hints.h"
 #include "../SDL_hints_c.h"
+#include "SDL_steam_virtual_gamepad.h"
 
 #ifndef SDL_EVENTS_DISABLED
 #include "../events/SDL_events_c.h"
@@ -622,6 +623,8 @@ int SDL_JoystickInit(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) {
@@ -660,6 +663,19 @@ SDL_JoystickID SDL_GetNextJoystickInstanceID()
     return SDL_AtomicIncRef(&SDL_next_joystick_instance_id);
 }
 
+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(SDL_JoystickGetDeviceIndexFromInstanceID(instance_id), &driver, &device_index)) {
+        info = SDL_GetSteamVirtualGamepadInfo(driver->GetDeviceSteamVirtualGamepadSlot(device_index));
+    }
+    return info;
+}
+
 /*
  * Get the implementation dependent name of a joystick
  */
@@ -667,9 +683,13 @@ const char *SDL_JoystickNameForIndex(int device_index)
 {
     SDL_JoystickDriver *driver;
     const char *name = NULL;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
-    if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickGetDeviceInstanceID(device_index));
+    if (info) {
+        name = info->name;
+    } else if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
         name = driver->GetDeviceName(device_index);
     }
     SDL_UnlockJoysticks();
@@ -750,6 +770,7 @@ SDL_Joystick *SDL_JoystickOpen(int device_index)
     const char *joystickname = NULL;
     const char *joystickpath = NULL;
     SDL_JoystickPowerLevel initial_power_level;
+    const SDL_SteamVirtualGamepadInfo *info;
 
     SDL_LockJoysticks();
 
@@ -839,6 +860,12 @@ SDL_Joystick *SDL_JoystickOpen(int device_index)
 
     joystick->is_game_controller = SDL_IsGameController(device_index);
 
+    /* Get the Steam Input API handle */
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(instance_id);
+    if (info) {
+        joystick->steam_handle = info->handle;
+    }
+
     /* Add joystick to list */
     ++joystick->ref_count;
     /* Link the joystick in the list */
@@ -1282,15 +1309,20 @@ SDL_Joystick *SDL_JoystickFromPlayerIndex(int player_index)
 const char *SDL_JoystickName(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;
 }
 
@@ -1609,6 +1641,8 @@ void SDL_JoystickQuit(void)
     SDL_QuitSubSystem(SDL_INIT_EVENTS);
 #endif
 
+    SDL_QuitSteamVirtualGamepadInfo();
+
     SDL_DelHintCallback(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
                         SDL_JoystickAllowBackgroundEventsChanged, NULL);
 
@@ -1709,7 +1743,10 @@ void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
     }
 
     if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &driver_device_index)) {
-        player_index = driver->GetDevicePlayerIndex(driver_device_index);
+        player_index = driver->GetDeviceSteamVirtualGamepadSlot(driver_device_index);
+        if (player_index < 0) {
+            player_index = driver->GetDevicePlayerIndex(driver_device_index);
+        }
     }
     if (player_index < 0 && SDL_IsGameController(device_index)) {
         player_index = SDL_FindFreePlayerIndex();
@@ -2064,6 +2101,43 @@ int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
     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_game_controller) {
+            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_CONTROLLERSTEAMHANDLEUPDATED;
+            event.common.timestamp = 0;
+            event.cdevice.which = joystick->instance_id;
+            SDL_PushEvent(&event);
+        }
+    }
+}
+
 void SDL_JoystickUpdate(void)
 {
     int i;
@@ -2076,6 +2150,10 @@ void SDL_JoystickUpdate(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();
@@ -2906,18 +2984,38 @@ SDL_JoystickGUID SDL_JoystickGetDeviceGUID(int device_index)
 Uint16 SDL_JoystickGetDeviceVendor(int device_index)
 {
     Uint16 vendor;
-    SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(device_index);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickGetDeviceInstanceID(device_index));
+    if (info) {
+        vendor = info->vendor_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(device_index);
+
+        SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
     return vendor;
 }
 
 Uint16 SDL_JoystickGetDeviceProduct(int device_index)
 {
     Uint16 product;
-    SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(device_index);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(SDL_JoystickGetDeviceInstanceID(device_index));
+    if (info) {
+        product = info->product_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(device_index);
+
+        SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, NULL, &product, NULL, NULL);
     return product;
 }
 
@@ -2995,18 +3093,38 @@ SDL_JoystickGUID SDL_JoystickGetGUID(SDL_Joystick *joystick)
 Uint16 SDL_JoystickGetVendor(SDL_Joystick *joystick)
 {
     Uint16 vendor;
-    SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+    if (info) {
+        vendor = info->vendor_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
+
+        SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
+    }
+    SDL_UnlockJoysticks();
 
-    SDL_GetJoystickGUIDInfo(guid, &vendor, NULL, NULL, NULL);
     return vendor;
 }
 
 Uint16 SDL_JoystickGetProduct(SDL_Joystick *joystick)
 {
     Uint16 product;
-    SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
+    const SDL_SteamVirtualGamepadInfo *info;
+
+    SDL_LockJoysticks();
+    info = SDL_GetJoystickInstanceVirtualGamepadInfo(joystick->instance_id);
+    if (info) {
+        product = info->product_id;
+    } else {
+        SDL_JoystickGUID guid = SDL_JoystickGetGUID(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

@@ -34,6 +34,7 @@ extern "C" {
 #endif
 
 struct _SDL_JoystickDriver;
+struct SDL_SteamVirtualGamepadInfo;
 extern char SDL_joystick_magic;
 
 /* Initialization and shutdown functions */
@@ -179,6 +180,9 @@ extern int SDL_PrivateJoystickSensor(SDL_Joystick *joystick,
 extern void SDL_PrivateJoystickBatteryLevel(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_PrivateJoystickValid(SDL_Joystick *joystick);
 

+ 252 - 0
src/joystick/SDL_steam_virtual_gamepad.c

@@ -0,0 +1,252 @@
+/*
+  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_hints.h"
+#include "SDL_timer.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"
+
+extern SDL_GameControllerType SDL_GetGameControllerTypeFromString(const char *str);
+
+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 Uint32 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;
+    Uint32 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 &&
+        !SDL_TICKS_PASSED(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_GetGameControllerTypeFromString(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_GameControllerType 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

@@ -79,6 +79,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;
@@ -167,6 +168,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

@@ -542,6 +542,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;
@@ -682,6 +687,7 @@ SDL_JoystickDriver SDL_ANDROID_JoystickDriver = {
     ANDROID_JoystickDetect,
     ANDROID_JoystickGetDeviceName,
     ANDROID_JoystickGetDevicePath,
+    ANDROID_JoystickGetDeviceSteamVirtualGamepadSlot,
     ANDROID_JoystickGetDevicePlayerIndex,
     ANDROID_JoystickSetDevicePlayerIndex,
     ANDROID_JoystickGetDeviceGUID,

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

@@ -574,6 +574,11 @@ static const char *BSD_JoystickGetDevicePath(int device_index)
     return JoystickByDevIndex(device_index)->path;
 }
 
+static int BSD_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
+{
+    return -1;
+}
+
 static int BSD_JoystickGetDevicePlayerIndex(int device_index)
 {
     return -1;
@@ -870,6 +875,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

@@ -407,6 +407,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;
@@ -487,6 +500,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) {
@@ -714,6 +728,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;
@@ -1059,6 +1079,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

@@ -52,6 +52,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;
@@ -131,6 +136,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

@@ -274,6 +274,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;
@@ -421,6 +426,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

@@ -102,6 +102,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;
@@ -299,6 +304,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

@@ -1373,6 +1373,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;
@@ -1651,6 +1656,7 @@ SDL_JoystickDriver SDL_HIDAPI_JoystickDriver = {
     HIDAPI_JoystickDetect,
     HIDAPI_JoystickGetDeviceName,
     HIDAPI_JoystickGetDevicePath,
+    HIDAPI_JoystickGetDeviceSteamVirtualGamepadSlot,
     HIDAPI_JoystickGetDevicePlayerIndex,
     HIDAPI_JoystickSetDevicePlayerIndex,
     HIDAPI_JoystickGetDeviceGUID,

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

@@ -668,6 +668,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;
@@ -712,6 +716,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;
         }
@@ -905,6 +910,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
@@ -2163,6 +2173,7 @@ SDL_JoystickDriver SDL_IOS_JoystickDriver = {
     IOS_JoystickDetect,
     IOS_JoystickGetDeviceName,
     IOS_JoystickGetDevicePath,
+    IOS_JoystickGetDeviceSteamVirtualGamepadSlot,
     IOS_JoystickGetDevicePlayerIndex,
     IOS_JoystickSetDevicePlayerIndex,
     IOS_JoystickGetDeviceGUID,

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

@@ -221,14 +221,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;
@@ -441,7 +445,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);
@@ -456,7 +459,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)) {
@@ -483,7 +486,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;
@@ -501,8 +503,10 @@ static void MaybeAddDevice(const char *path)
         goto done;
     }
 
-    close(fd);
 done:
+    if (fd >= 0) {
+        close(fd);
+    }
     SDL_UnlockJoysticks();
 }
 
@@ -870,7 +874,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;
@@ -885,8 +888,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];
@@ -1112,11 +1114,16 @@ static const char *LINUX_JoystickGetDevicePath(int device_index)
     return JoystickByDevIndex(device_index)->path;
 }
 
-static int LINUX_JoystickGetDevicePlayerIndex(int device_index)
+static int LINUX_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
 {
     return JoystickByDevIndex(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)
 {
 }
@@ -2736,6 +2743,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

@@ -234,6 +234,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;
@@ -274,6 +279,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

@@ -160,6 +160,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)
 {
@@ -343,6 +349,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

@@ -124,6 +124,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;
@@ -255,6 +260,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

@@ -362,6 +362,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;
@@ -722,6 +727,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

@@ -187,6 +187,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;
@@ -377,6 +382,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

@@ -437,6 +437,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)
 {
@@ -489,10 +500,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

@@ -119,6 +119,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;
@@ -836,6 +837,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)  \
@@ -877,6 +891,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;
@@ -1183,6 +1198,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;
@@ -2175,6 +2195,7 @@ SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver = {
     RAWINPUT_JoystickDetect,
     RAWINPUT_JoystickGetDeviceName,
     RAWINPUT_JoystickGetDevicePath,
+    RAWINPUT_JoystickGetDeviceSteamVirtualGamepadSlot,
     RAWINPUT_JoystickGetDevicePlayerIndex,
     RAWINPUT_JoystickSetDevicePlayerIndex,
     RAWINPUT_JoystickGetDeviceGUID,

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

@@ -64,6 +64,7 @@ typedef struct WindowsGamingInputControllerState
     int naxes;
     int nhats;
     int nbuttons;
+    int steam_virtual_gamepad_slot;
 } WindowsGamingInputControllerState;
 
 static struct
@@ -385,6 +386,50 @@ 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_IRawGameController2, (void **)&controller2);
+        if (SUCCEEDED(hr)) {
+            HSTRING hString;
+            hr = __x_ABI_CWindows_CGaming_CInput_CIRawGameController2_get_NonRoamableId(controller2, &hString);
+            if (SUCCEEDED(hr)) {
+                typedef PCWSTR(WINAPI * WindowsGetStringRawBuffer_t)(HSTRING string, UINT32 * length);
+                typedef HRESULT(WINAPI * WindowsDeleteString_t)(HSTRING string);
+
+                WindowsGetStringRawBuffer_t WindowsGetStringRawBufferFunc = NULL;
+                WindowsDeleteString_t WindowsDeleteStringFunc = NULL;
+#ifdef __WINRT__
+                WindowsGetStringRawBufferFunc = WindowsGetStringRawBuffer;
+                WindowsDeleteStringFunc = WindowsDeleteString;
+#else
+                {
+                    WindowsGetStringRawBufferFunc = (WindowsGetStringRawBuffer_t)WIN_LoadComBaseFunction("WindowsGetStringRawBuffer");
+                    WindowsDeleteStringFunc = (WindowsDeleteString_t)WIN_LoadComBaseFunction("WindowsDeleteString");
+                }
+#endif /* __WINRT__ */
+                if (WindowsGetStringRawBufferFunc && WindowsDeleteStringFunc) {
+                    PCWSTR string = WindowsGetStringRawBufferFunc(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);
+                        }
+                    }
+                    WindowsDeleteStringFunc(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;
@@ -502,6 +547,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_get_ButtonCount(controller, &state->nbuttons);
                 __x_ABI_CWindows_CGaming_CInput_CIRawGameController_get_AxisCount(controller, &state->naxes);
@@ -688,6 +734,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;
@@ -1004,6 +1055,7 @@ SDL_JoystickDriver SDL_WGI_JoystickDriver = {
     WGI_JoystickDetect,
     WGI_JoystickGetDeviceName,
     WGI_JoystickGetDevicePath,
+    WGI_JoystickGetDeviceSteamVirtualGamepadSlot,
     WGI_JoystickGetDevicePlayerIndex,
     WGI_JoystickSetDevicePlayerIndex,
     WGI_JoystickGetDeviceGUID,

+ 19 - 3
src/joystick/windows/SDL_windowsjoystick.c

@@ -618,6 +618,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;
@@ -676,12 +693,10 @@ 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_malloc(sizeof(struct joystick_hwdata));
+    joystick->hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(struct joystick_hwdata));
     if (!joystick->hwdata) {
         return SDL_OutOfMemory();
     }
-    SDL_zerop(joystick->hwdata);
     joystick->hwdata->guid = device->guid;
 
     if (device->bXInputDevice) {
@@ -800,6 +815,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

@@ -43,6 +43,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

@@ -129,13 +129,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