Browse Source

Added HIDAPI joystick drivers for more consistent support for Xbox, PS4 and Nintendo Switch Pro controller support across platforms.
Added SDL_GameControllerRumble() and SDL_JoystickRumble() for simple force feedback outside of the SDL haptics API

Sam Lantinga 6 năm trước cách đây
mục cha
commit
d2042e1ed4
53 tập tin đã thay đổi với 6833 bổ sung1373 xóa
  1. 18 1
      Android.mk
  2. 25 5
      VisualC/SDL/SDL.vcxproj
  3. 468 460
      VisualC/SDL/SDL.vcxproj.filters
  4. 69 19
      Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj
  5. 91 0
      Xcode/SDL/SDL.xcodeproj/project.pbxproj
  6. 19 0
      android-project/app/src/main/java/org/libsdl/app/HIDDevice.java
  7. 640 0
      android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
  8. 614 0
      android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
  9. 298 0
      android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
  10. 17 5
      android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
  11. 109 12
      configure
  12. 55 15
      configure.in
  13. 1 0
      include/SDL_config.h.in
  14. 1 0
      include/SDL_config_android.h
  15. 1 0
      include/SDL_config_iphoneos.h
  16. 1 0
      include/SDL_config_macosx.h
  17. 1 0
      include/SDL_config_windows.h
  18. 13 0
      include/SDL_gamecontroller.h
  19. 3 3
      include/SDL_haptic.h
  20. 78 0
      include/SDL_hints.h
  21. 17 4
      include/SDL_joystick.h
  22. 1 0
      include/SDL_stdinc.h
  23. 3 0
      src/dynapi/SDL_dynapi_overrides.h
  24. 3 2
      src/dynapi/SDL_dynapi_procs.h
  25. 21 8
      src/joystick/SDL_gamecontroller.c
  26. 2 0
      src/joystick/SDL_gamecontrollerdb.h
  27. 302 74
      src/joystick/SDL_joystick.c
  28. 30 1
      src/joystick/SDL_joystick_c.h
  29. 79 52
      src/joystick/SDL_sysjoystick.h
  30. 62 139
      src/joystick/android/SDL_sysjoystick.c
  31. 0 3
      src/joystick/android/SDL_sysjoystick_c.h
  32. 267 85
      src/joystick/darwin/SDL_sysjoystick.c
  33. 6 0
      src/joystick/darwin/SDL_sysjoystick_c.h
  34. 49 56
      src/joystick/dummy/SDL_sysjoystick.c
  35. 533 0
      src/joystick/hidapi/SDL_hidapi_ps4.c
  36. 899 0
      src/joystick/hidapi/SDL_hidapi_switch.c
  37. 363 0
      src/joystick/hidapi/SDL_hidapi_xbox360.c
  38. 364 0
      src/joystick/hidapi/SDL_hidapi_xboxone.c
  39. 541 0
      src/joystick/hidapi/SDL_hidapijoystick.c
  40. 70 0
      src/joystick/hidapi/SDL_hidapijoystick_c.h
  41. 75 141
      src/joystick/iphoneos/SDL_sysjoystick.m
  42. 0 3
      src/joystick/iphoneos/SDL_sysjoystick_c.h
  43. 115 40
      src/joystick/linux/SDL_sysjoystick.c
  44. 4 0
      src/joystick/linux/SDL_sysjoystick_c.h
  45. 352 185
      src/joystick/windows/SDL_dinputjoystick.c
  46. 1 0
      src/joystick/windows/SDL_dinputjoystick_c.h
  47. 67 45
      src/joystick/windows/SDL_windowsjoystick.c
  48. 5 1
      src/joystick/windows/SDL_windowsjoystick_c.h
  49. 58 13
      src/joystick/windows/SDL_xinputjoystick.c
  50. 1 0
      src/joystick/windows/SDL_xinputjoystick_c.h
  51. 12 1
      src/stdlib/SDL_string.c
  52. 5 0
      test/testgamecontroller.c
  53. 4 0
      test/testjoystick.c

+ 18 - 1
Android.mk

@@ -31,7 +31,7 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/haptic/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/joystick/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/joystick/android/*.c) \
-	$(LOCAL_PATH)/src/joystick/steam/SDL_steamcontroller.c \
+	$(wildcard $(LOCAL_PATH)/src/joystick/hidapi/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/loadso/dlopen/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/power/android/*.c) \
@@ -48,6 +48,8 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/video/yuv2rgb/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/test/*.c))
 
+LOCAL_SHARED_LIBRARIES := hidapi
+
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES
 LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid
 
@@ -88,4 +90,19 @@ LOCAL_MODULE_FILENAME := libSDL2main
 
 include $(BUILD_STATIC_LIBRARY)
 
+###########################
+#
+# hidapi library
+#
+###########################
 
+include $(CLEAR_VARS)
+
+LOCAL_CPPFLAGS += -std=c++11
+
+LOCAL_SRC_FILES := $(LOCAL_PATH)/src/hidapi/android/hid.cpp
+
+LOCAL_MODULE := libhidapi
+LOCAL_LDLIBS := -llog
+
+include $(BUILD_SHARED_LIBRARY)

+ 25 - 5
VisualC/SDL/SDL.vcxproj

@@ -80,6 +80,18 @@
     <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
     <LibraryPath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;$(LibraryPath)</LibraryPath>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <IncludePath>D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <IncludePath>D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <IncludePath>D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <IncludePath>D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath)</IncludePath>
+  </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <PreBuildEvent>
       <Command>
@@ -109,7 +121,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
@@ -140,7 +152,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
@@ -174,7 +186,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
@@ -206,7 +218,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ResourceCompile>
     <Link>
-      <AdditionalDependencies>winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalDependencies>setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <SubSystem>Windows</SubSystem>
@@ -318,6 +330,8 @@
     <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
     <ClInclude Include="..\..\src\haptic\windows\SDL_xinputhaptic_c.h" />
+    <ClInclude Include="..\..\src\joystick\hidapi\controller_type.h" />
+    <ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
     <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
     <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h" />
     <ClInclude Include="..\..\src\joystick\windows\SDL_dinputjoystick_c.h" />
@@ -411,6 +425,12 @@
     <ClCompile Include="..\..\src\haptic\windows\SDL_dinputhaptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c" />
     <ClCompile Include="..\..\src\haptic\windows\SDL_xinputhaptic.c" />
+    <ClCompile Include="..\..\src\hidapi\windows\hid.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapijoystick.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps4.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
+    <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\windows\SDL_dinputjoystick.c" />
@@ -523,4 +543,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 468 - 460
VisualC/SDL/SDL.vcxproj.filters

@@ -1,461 +1,469 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-    <Filter Include="API Headers">
-      <UniqueIdentifier>{395b3af0-33d0-411b-b153-de1676bf1ef8}</UniqueIdentifier>
-    </Filter>
-  </ItemGroup>
-  <ItemGroup>
-    <ClInclude Include="..\..\include\begin_code.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\close_code.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_assert.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_atomic.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_audio.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_bits.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_blendmode.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_clipboard.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_config.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_config_windows.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_copying.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_cpuinfo.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_egl.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_endian.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_error.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_events.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_filesystem.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_gamecontroller.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_gesture.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_haptic.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_hints.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_joystick.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_keyboard.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_keycode.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_loadso.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_log.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_main.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_messagebox.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_mouse.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_mutex.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_name.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengl.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengl_glext.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles2.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles2_gl2.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles2_gl2ext.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles2_gl2platform.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_opengles2_khrplatform.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_pixels.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_platform.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_power.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_quit.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_rect.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_render.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_revision.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_rwops.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_scancode.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_shape.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_stdinc.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_surface.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_system.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_syswm.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_assert.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_common.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_compare.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_crc32.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_font.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_fuzzer.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_harness.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_images.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_log.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_md5.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_test_random.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_thread.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_timer.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_touch.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_types.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_version.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_video.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\include\SDL_vulkan.h">
-      <Filter>API Headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\src\audio\directsound\SDL_directsound.h" />
-    <ClInclude Include="..\..\src\audio\disk\SDL_diskaudio.h" />
-    <ClInclude Include="..\..\src\audio\dummy\SDL_dummyaudio.h" />
-    <ClInclude Include="..\..\src\audio\SDL_audio_c.h" />
-    <ClInclude Include="..\..\src\audio\SDL_audiodev_c.h" />
-    <ClInclude Include="..\..\src\audio\SDL_sysaudio.h" />
-    <ClInclude Include="..\..\src\audio\SDL_wave.h" />
-    <ClInclude Include="..\..\src\audio\wasapi\SDL_wasapi.h" />
-    <ClInclude Include="..\..\src\audio\winmm\SDL_winmm.h" />
-    <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
-    <ClInclude Include="..\..\src\core\windows\SDL_windows.h" />
-    <ClInclude Include="..\..\src\core\windows\SDL_xinput.h" />
-    <ClInclude Include="..\..\src\dynapi\SDL_dynapi.h" />
-    <ClInclude Include="..\..\src\dynapi\SDL_dynapi_overrides.h" />
-    <ClInclude Include="..\..\src\dynapi\SDL_dynapi_procs.h" />
-    <ClInclude Include="..\..\src\events\blank_cursor.h" />
-    <ClInclude Include="..\..\src\events\default_cursor.h" />
-    <ClInclude Include="..\..\src\events\SDL_clipboardevents_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_dropevents_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_events_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_gesture_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_sysevents.h" />
-    <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
-    <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
-    <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
-    <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
-    <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
-    <ClInclude Include="..\..\src\haptic\windows\SDL_xinputhaptic_c.h" />
-    <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
-    <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h" />
-    <ClInclude Include="..\..\src\joystick\windows\SDL_dinputjoystick_c.h" />
-    <ClInclude Include="..\..\src\joystick\windows\SDL_windowsjoystick_c.h" />
-    <ClInclude Include="..\..\src\joystick\windows\SDL_xinputjoystick_c.h" />
-    <ClInclude Include="..\..\src\libm\math_libm.h" />
-    <ClInclude Include="..\..\src\libm\math_private.h" />
-    <ClInclude Include="..\..\src\render\opengl\SDL_glfuncs.h" />
-    <ClInclude Include="..\..\src\render\opengl\SDL_shaders_gl.h" />
-    <ClInclude Include="..\..\src\render\opengles\SDL_glesfuncs.h" />
-    <ClInclude Include="..\..\src\render\SDL_d3dmath.h" />
-    <ClInclude Include="..\..\src\render\SDL_sysrender.h" />
-    <ClInclude Include="..\..\src\render\SDL_yuv_sw_c.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_blendfillrect.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_blendline.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_blendpoint.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_draw.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_drawline.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_drawpoint.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_render_sw_c.h" />
-    <ClInclude Include="..\..\src\render\software\SDL_rotate.h" />
-    <ClInclude Include="..\..\src\SDL_dataqueue.h" />
-    <ClInclude Include="..\..\src\SDL_error_c.h" />
-    <ClInclude Include="..\..\src\thread\SDL_systhread.h" />
-    <ClInclude Include="..\..\src\thread\SDL_thread_c.h" />
-    <ClInclude Include="..\..\src\thread\windows\SDL_systhread_c.h" />
-    <ClInclude Include="..\..\src\timer\SDL_timer_c.h" />
-    <ClInclude Include="..\..\src\video\dummy\SDL_nullevents_c.h" />
-    <ClInclude Include="..\..\src\video\dummy\SDL_nullframebuffer_c.h" />
-    <ClInclude Include="..\..\src\video\dummy\SDL_nullvideo.h" />
-    <ClInclude Include="..\..\src\video\SDL_blit.h" />
-    <ClInclude Include="..\..\src\video\SDL_blit_auto.h" />
-    <ClInclude Include="..\..\src\video\SDL_blit_copy.h" />
-    <ClInclude Include="..\..\src\video\SDL_blit_slow.h" />
-    <ClInclude Include="..\..\src\video\SDL_pixels_c.h" />
-    <ClInclude Include="..\..\src\video\SDL_rect_c.h" />
-    <ClInclude Include="..\..\src\video\SDL_RLEaccel_c.h" />
-    <ClInclude Include="..\..\src\video\SDL_shape_internals.h" />
-    <ClInclude Include="..\..\src\video\SDL_sysvideo.h" />
-    <ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_vkeys.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsmodes.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowsvulkan.h" />
-    <ClInclude Include="..\..\src\video\windows\SDL_windowswindow.h" />
-    <ClInclude Include="..\..\src\video\windows\wmmsg.h" />
-    <ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
-    <ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb.h" />
-    <ClInclude Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.h" />
-    <ClInclude Include="..\..\src\render\direct3d\SDL_shaders_d3d.h" />
-  </ItemGroup>
-  <ItemGroup>
-    <ClCompile Include="..\..\src\libm\e_atan2.c" />
-    <ClCompile Include="..\..\src\libm\e_exp.c" />
-    <ClCompile Include="..\..\src\libm\e_log.c" />
-    <ClCompile Include="..\..\src\libm\e_log10.c" />
-    <ClCompile Include="..\..\src\libm\e_pow.c" />
-    <ClCompile Include="..\..\src\libm\e_rem_pio2.c" />
-    <ClCompile Include="..\..\src\libm\e_sqrt.c" />
-    <ClCompile Include="..\..\src\libm\k_cos.c" />
-    <ClCompile Include="..\..\src\libm\k_rem_pio2.c" />
-    <ClCompile Include="..\..\src\libm\k_sin.c" />
-    <ClCompile Include="..\..\src\libm\k_tan.c" />
-    <ClCompile Include="..\..\src\libm\s_atan.c" />
-    <ClCompile Include="..\..\src\libm\s_copysign.c" />
-    <ClCompile Include="..\..\src\libm\s_cos.c" />
-    <ClCompile Include="..\..\src\libm\s_fabs.c" />
-    <ClCompile Include="..\..\src\libm\s_floor.c" />
-    <ClCompile Include="..\..\src\libm\s_scalbn.c" />
-    <ClCompile Include="..\..\src\libm\s_sin.c" />
-    <ClCompile Include="..\..\src\libm\s_tan.c" />
-    <ClCompile Include="..\..\src\SDL.c" />
-    <ClCompile Include="..\..\src\SDL_assert.c" />
-    <ClCompile Include="..\..\src\atomic\SDL_atomic.c" />
-    <ClCompile Include="..\..\src\audio\SDL_audio.c" />
-    <ClCompile Include="..\..\src\audio\SDL_audiocvt.c" />
-    <ClCompile Include="..\..\src\audio\SDL_audiodev.c" />
-    <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_blendfillrect.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_blendline.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_blendpoint.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_0.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_1.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_A.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_auto.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_copy.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_N.c" />
-    <ClCompile Include="..\..\src\video\SDL_blit_slow.c" />
-    <ClCompile Include="..\..\src\video\SDL_bmp.c" />
-    <ClCompile Include="..\..\src\video\SDL_clipboard.c" />
-    <ClCompile Include="..\..\src\events\SDL_clipboardevents.c" />
-    <ClCompile Include="..\..\src\cpuinfo\SDL_cpuinfo.c" />
-    <ClCompile Include="..\..\src\render\SDL_d3dmath.c" />
-    <ClCompile Include="..\..\src\haptic\windows\SDL_dinputhaptic.c" />
-    <ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c" />
-    <ClCompile Include="..\..\src\audio\directsound\SDL_directsound.c" />
-    <ClCompile Include="..\..\src\audio\disk\SDL_diskaudio.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_drawline.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_drawpoint.c" />
-    <ClCompile Include="..\..\src\events\SDL_dropevents.c" />
-    <ClCompile Include="..\..\src\audio\dummy\SDL_dummyaudio.c" />
-    <ClCompile Include="..\..\src\dynapi\SDL_dynapi.c" />
-    <ClCompile Include="..\..\src\video\SDL_egl.c" />
-    <ClCompile Include="..\..\src\SDL_dataqueue.c" />
-    <ClCompile Include="..\..\src\SDL_error.c" />
-    <ClCompile Include="..\..\src\events\SDL_events.c" />
-    <ClCompile Include="..\..\src\video\SDL_fillrect.c" />
-    <ClCompile Include="..\..\src\joystick\SDL_gamecontroller.c" />
-    <ClCompile Include="..\..\src\events\SDL_gesture.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_getenv.c" />
-    <ClCompile Include="..\..\src\haptic\SDL_haptic.c" />
-    <ClCompile Include="..\..\src\SDL_hints.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_iconv.c" />
-    <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
-    <ClCompile Include="..\..\src\events\SDL_keyboard.c" />
-    <ClCompile Include="..\..\src\SDL_log.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_malloc.c" />
-    <ClCompile Include="..\..\src\audio\SDL_mixer.c" />
-    <ClCompile Include="..\..\src\joystick\windows\SDL_mmjoystick.c" />
-    <ClCompile Include="..\..\src\events\SDL_mouse.c" />
-    <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
-    <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
-    <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
-    <ClCompile Include="..\..\src\video\SDL_pixels.c" />
-    <ClCompile Include="..\..\src\power\SDL_power.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_qsort.c" />
-    <ClCompile Include="..\..\src\events\SDL_quit.c" />
-    <ClCompile Include="..\..\src\video\SDL_rect.c" />
-    <ClCompile Include="..\..\src\render\SDL_render.c" />
-    <ClCompile Include="..\..\src\render\direct3d\SDL_render_d3d.c" />
-    <ClCompile Include="..\..\src\render\direct3d11\SDL_render_d3d11.c" />
-    <ClCompile Include="..\..\src\render\opengl\SDL_render_gl.c" />
-    <ClCompile Include="..\..\src\render\opengles2\SDL_render_gles2.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_render_sw.c" />
-    <ClCompile Include="..\..\src\video\SDL_RLEaccel.c" />
-    <ClCompile Include="..\..\src\render\software\SDL_rotate.c" />
-    <ClCompile Include="..\..\src\file\SDL_rwops.c" />
-    <ClCompile Include="..\..\src\render\opengl\SDL_shaders_gl.c" />
-    <ClCompile Include="..\..\src\render\opengles2\SDL_shaders_gles2.c" />
-    <ClCompile Include="..\..\src\video\SDL_shape.c" />
-    <ClCompile Include="..\..\src\atomic\SDL_spinlock.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_stdlib.c" />
-    <ClCompile Include="..\..\src\video\SDL_stretch.c" />
-    <ClCompile Include="..\..\src\stdlib\SDL_string.c" />
-    <ClCompile Include="..\..\src\video\SDL_surface.c" />
-    <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
-    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfilesystem.c" />
-    <ClCompile Include="..\..\src\loadso\windows\SDL_sysloadso.c" />
-    <ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c" />
-    <ClCompile Include="..\..\src\power\windows\SDL_syspower.c" />
-    <ClCompile Include="..\..\src\thread\windows\SDL_syssem.c" />
-    <ClCompile Include="..\..\src\thread\windows\SDL_systhread.c" />
-    <ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
-    <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />
-    <ClCompile Include="..\..\src\thread\SDL_thread.c" />
-    <ClCompile Include="..\..\src\timer\SDL_timer.c" />
-    <ClCompile Include="..\..\src\events\SDL_touch.c" />
-    <ClCompile Include="..\..\src\video\SDL_video.c" />
-    <ClCompile Include="..\..\src\audio\SDL_wave.c" />
-    <ClCompile Include="..\..\src\events\SDL_windowevents.c" />
-    <ClCompile Include="..\..\src\core\windows\SDL_windows.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
-    <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c" />
-    <ClCompile Include="..\..\src\joystick\windows\SDL_windowsjoystick.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsmodes.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsopengl.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
-    <ClCompile Include="..\..\src\audio\winmm\SDL_winmm.c" />
-    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
-    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi_win32.c" />
-    <ClCompile Include="..\..\src\core\windows\SDL_xinput.c" />
-    <ClCompile Include="..\..\src\haptic\windows\SDL_xinputhaptic.c" />
-    <ClCompile Include="..\..\src\joystick\windows\SDL_xinputjoystick.c" />
-    <ClCompile Include="..\..\src\render\SDL_yuv_sw.c" />
-    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
-    <ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
-    <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
-    <ClCompile Include="..\..\src\libm\e_fmod.c" />
-    <ClCompile Include="..\..\src\video\SDL_yuv.c" />
-    <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb.c" />
-    <ClCompile Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.c" />
-    <ClCompile Include="..\..\src\render\direct3d\SDL_shaders_d3d.c" />
-  </ItemGroup>
-  <ItemGroup>
-    <ResourceCompile Include="..\..\src\main\windows\version.rc" />
-  </ItemGroup>
+???<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="API Headers">
+      <UniqueIdentifier>{395b3af0-33d0-411b-b153-de1676bf1ef8}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\include\begin_code.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\close_code.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_assert.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_atomic.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_audio.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_bits.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_blendmode.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_clipboard.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_config.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_config_windows.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_copying.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_cpuinfo.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_egl.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_endian.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_error.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_events.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_filesystem.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_gamecontroller.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_gesture.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_haptic.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_hints.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_joystick.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_keyboard.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_keycode.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_loadso.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_log.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_main.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_messagebox.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_mouse.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_mutex.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_name.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengl.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengl_glext.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles2.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles2_gl2.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles2_gl2ext.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles2_gl2platform.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_opengles2_khrplatform.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_pixels.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_platform.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_power.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_quit.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_rect.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_render.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_revision.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_rwops.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_scancode.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_shape.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_stdinc.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_surface.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_system.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_syswm.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_assert.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_common.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_compare.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_crc32.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_font.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_fuzzer.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_harness.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_images.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_log.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_md5.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_test_random.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_thread.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_timer.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_touch.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_types.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_version.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_video.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\include\SDL_vulkan.h">
+      <Filter>API Headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\audio\directsound\SDL_directsound.h" />
+    <ClInclude Include="..\..\src\audio\disk\SDL_diskaudio.h" />
+    <ClInclude Include="..\..\src\audio\dummy\SDL_dummyaudio.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audio_c.h" />
+    <ClInclude Include="..\..\src\audio\SDL_audiodev_c.h" />
+    <ClInclude Include="..\..\src\audio\SDL_sysaudio.h" />
+    <ClInclude Include="..\..\src\audio\SDL_wave.h" />
+    <ClInclude Include="..\..\src\audio\wasapi\SDL_wasapi.h" />
+    <ClInclude Include="..\..\src\audio\winmm\SDL_winmm.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_directx.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_windows.h" />
+    <ClInclude Include="..\..\src\core\windows\SDL_xinput.h" />
+    <ClInclude Include="..\..\src\dynapi\SDL_dynapi.h" />
+    <ClInclude Include="..\..\src\dynapi\SDL_dynapi_overrides.h" />
+    <ClInclude Include="..\..\src\dynapi\SDL_dynapi_procs.h" />
+    <ClInclude Include="..\..\src\events\blank_cursor.h" />
+    <ClInclude Include="..\..\src\events\default_cursor.h" />
+    <ClInclude Include="..\..\src\events\SDL_clipboardevents_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_dropevents_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_events_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_gesture_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_keyboard_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_mouse_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_sysevents.h" />
+    <ClInclude Include="..\..\src\events\SDL_touch_c.h" />
+    <ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
+    <ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
+    <ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
+    <ClInclude Include="..\..\src\haptic\windows\SDL_windowshaptic_c.h" />
+    <ClInclude Include="..\..\src\haptic\windows\SDL_xinputhaptic_c.h" />
+    <ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
+    <ClInclude Include="..\..\src\joystick\SDL_sysjoystick.h" />
+    <ClInclude Include="..\..\src\joystick\windows\SDL_dinputjoystick_c.h" />
+    <ClInclude Include="..\..\src\joystick\windows\SDL_windowsjoystick_c.h" />
+    <ClInclude Include="..\..\src\joystick\windows\SDL_xinputjoystick_c.h" />
+    <ClInclude Include="..\..\src\libm\math_libm.h" />
+    <ClInclude Include="..\..\src\libm\math_private.h" />
+    <ClInclude Include="..\..\src\render\opengl\SDL_glfuncs.h" />
+    <ClInclude Include="..\..\src\render\opengl\SDL_shaders_gl.h" />
+    <ClInclude Include="..\..\src\render\opengles\SDL_glesfuncs.h" />
+    <ClInclude Include="..\..\src\render\SDL_d3dmath.h" />
+    <ClInclude Include="..\..\src\render\SDL_sysrender.h" />
+    <ClInclude Include="..\..\src\render\SDL_yuv_sw_c.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_blendfillrect.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_blendline.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_blendpoint.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_draw.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_drawline.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_drawpoint.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_render_sw_c.h" />
+    <ClInclude Include="..\..\src\render\software\SDL_rotate.h" />
+    <ClInclude Include="..\..\src\SDL_dataqueue.h" />
+    <ClInclude Include="..\..\src\SDL_error_c.h" />
+    <ClInclude Include="..\..\src\thread\SDL_systhread.h" />
+    <ClInclude Include="..\..\src\thread\SDL_thread_c.h" />
+    <ClInclude Include="..\..\src\thread\windows\SDL_systhread_c.h" />
+    <ClInclude Include="..\..\src\timer\SDL_timer_c.h" />
+    <ClInclude Include="..\..\src\video\dummy\SDL_nullevents_c.h" />
+    <ClInclude Include="..\..\src\video\dummy\SDL_nullframebuffer_c.h" />
+    <ClInclude Include="..\..\src\video\dummy\SDL_nullvideo.h" />
+    <ClInclude Include="..\..\src\video\SDL_blit.h" />
+    <ClInclude Include="..\..\src\video\SDL_blit_auto.h" />
+    <ClInclude Include="..\..\src\video\SDL_blit_copy.h" />
+    <ClInclude Include="..\..\src\video\SDL_blit_slow.h" />
+    <ClInclude Include="..\..\src\video\SDL_pixels_c.h" />
+    <ClInclude Include="..\..\src\video\SDL_rect_c.h" />
+    <ClInclude Include="..\..\src\video\SDL_RLEaccel_c.h" />
+    <ClInclude Include="..\..\src\video\SDL_shape_internals.h" />
+    <ClInclude Include="..\..\src\video\SDL_sysvideo.h" />
+    <ClInclude Include="..\..\src\video\SDL_vulkan_internal.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_vkeys.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsclipboard.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsevents.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsframebuffer.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowskeyboard.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsmessagebox.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsmodes.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsmouse.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsopengl.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsshape.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsvideo.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowsvulkan.h" />
+    <ClInclude Include="..\..\src\video\windows\SDL_windowswindow.h" />
+    <ClInclude Include="..\..\src\video\windows\wmmsg.h" />
+    <ClInclude Include="..\..\src\video\SDL_yuv_c.h" />
+    <ClInclude Include="..\..\src\video\yuv2rgb\yuv_rgb.h" />
+    <ClInclude Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.h" />
+    <ClInclude Include="..\..\src\render\direct3d\SDL_shaders_d3d.h" />
+    <ClInclude Include="..\..\src\joystick\hidapi\controller_type.h" />
+    <ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\src\libm\e_atan2.c" />
+    <ClCompile Include="..\..\src\libm\e_exp.c" />
+    <ClCompile Include="..\..\src\libm\e_log.c" />
+    <ClCompile Include="..\..\src\libm\e_log10.c" />
+    <ClCompile Include="..\..\src\libm\e_pow.c" />
+    <ClCompile Include="..\..\src\libm\e_rem_pio2.c" />
+    <ClCompile Include="..\..\src\libm\e_sqrt.c" />
+    <ClCompile Include="..\..\src\libm\k_cos.c" />
+    <ClCompile Include="..\..\src\libm\k_rem_pio2.c" />
+    <ClCompile Include="..\..\src\libm\k_sin.c" />
+    <ClCompile Include="..\..\src\libm\k_tan.c" />
+    <ClCompile Include="..\..\src\libm\s_atan.c" />
+    <ClCompile Include="..\..\src\libm\s_copysign.c" />
+    <ClCompile Include="..\..\src\libm\s_cos.c" />
+    <ClCompile Include="..\..\src\libm\s_fabs.c" />
+    <ClCompile Include="..\..\src\libm\s_floor.c" />
+    <ClCompile Include="..\..\src\libm\s_scalbn.c" />
+    <ClCompile Include="..\..\src\libm\s_sin.c" />
+    <ClCompile Include="..\..\src\libm\s_tan.c" />
+    <ClCompile Include="..\..\src\SDL.c" />
+    <ClCompile Include="..\..\src\SDL_assert.c" />
+    <ClCompile Include="..\..\src\atomic\SDL_atomic.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audio.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audiocvt.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audiodev.c" />
+    <ClCompile Include="..\..\src\audio\SDL_audiotypecvt.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_blendfillrect.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_blendline.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_blendpoint.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_0.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_1.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_A.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_auto.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_copy.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_N.c" />
+    <ClCompile Include="..\..\src\video\SDL_blit_slow.c" />
+    <ClCompile Include="..\..\src\video\SDL_bmp.c" />
+    <ClCompile Include="..\..\src\video\SDL_clipboard.c" />
+    <ClCompile Include="..\..\src\events\SDL_clipboardevents.c" />
+    <ClCompile Include="..\..\src\cpuinfo\SDL_cpuinfo.c" />
+    <ClCompile Include="..\..\src\render\SDL_d3dmath.c" />
+    <ClCompile Include="..\..\src\haptic\windows\SDL_dinputhaptic.c" />
+    <ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c" />
+    <ClCompile Include="..\..\src\audio\directsound\SDL_directsound.c" />
+    <ClCompile Include="..\..\src\audio\disk\SDL_diskaudio.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_drawline.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_drawpoint.c" />
+    <ClCompile Include="..\..\src\events\SDL_dropevents.c" />
+    <ClCompile Include="..\..\src\audio\dummy\SDL_dummyaudio.c" />
+    <ClCompile Include="..\..\src\dynapi\SDL_dynapi.c" />
+    <ClCompile Include="..\..\src\video\SDL_egl.c" />
+    <ClCompile Include="..\..\src\SDL_dataqueue.c" />
+    <ClCompile Include="..\..\src\SDL_error.c" />
+    <ClCompile Include="..\..\src\events\SDL_events.c" />
+    <ClCompile Include="..\..\src\video\SDL_fillrect.c" />
+    <ClCompile Include="..\..\src\joystick\SDL_gamecontroller.c" />
+    <ClCompile Include="..\..\src\events\SDL_gesture.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_getenv.c" />
+    <ClCompile Include="..\..\src\haptic\SDL_haptic.c" />
+    <ClCompile Include="..\..\src\SDL_hints.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_iconv.c" />
+    <ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
+    <ClCompile Include="..\..\src\events\SDL_keyboard.c" />
+    <ClCompile Include="..\..\src\SDL_log.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_malloc.c" />
+    <ClCompile Include="..\..\src\audio\SDL_mixer.c" />
+    <ClCompile Include="..\..\src\joystick\windows\SDL_mmjoystick.c" />
+    <ClCompile Include="..\..\src\events\SDL_mouse.c" />
+    <ClCompile Include="..\..\src\video\dummy\SDL_nullevents.c" />
+    <ClCompile Include="..\..\src\video\dummy\SDL_nullframebuffer.c" />
+    <ClCompile Include="..\..\src\video\dummy\SDL_nullvideo.c" />
+    <ClCompile Include="..\..\src\video\SDL_pixels.c" />
+    <ClCompile Include="..\..\src\power\SDL_power.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_qsort.c" />
+    <ClCompile Include="..\..\src\events\SDL_quit.c" />
+    <ClCompile Include="..\..\src\video\SDL_rect.c" />
+    <ClCompile Include="..\..\src\render\SDL_render.c" />
+    <ClCompile Include="..\..\src\render\direct3d\SDL_render_d3d.c" />
+    <ClCompile Include="..\..\src\render\direct3d11\SDL_render_d3d11.c" />
+    <ClCompile Include="..\..\src\render\opengl\SDL_render_gl.c" />
+    <ClCompile Include="..\..\src\render\opengles2\SDL_render_gles2.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_render_sw.c" />
+    <ClCompile Include="..\..\src\video\SDL_RLEaccel.c" />
+    <ClCompile Include="..\..\src\render\software\SDL_rotate.c" />
+    <ClCompile Include="..\..\src\file\SDL_rwops.c" />
+    <ClCompile Include="..\..\src\render\opengl\SDL_shaders_gl.c" />
+    <ClCompile Include="..\..\src\render\opengles2\SDL_shaders_gles2.c" />
+    <ClCompile Include="..\..\src\video\SDL_shape.c" />
+    <ClCompile Include="..\..\src\atomic\SDL_spinlock.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_stdlib.c" />
+    <ClCompile Include="..\..\src\video\SDL_stretch.c" />
+    <ClCompile Include="..\..\src\stdlib\SDL_string.c" />
+    <ClCompile Include="..\..\src\video\SDL_surface.c" />
+    <ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
+    <ClCompile Include="..\..\src\filesystem\windows\SDL_sysfilesystem.c" />
+    <ClCompile Include="..\..\src\loadso\windows\SDL_sysloadso.c" />
+    <ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c" />
+    <ClCompile Include="..\..\src\power\windows\SDL_syspower.c" />
+    <ClCompile Include="..\..\src\thread\windows\SDL_syssem.c" />
+    <ClCompile Include="..\..\src\thread\windows\SDL_systhread.c" />
+    <ClCompile Include="..\..\src\timer\windows\SDL_systimer.c" />
+    <ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />
+    <ClCompile Include="..\..\src\thread\SDL_thread.c" />
+    <ClCompile Include="..\..\src\timer\SDL_timer.c" />
+    <ClCompile Include="..\..\src\events\SDL_touch.c" />
+    <ClCompile Include="..\..\src\video\SDL_video.c" />
+    <ClCompile Include="..\..\src\audio\SDL_wave.c" />
+    <ClCompile Include="..\..\src\events\SDL_windowevents.c" />
+    <ClCompile Include="..\..\src\core\windows\SDL_windows.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsclipboard.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsevents.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsframebuffer.c" />
+    <ClCompile Include="..\..\src\haptic\windows\SDL_windowshaptic.c" />
+    <ClCompile Include="..\..\src\joystick\windows\SDL_windowsjoystick.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowskeyboard.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsmessagebox.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsmodes.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsmouse.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsopengl.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsopengles.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsshape.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsvideo.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowswindow.c" />
+    <ClCompile Include="..\..\src\audio\winmm\SDL_winmm.c" />
+    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
+    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi_win32.c" />
+    <ClCompile Include="..\..\src\core\windows\SDL_xinput.c" />
+    <ClCompile Include="..\..\src\haptic\windows\SDL_xinputhaptic.c" />
+    <ClCompile Include="..\..\src\joystick\windows\SDL_xinputjoystick.c" />
+    <ClCompile Include="..\..\src\render\SDL_yuv_sw.c" />
+    <ClCompile Include="..\..\src\audio\wasapi\SDL_wasapi.c" />
+    <ClCompile Include="..\..\src\video\SDL_vulkan_utils.c" />
+    <ClCompile Include="..\..\src\video\windows\SDL_windowsvulkan.c" />
+    <ClCompile Include="..\..\src\libm\e_fmod.c" />
+    <ClCompile Include="..\..\src\video\SDL_yuv.c" />
+    <ClCompile Include="..\..\src\video\yuv2rgb\yuv_rgb.c" />
+    <ClCompile Include="..\..\src\render\direct3d11\SDL_shaders_d3d11.c" />
+    <ClCompile Include="..\..\src\render\direct3d\SDL_shaders_d3d.c" />
+    <ClCompile Include="..\..\src\hidapi\windows\hid.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps4.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_switch.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
+    <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapijoystick.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="..\..\src\main\windows\version.rc" />
+  </ItemGroup>
 </Project>

+ 69 - 19
Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj

@@ -110,8 +110,8 @@
 		56F9D5601DF73BA400C15B5D /* SDL_dataqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = 566726431DF72CF5001DD3DB /* SDL_dataqueue.c */; };
 		93CB792313FC5E5200BD3E05 /* SDL_uikitviewcontroller.h in Headers */ = {isa = PBXBuildFile; fileRef = 93CB792213FC5E5200BD3E05 /* SDL_uikitviewcontroller.h */; };
 		93CB792613FC5F5300BD3E05 /* SDL_uikitviewcontroller.m in Sources */ = {isa = PBXBuildFile; fileRef = 93CB792513FC5F5300BD3E05 /* SDL_uikitviewcontroller.m */; };
-		A7A9EEA91F702631002A5589 /* SDL_steamcontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */; };
-		A7A9EEAA1F702631002A5589 /* SDL_steamcontroller.h in Headers */ = {isa = PBXBuildFile; fileRef = A7A9EEA81F702631002A5589 /* SDL_steamcontroller.h */; };
+		A704172E20F7E74800A82227 /* controller_type.h in Headers */ = {isa = PBXBuildFile; fileRef = A704172D20F7E74800A82227 /* controller_type.h */; };
+		A704172F20F7E76000A82227 /* SDL_gamecontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */; };
 		A7F629241FE06523002F9CC9 /* SDL_uikitmetalview.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D7516F81EE1C28A00820EEA /* SDL_uikitmetalview.m */; };
 		AA0AD06216647BBB00CE5896 /* SDL_gamecontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */; };
 		AA0AD06516647BD400CE5896 /* SDL_gamecontroller.h in Headers */ = {isa = PBXBuildFile; fileRef = AA0AD06416647BD400CE5896 /* SDL_gamecontroller.h */; };
@@ -194,7 +194,18 @@
 		AADC5A631FDA10C800960936 /* SDL_shaders_metal_ios.h in Headers */ = {isa = PBXBuildFile; fileRef = AADC5A611FDA10C800960936 /* SDL_shaders_metal_ios.h */; };
 		AADC5A641FDA10C800960936 /* SDL_render_metal.m in Sources */ = {isa = PBXBuildFile; fileRef = AADC5A621FDA10C800960936 /* SDL_render_metal.m */; };
 		AADC5A651FDA10CB00960936 /* SDL_render_metal.m in Sources */ = {isa = PBXBuildFile; fileRef = AADC5A621FDA10C800960936 /* SDL_render_metal.m */; };
-		AAE7A4222041CCA90096E65A /* SDL_steamcontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */; };
+		F3BDD77620F51C3C004ECBF3 /* hid.mm in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD77520F51C3C004ECBF3 /* hid.mm */; };
+		F3BDD79220F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */; };
+		F3BDD79320F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */; };
+		F3BDD79420F51CB8004ECBF3 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */; };
+		F3BDD79520F51CB8004ECBF3 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */; };
+		F3BDD79620F51CB8004ECBF3 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */; };
+		F3BDD79720F51CB8004ECBF3 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */; };
+		F3BDD79820F51CB8004ECBF3 /* SDL_hidapi_ps4.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78E20F51CB8004ECBF3 /* SDL_hidapi_ps4.c */; };
+		F3BDD79920F51CB8004ECBF3 /* SDL_hidapi_ps4.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78E20F51CB8004ECBF3 /* SDL_hidapi_ps4.c */; };
+		F3BDD79B20F51CB8004ECBF3 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3BDD79020F51CB8004ECBF3 /* SDL_hidapijoystick_c.h */; };
+		F3BDD79C20F51CB8004ECBF3 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD79120F51CB8004ECBF3 /* SDL_hidapijoystick.c */; };
+		F3BDD79D20F51CB8004ECBF3 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD79120F51CB8004ECBF3 /* SDL_hidapijoystick.c */; };
 		FA1DC2721C62BE65008F99A0 /* SDL_uikitclipboard.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1DC2701C62BE65008F99A0 /* SDL_uikitclipboard.h */; };
 		FA1DC2731C62BE65008F99A0 /* SDL_uikitclipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = FA1DC2711C62BE65008F99A0 /* SDL_uikitclipboard.m */; };
 		FAB5981D1BB5C31500BE72C5 /* SDL_atomic.c in Sources */ = {isa = PBXBuildFile; fileRef = 04FFAB8912E23B8D00BA343D /* SDL_atomic.c */; };
@@ -223,7 +234,6 @@
 		FAB5984C1BB5C31600BE72C5 /* SDL_syshaptic.c in Sources */ = {isa = PBXBuildFile; fileRef = 047677B80EA76A31008ABAF1 /* SDL_syshaptic.c */; };
 		FAB5984D1BB5C31600BE72C5 /* SDL_haptic.c in Sources */ = {isa = PBXBuildFile; fileRef = 047677B90EA76A31008ABAF1 /* SDL_haptic.c */; };
 		FAB598501BB5C31600BE72C5 /* SDL_sysjoystick.m in Sources */ = {isa = PBXBuildFile; fileRef = FD689F000E26E5B600F90B21 /* SDL_sysjoystick.m */; };
-		FAB598511BB5C31600BE72C5 /* SDL_gamecontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */; };
 		FAB598521BB5C31600BE72C5 /* SDL_joystick.c in Sources */ = {isa = PBXBuildFile; fileRef = FD5F9D1E0E0E08B3008E885B /* SDL_joystick.c */; };
 		FAB598551BB5C31600BE72C5 /* SDL_sysloadso.c in Sources */ = {isa = PBXBuildFile; fileRef = 047AF1B20EA98D6C00811173 /* SDL_sysloadso.c */; };
 		FAB598561BB5C31600BE72C5 /* SDL_sysloadso.c in Sources */ = {isa = PBXBuildFile; fileRef = FD8BD8190E27E25900B52CD5 /* SDL_sysloadso.c */; };
@@ -432,8 +442,7 @@
 		56ED04E2118A8EFD00A56AA6 /* SDL_syspower.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDL_syspower.m; path = ../../src/power/uikit/SDL_syspower.m; sourceTree = SOURCE_ROOT; };
 		93CB792213FC5E5200BD3E05 /* SDL_uikitviewcontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_uikitviewcontroller.h; sourceTree = "<group>"; };
 		93CB792513FC5F5300BD3E05 /* SDL_uikitviewcontroller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_uikitviewcontroller.m; sourceTree = "<group>"; };
-		A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_steamcontroller.c; sourceTree = "<group>"; };
-		A7A9EEA81F702631002A5589 /* SDL_steamcontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_steamcontroller.h; sourceTree = "<group>"; };
+		A704172D20F7E74800A82227 /* controller_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_type.h; sourceTree = "<group>"; };
 		AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_gamecontroller.c; sourceTree = "<group>"; };
 		AA0AD06416647BD400CE5896 /* SDL_gamecontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamecontroller.h; sourceTree = "<group>"; };
 		AA0F8494178D5F1A00823F9D /* SDL_systls.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_systls.c; sourceTree = "<group>"; };
@@ -510,6 +519,13 @@
 		AADA5B8E16CCAB7C00107CF7 /* SDL_bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_bits.h; sourceTree = "<group>"; };
 		AADC5A611FDA10C800960936 /* SDL_shaders_metal_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_shaders_metal_ios.h; sourceTree = "<group>"; };
 		AADC5A621FDA10C800960936 /* SDL_render_metal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_render_metal.m; sourceTree = "<group>"; };
+		F3BDD77520F51C3C004ECBF3 /* hid.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = hid.mm; sourceTree = "<group>"; };
+		F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360.c; sourceTree = "<group>"; };
+		F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = "<group>"; };
+		F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = "<group>"; };
+		F3BDD78E20F51CB8004ECBF3 /* SDL_hidapi_ps4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps4.c; sourceTree = "<group>"; };
+		F3BDD79020F51CB8004ECBF3 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = "<group>"; };
+		F3BDD79120F51CB8004ECBF3 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = "<group>"; };
 		FA1DC2701C62BE65008F99A0 /* SDL_uikitclipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_uikitclipboard.h; sourceTree = "<group>"; };
 		FA1DC2711C62BE65008F99A0 /* SDL_uikitclipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_uikitclipboard.m; sourceTree = "<group>"; };
 		FAB598141BB5C1B100BE72C5 /* libSDL2.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDL2.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -778,15 +794,6 @@
 			name = uikit;
 			sourceTree = "<group>";
 		};
-		A7A9EEA61F702607002A5589 /* steam */ = {
-			isa = PBXGroup;
-			children = (
-				A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */,
-				A7A9EEA81F702631002A5589 /* SDL_steamcontroller.h */,
-			);
-			path = steam;
-			sourceTree = "<group>";
-		};
 		AA13B3521FB8B41700D9FEE6 /* yuv2rgb */ = {
 			isa = PBXGroup;
 			children = (
@@ -807,6 +814,36 @@
 			path = metal;
 			sourceTree = "<group>";
 		};
+		F35CEA6E20F51B7F003ECE98 /* hidapi */ = {
+			isa = PBXGroup;
+			children = (
+				F3BDD77420F51C18004ECBF3 /* ios */,
+			);
+			name = hidapi;
+			path = ../../src/hidapi;
+			sourceTree = SOURCE_ROOT;
+		};
+		F3BDD77420F51C18004ECBF3 /* ios */ = {
+			isa = PBXGroup;
+			children = (
+				F3BDD77520F51C3C004ECBF3 /* hid.mm */,
+			);
+			path = ios;
+			sourceTree = "<group>";
+		};
+		F3BDD78A20F51C8D004ECBF3 /* hidapi */ = {
+			isa = PBXGroup;
+			children = (
+				F3BDD78E20F51CB8004ECBF3 /* SDL_hidapi_ps4.c */,
+				F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */,
+				F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */,
+				F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */,
+				F3BDD79020F51CB8004ECBF3 /* SDL_hidapijoystick_c.h */,
+				F3BDD79120F51CB8004ECBF3 /* SDL_hidapijoystick.c */,
+			);
+			path = hidapi;
+			sourceTree = "<group>";
+		};
 		FD3F4A6F0DEA620800C5B771 /* stdlib */ = {
 			isa = PBXGroup;
 			children = (
@@ -824,8 +861,9 @@
 		FD5F9D080E0E08B3008E885B /* joystick */ = {
 			isa = PBXGroup;
 			children = (
-				A7A9EEA61F702607002A5589 /* steam */,
+				F3BDD78A20F51C8D004ECBF3 /* hidapi */,
 				FD689EFF0E26E5B600F90B21 /* iphoneos */,
+				A704172D20F7E74800A82227 /* controller_type.h */,
 				AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */,
 				FD5F9D1E0E0E08B3008E885B /* SDL_joystick.c */,
 				FD5F9D1F0E0E08B3008E885B /* SDL_joystick_c.h */,
@@ -970,6 +1008,7 @@
 				FD99B99D0DD52EDC00FB1D6B /* file */,
 				56C181E017C44D6900406AE3 /* filesystem */,
 				047677B60EA769DF008ABAF1 /* haptic */,
+				F35CEA6E20F51B7F003ECE98 /* hidapi */,
 				FD5F9D080E0E08B3008E885B /* joystick */,
 				FD8BD8150E27E25900B52CD5 /* loadso */,
 				56ED04DE118A8E9A00A56AA6 /* power */,
@@ -1213,13 +1252,13 @@
 				AA13B3591FB8B46400D9FEE6 /* yuv_rgb.h in Headers */,
 				04F7807712FB751400FC43C0 /* SDL_blendfillrect.h in Headers */,
 				04F7807912FB751400FC43C0 /* SDL_blendline.h in Headers */,
+				F3BDD79B20F51CB8004ECBF3 /* SDL_hidapijoystick_c.h in Headers */,
 				04F7807B12FB751400FC43C0 /* SDL_blendpoint.h in Headers */,
 				04F7807C12FB751400FC43C0 /* SDL_draw.h in Headers */,
 				04F7807E12FB751400FC43C0 /* SDL_drawline.h in Headers */,
 				AA13B34E1FB8B27800D9FEE6 /* SDL_yuv_c.h in Headers */,
 				04F7808012FB751400FC43C0 /* SDL_drawpoint.h in Headers */,
 				04F7808412FB753F00FC43C0 /* SDL_nullframebuffer_c.h in Headers */,
-				A7A9EEAA1F702631002A5589 /* SDL_steamcontroller.h in Headers */,
 				0442EC5012FE1C1E004C9285 /* SDL_render_sw_c.h in Headers */,
 				FA1DC2721C62BE65008F99A0 /* SDL_uikitclipboard.h in Headers */,
 				0402A85A12FE70C600CECEE3 /* SDL_shaders_gles2.h in Headers */,
@@ -1250,6 +1289,7 @@
 				AA7558AA1595D55500BBD41B /* SDL_joystick.h in Headers */,
 				AA13B34B1FB8B27800D9FEE6 /* SDL_shape_internals.h in Headers */,
 				AA7558AB1595D55500BBD41B /* SDL_keyboard.h in Headers */,
+				A704172E20F7E74800A82227 /* controller_type.h in Headers */,
 				AA7558AC1595D55500BBD41B /* SDL_keycode.h in Headers */,
 				AA7558AD1595D55500BBD41B /* SDL_loadso.h in Headers */,
 				AA7558AE1595D55500BBD41B /* SDL_log.h in Headers */,
@@ -1432,6 +1472,7 @@
 				FAB598251BB5C31500BE72C5 /* SDL_audiocvt.c in Sources */,
 				FAB598271BB5C31500BE72C5 /* SDL_audiotypecvt.c in Sources */,
 				FAB598281BB5C31500BE72C5 /* SDL_mixer.c in Sources */,
+				F3BDD79720F51CB8004ECBF3 /* SDL_hidapi_xboxone.c in Sources */,
 				FAB5982A1BB5C31500BE72C5 /* SDL_wave.c in Sources */,
 				FAFDF8C61D88D4530083E6F2 /* SDL_uikitclipboard.m in Sources */,
 				FAB5982C1BB5C31500BE72C5 /* SDL_cpuinfo.c in Sources */,
@@ -1442,7 +1483,9 @@
 				A7F629241FE06523002F9CC9 /* SDL_uikitmetalview.m in Sources */,
 				FAB5983C1BB5C31500BE72C5 /* SDL_gesture.c in Sources */,
 				FAB5983E1BB5C31500BE72C5 /* SDL_keyboard.c in Sources */,
+				F3BDD79520F51CB8004ECBF3 /* SDL_hidapi_switch.c in Sources */,
 				FAB598401BB5C31500BE72C5 /* SDL_mouse.c in Sources */,
+				A704172F20F7E76000A82227 /* SDL_gamecontroller.c in Sources */,
 				FAB598421BB5C31500BE72C5 /* SDL_quit.c in Sources */,
 				FAB598441BB5C31500BE72C5 /* SDL_touch.c in Sources */,
 				FAB598461BB5C31500BE72C5 /* SDL_windowevents.c in Sources */,
@@ -1454,8 +1497,8 @@
 				AADC5A5F1FDA105600960936 /* SDL_vulkan_utils.c in Sources */,
 				AADC5A5E1FDA105300960936 /* SDL_yuv.c in Sources */,
 				FAB5984D1BB5C31600BE72C5 /* SDL_haptic.c in Sources */,
+				F3BDD79320F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */,
 				FAB598501BB5C31600BE72C5 /* SDL_sysjoystick.m in Sources */,
-				FAB598511BB5C31600BE72C5 /* SDL_gamecontroller.c in Sources */,
 				FAB598521BB5C31600BE72C5 /* SDL_joystick.c in Sources */,
 				FAB598551BB5C31600BE72C5 /* SDL_sysloadso.c in Sources */,
 				AADC5A651FDA10CB00960936 /* SDL_render_metal.m in Sources */,
@@ -1482,6 +1525,7 @@
 				FAB598761BB5C31600BE72C5 /* SDL_stdlib.c in Sources */,
 				FAB598771BB5C31600BE72C5 /* SDL_string.c in Sources */,
 				FAB598781BB5C31600BE72C5 /* SDL_syscond.c in Sources */,
+				F3BDD79D20F51CB8004ECBF3 /* SDL_hidapijoystick.c in Sources */,
 				AADC5A601FDA10A400960936 /* SDL_uikitvulkan.m in Sources */,
 				FAB598791BB5C31600BE72C5 /* SDL_sysmutex.c in Sources */,
 				FAB5987B1BB5C31600BE72C5 /* SDL_syssem.c in Sources */,
@@ -1492,6 +1536,7 @@
 				FAB598821BB5C31600BE72C5 /* SDL_systimer.c in Sources */,
 				FAB598831BB5C31600BE72C5 /* SDL_timer.c in Sources */,
 				FAB598871BB5C31600BE72C5 /* SDL_uikitappdelegate.m in Sources */,
+				F3BDD79920F51CB8004ECBF3 /* SDL_hidapi_ps4.c in Sources */,
 				FAB598891BB5C31600BE72C5 /* SDL_uikitevents.m in Sources */,
 				FAB5988B1BB5C31600BE72C5 /* SDL_uikitmessagebox.m in Sources */,
 				FAB5988D1BB5C31600BE72C5 /* SDL_uikitmodes.m in Sources */,
@@ -1535,7 +1580,6 @@
 			files = (
 				FD6526810DE8FCDD002AD96B /* SDL_systimer.c in Sources */,
 				FD6526800DE8FCDD002AD96B /* SDL_timer.c in Sources */,
-				A7A9EEA91F702631002A5589 /* SDL_steamcontroller.c in Sources */,
 				FD3F4A7B0DEA620800C5B771 /* SDL_string.c in Sources */,
 				FD6526660DE8FCDD002AD96B /* SDL_dummyaudio.c in Sources */,
 				FD6526670DE8FCDD002AD96B /* SDL_audio.c in Sources */,
@@ -1566,7 +1610,9 @@
 				FD3F4A760DEA620800C5B771 /* SDL_getenv.c in Sources */,
 				FD3F4A770DEA620800C5B771 /* SDL_iconv.c in Sources */,
 				FD3F4A780DEA620800C5B771 /* SDL_malloc.c in Sources */,
+				F3BDD79220F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */,
 				FD3F4A790DEA620800C5B771 /* SDL_qsort.c in Sources */,
+				F3BDD79820F51CB8004ECBF3 /* SDL_hidapi_ps4.c in Sources */,
 				FD3F4A7A0DEA620800C5B771 /* SDL_stdlib.c in Sources */,
 				FDA6844D0DF2374E00F98A1A /* SDL_blit.c in Sources */,
 				FDA6844F0DF2374E00F98A1A /* SDL_blit_0.c in Sources */,
@@ -1599,11 +1645,13 @@
 				FD689F270E26E5D900F90B21 /* SDL_uikitopenglview.m in Sources */,
 				FD689FCE0E26E9D400F90B21 /* SDL_uikitappdelegate.m in Sources */,
 				FD8BD8250E27E25900B52CD5 /* SDL_sysloadso.c in Sources */,
+				F3BDD79C20F51CB8004ECBF3 /* SDL_hidapijoystick.c in Sources */,
 				047677BB0EA76A31008ABAF1 /* SDL_syshaptic.c in Sources */,
 				047677BC0EA76A31008ABAF1 /* SDL_haptic.c in Sources */,
 				047AF1B30EA98D6C00811173 /* SDL_sysloadso.c in Sources */,
 				046387460F0B5B7D0041FD65 /* SDL_fillrect.c in Sources */,
 				04F2AF561104ABD200D6DDF7 /* SDL_assert.c in Sources */,
+				F3BDD79620F51CB8004ECBF3 /* SDL_hidapi_xboxone.c in Sources */,
 				56ED04E1118A8EE200A56AA6 /* SDL_power.c in Sources */,
 				56ED04E3118A8EFD00A56AA6 /* SDL_syspower.m in Sources */,
 				006E9889119552DD001DE610 /* SDL_rwopsbundlesupport.m in Sources */,
@@ -1629,10 +1677,12 @@
 				0402A85912FE70C600CECEE3 /* SDL_shaders_gles2.c in Sources */,
 				04BAC09D1300C1290055DE28 /* SDL_log.c in Sources */,
 				56EA86FB13E9EC2B002E47EB /* SDL_coreaudio.m in Sources */,
+				F3BDD79420F51CB8004ECBF3 /* SDL_hidapi_switch.c in Sources */,
 				93CB792613FC5F5300BD3E05 /* SDL_uikitviewcontroller.m in Sources */,
 				AA628ADB159369E3005138DD /* SDL_rotate.c in Sources */,
 				AA126AD51617C5E7005ABC8F /* SDL_uikitmodes.m in Sources */,
 				AA704DD7162AA90A0076D1C1 /* SDL_dropevents.c in Sources */,
+				F3BDD77620F51C3C004ECBF3 /* hid.mm in Sources */,
 				AABCC3951640643D00AB8930 /* SDL_uikitmessagebox.m in Sources */,
 				AA0AD06216647BBB00CE5896 /* SDL_gamecontroller.c in Sources */,
 				AA0F8495178D5F1A00823F9D /* SDL_systls.c in Sources */,

+ 91 - 0
Xcode/SDL/SDL.xcodeproj/project.pbxproj

@@ -462,6 +462,30 @@
 		5C2EF6FE1FC9EE65003F5197 /* SDL_egl.c in Sources */ = {isa = PBXBuildFile; fileRef = 5C2EF6F51FC9EE35003F5197 /* SDL_egl.c */; };
 		5C2EF6FF1FC9EE65003F5197 /* SDL_rect_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C2EF6F41FC9EE34003F5197 /* SDL_rect_c.h */; };
 		5C2EF7011FC9EF10003F5197 /* SDL_egl.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C2EF7001FC9EF0F003F5197 /* SDL_egl.h */; };
+		A704170920F09A9800A82227 /* hid.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170820F09A9800A82227 /* hid.c */; };
+		A704170A20F09A9800A82227 /* hid.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170820F09A9800A82227 /* hid.c */; };
+		A704170B20F09A9800A82227 /* hid.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170820F09A9800A82227 /* hid.c */; };
+		A704171420F09AC900A82227 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */; };
+		A704171520F09AC900A82227 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */; };
+		A704171620F09AC900A82227 /* SDL_hidapijoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */; };
+		A704171720F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */; };
+		A704171820F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */; };
+		A704171920F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */; };
+		A704171A20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */; };
+		A704171B20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */; };
+		A704171C20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */; };
+		A704171D20F09AC900A82227 /* controller_type.h in Headers */ = {isa = PBXBuildFile; fileRef = A704171020F09AC900A82227 /* controller_type.h */; };
+		A704171E20F09AC900A82227 /* controller_type.h in Headers */ = {isa = PBXBuildFile; fileRef = A704171020F09AC900A82227 /* controller_type.h */; };
+		A704171F20F09AC900A82227 /* controller_type.h in Headers */ = {isa = PBXBuildFile; fileRef = A704171020F09AC900A82227 /* controller_type.h */; };
+		A704172020F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */; };
+		A704172120F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */; };
+		A704172220F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */; };
+		A704172320F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */; };
+		A704172420F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */; };
+		A704172520F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */; };
+		A704172620F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */; };
+		A704172720F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */; };
+		A704172820F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */; };
 		A7381E961D8B69D600B177DD /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; };
 		A7381E971D8B6A0300B177DD /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E931D8B69C300B177DD /* AudioToolbox.framework */; };
 		A77E6EB4167AB0A90010E40B /* SDL_gamecontroller.h in Headers */ = {isa = PBXBuildFile; fileRef = A77E6EB3167AB0A90010E40B /* SDL_gamecontroller.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1114,6 +1138,14 @@
 		5C2EF6F51FC9EE35003F5197 /* SDL_egl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_egl.c; sourceTree = "<group>"; };
 		5C2EF6F61FC9EE35003F5197 /* SDL_egl_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_egl_c.h; sourceTree = "<group>"; };
 		5C2EF7001FC9EF0F003F5197 /* SDL_egl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_egl.h; sourceTree = "<group>"; };
+		A704170820F09A9800A82227 /* hid.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hid.c; sourceTree = "<group>"; };
+		A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = "<group>"; };
+		A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = "<group>"; };
+		A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = "<group>"; };
+		A704171020F09AC900A82227 /* controller_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_type.h; sourceTree = "<group>"; };
+		A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps4.c; sourceTree = "<group>"; };
+		A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = "<group>"; };
+		A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360.c; sourceTree = "<group>"; };
 		A7381E931D8B69C300B177DD /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
 		A7381E951D8B69D600B177DD /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
 		A77E6EB3167AB0A90010E40B /* SDL_gamecontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamecontroller.h; sourceTree = "<group>"; };
@@ -1536,6 +1568,7 @@
 		04BDFDFF12E6671700899322 /* joystick */ = {
 			isa = PBXGroup;
 			children = (
+				A704170C20F09AA600A82227 /* hidapi */,
 				04BDFE0612E6671700899322 /* darwin */,
 				04BDFE1612E6671700899322 /* SDL_joystick.c */,
 				04BDFE1712E6671700899322 /* SDL_joystick_c.h */,
@@ -1818,6 +1851,7 @@
 				567E2F1F17C44BBB005F1892 /* filesystem */,
 				04BDFDEC12E6671700899322 /* file */,
 				04BDFDF112E6671700899322 /* haptic */,
+				A73EBCD520F099C10043B449 /* hidapi */,
 				04BDFDFF12E6671700899322 /* joystick */,
 				04BDFE2F12E6671700899322 /* loadso */,
 				04BDFE4512E6671700899322 /* power */,
@@ -1879,6 +1913,37 @@
 			path = opengles2;
 			sourceTree = "<group>";
 		};
+		A704170720F09A6700A82227 /* mac */ = {
+			isa = PBXGroup;
+			children = (
+				A704170820F09A9800A82227 /* hid.c */,
+			);
+			path = mac;
+			sourceTree = "<group>";
+		};
+		A704170C20F09AA600A82227 /* hidapi */ = {
+			isa = PBXGroup;
+			children = (
+				A704171020F09AC900A82227 /* controller_type.h */,
+				A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */,
+				A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */,
+				A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */,
+				A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */,
+				A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */,
+				A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */,
+			);
+			path = hidapi;
+			sourceTree = "<group>";
+		};
+		A73EBCD520F099C10043B449 /* hidapi */ = {
+			isa = PBXGroup;
+			children = (
+				A704170720F09A6700A82227 /* mac */,
+			);
+			name = hidapi;
+			path = ../../src/hidapi;
+			sourceTree = SOURCE_ROOT;
+		};
 		AA9A7F0E1FB0200B00FED37F /* yuv2rgb */ = {
 			isa = PBXGroup;
 			children = (
@@ -1993,6 +2058,7 @@
 				AA7558421595D4D800BBD41B /* SDL_revision.h in Headers */,
 				AA7558441595D4D800BBD41B /* SDL_rwops.h in Headers */,
 				AA7558461595D4D800BBD41B /* SDL_scancode.h in Headers */,
+				A704171720F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */,
 				AA7558481595D4D800BBD41B /* SDL_shape.h in Headers */,
 				AA75584A1595D4D800BBD41B /* SDL_stdinc.h in Headers */,
 				AA75584C1595D4D800BBD41B /* SDL_surface.h in Headers */,
@@ -2083,6 +2149,7 @@
 				04BD01F912E6671800899322 /* SDL_x11window.h in Headers */,
 				041B2CA612FA0D680087D585 /* SDL_sysrender.h in Headers */,
 				AA9A7F161FB0209D00FED37F /* SDL_yuv_c.h in Headers */,
+				A704171D20F09AC900A82227 /* controller_type.h in Headers */,
 				04409B9312FA97ED00FB9AA8 /* SDL_yuv_sw_c.h in Headers */,
 				04F7803912FB748500FC43C0 /* SDL_nullframebuffer_c.h in Headers */,
 				04F7804A12FB74A200FC43C0 /* SDL_blendfillrect.h in Headers */,
@@ -2132,6 +2199,7 @@
 				AA75581F1595D4D800BBD41B /* SDL_joystick.h in Headers */,
 				AA7558211595D4D800BBD41B /* SDL_keyboard.h in Headers */,
 				AA7558231595D4D800BBD41B /* SDL_keycode.h in Headers */,
+				A704171E20F09AC900A82227 /* controller_type.h in Headers */,
 				AA7558251595D4D800BBD41B /* SDL_loadso.h in Headers */,
 				AA7558271595D4D800BBD41B /* SDL_log.h in Headers */,
 				AA7558291595D4D800BBD41B /* SDL_main.h in Headers */,
@@ -2207,6 +2275,7 @@
 				04BD02DC12E6671800899322 /* SDL_systhread_c.h in Headers */,
 				04BD02E312E6671800899322 /* SDL_systhread.h in Headers */,
 				04BD02E512E6671800899322 /* SDL_thread_c.h in Headers */,
+				A704171820F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */,
 				04BD02F212E6671800899322 /* SDL_timer_c.h in Headers */,
 				04BD030D12E6671800899322 /* SDL_cocoaclipboard.h in Headers */,
 				04BD030F12E6671800899322 /* SDL_cocoaevents.h in Headers */,
@@ -2296,6 +2365,7 @@
 				DB313FD917554B71006C0E22 /* SDL_joystick.h in Headers */,
 				DB313FDA17554B71006C0E22 /* SDL_keyboard.h in Headers */,
 				DB313FDB17554B71006C0E22 /* SDL_keycode.h in Headers */,
+				A704171F20F09AC900A82227 /* controller_type.h in Headers */,
 				DB313FDC17554B71006C0E22 /* SDL_loadso.h in Headers */,
 				DB313FDD17554B71006C0E22 /* SDL_log.h in Headers */,
 				DB313FDE17554B71006C0E22 /* SDL_main.h in Headers */,
@@ -2371,6 +2441,7 @@
 				DB313F9317554B71006C0E22 /* SDL_systhread_c.h in Headers */,
 				DB313F9417554B71006C0E22 /* SDL_systhread.h in Headers */,
 				DB313F9517554B71006C0E22 /* SDL_thread_c.h in Headers */,
+				A704171920F09AC900A82227 /* SDL_hidapijoystick_c.h in Headers */,
 				DB313F9617554B71006C0E22 /* SDL_timer_c.h in Headers */,
 				DB313F9717554B71006C0E22 /* SDL_cocoaclipboard.h in Headers */,
 				DB313F9817554B71006C0E22 /* SDL_cocoaevents.h in Headers */,
@@ -2602,6 +2673,7 @@
 				04BD004112E6671800899322 /* SDL_cpuinfo.c in Sources */,
 				04BD004812E6671800899322 /* SDL_clipboardevents.c in Sources */,
 				04BD004A12E6671800899322 /* SDL_events.c in Sources */,
+				A704172620F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */,
 				04BD004C12E6671800899322 /* SDL_gesture.c in Sources */,
 				04BD004E12E6671800899322 /* SDL_keyboard.c in Sources */,
 				04BD005012E6671800899322 /* SDL_mouse.c in Sources */,
@@ -2640,6 +2712,7 @@
 				04BD00F612E6671800899322 /* SDL_cocoaevents.m in Sources */,
 				04BD00F812E6671800899322 /* SDL_cocoakeyboard.m in Sources */,
 				AA9A7F151FB0209D00FED37F /* SDL_yuv.c in Sources */,
+				A704171A20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */,
 				04BD00FA12E6671800899322 /* SDL_cocoamodes.m in Sources */,
 				4D16644F1EDD6023003DE88E /* SDL_vulkan_utils.c in Sources */,
 				04BD00FC12E6671800899322 /* SDL_cocoamouse.m in Sources */,
@@ -2648,6 +2721,7 @@
 				04BD010212E6671800899322 /* SDL_cocoavideo.m in Sources */,
 				04BD010412E6671800899322 /* SDL_cocoawindow.m in Sources */,
 				04BD011712E6671800899322 /* SDL_nullevents.c in Sources */,
+				A704172320F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */,
 				04BD011B12E6671800899322 /* SDL_nullvideo.c in Sources */,
 				04BD017512E6671800899322 /* SDL_blit.c in Sources */,
 				04BD017712E6671800899322 /* SDL_blit_0.c in Sources */,
@@ -2656,6 +2730,8 @@
 				04BD017912E6671800899322 /* SDL_blit_A.c in Sources */,
 				04BD017A12E6671800899322 /* SDL_blit_auto.c in Sources */,
 				04BD017C12E6671800899322 /* SDL_blit_copy.c in Sources */,
+				A704172020F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */,
+				A704170920F09A9800A82227 /* hid.c in Sources */,
 				04BD017E12E6671800899322 /* SDL_blit_N.c in Sources */,
 				04BD017F12E6671800899322 /* SDL_blit_slow.c in Sources */,
 				04BD018112E6671800899322 /* SDL_bmp.c in Sources */,
@@ -2664,6 +2740,7 @@
 				04BD018C12E6671800899322 /* SDL_pixels.c in Sources */,
 				04BD018E12E6671800899322 /* SDL_rect.c in Sources */,
 				04BD019612E6671800899322 /* SDL_RLEaccel.c in Sources */,
+				A704171420F09AC900A82227 /* SDL_hidapijoystick.c in Sources */,
 				04BD019812E6671800899322 /* SDL_shape.c in Sources */,
 				04BD019A12E6671800899322 /* SDL_stretch.c in Sources */,
 				04BD019B12E6671800899322 /* SDL_surface.c in Sources */,
@@ -2731,6 +2808,7 @@
 				04BD024812E6671800899322 /* SDL_audiotypecvt.c in Sources */,
 				04BD024912E6671800899322 /* SDL_mixer.c in Sources */,
 				04BD025112E6671800899322 /* SDL_wave.c in Sources */,
+				A704172720F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */,
 				04BD025C12E6671800899322 /* SDL_cpuinfo.c in Sources */,
 				04BD026312E6671800899322 /* SDL_clipboardevents.c in Sources */,
 				04BD026512E6671800899322 /* SDL_events.c in Sources */,
@@ -2769,6 +2847,7 @@
 				04BD02E412E6671800899322 /* SDL_thread.c in Sources */,
 				04BD02F112E6671800899322 /* SDL_timer.c in Sources */,
 				04BD02F312E6671800899322 /* SDL_systimer.c in Sources */,
+				A704171B20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */,
 				04BD030E12E6671800899322 /* SDL_cocoaclipboard.m in Sources */,
 				04BD031012E6671800899322 /* SDL_cocoaevents.m in Sources */,
 				04BD031212E6671800899322 /* SDL_cocoakeyboard.m in Sources */,
@@ -2777,6 +2856,7 @@
 				5C2EF6A31FC98B38003F5197 /* SDL_yuv.c in Sources */,
 				5C2EF6F11FC9D181003F5197 /* SDL_cocoaopengles.m in Sources */,
 				04BD031812E6671800899322 /* SDL_cocoaopengl.m in Sources */,
+				A704172420F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */,
 				04BD031A12E6671800899322 /* SDL_cocoashape.m in Sources */,
 				04BD031C12E6671800899322 /* SDL_cocoavideo.m in Sources */,
 				04BD031E12E6671800899322 /* SDL_cocoawindow.m in Sources */,
@@ -2785,6 +2865,8 @@
 				5C2EF6A51FC98B6B003F5197 /* yuv_rgb.c in Sources */,
 				04BD038F12E6671800899322 /* SDL_blit.c in Sources */,
 				04BD039112E6671800899322 /* SDL_blit_0.c in Sources */,
+				A704172120F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */,
+				A704170A20F09A9800A82227 /* hid.c in Sources */,
 				04BD039212E6671800899322 /* SDL_blit_1.c in Sources */,
 				04BD039312E6671800899322 /* SDL_blit_A.c in Sources */,
 				04BD039412E6671800899322 /* SDL_blit_auto.c in Sources */,
@@ -2793,6 +2875,7 @@
 				04BD039912E6671800899322 /* SDL_blit_slow.c in Sources */,
 				04BD039B12E6671800899322 /* SDL_bmp.c in Sources */,
 				04BD039C12E6671800899322 /* SDL_clipboard.c in Sources */,
+				A704171520F09AC900A82227 /* SDL_hidapijoystick.c in Sources */,
 				04BD03A112E6671800899322 /* SDL_fillrect.c in Sources */,
 				04BD03A612E6671800899322 /* SDL_pixels.c in Sources */,
 				04BD03A812E6671800899322 /* SDL_rect.c in Sources */,
@@ -2860,6 +2943,7 @@
 				DB31400617554B71006C0E22 /* SDL_audiotypecvt.c in Sources */,
 				DB31400717554B71006C0E22 /* SDL_mixer.c in Sources */,
 				DB31400817554B71006C0E22 /* SDL_wave.c in Sources */,
+				A704172820F09AC900A82227 /* SDL_hidapi_xbox360.c in Sources */,
 				DB31400917554B71006C0E22 /* SDL_cpuinfo.c in Sources */,
 				DB31400A17554B71006C0E22 /* SDL_clipboardevents.c in Sources */,
 				DB31400B17554B71006C0E22 /* SDL_events.c in Sources */,
@@ -2898,6 +2982,7 @@
 				DB31402B17554B71006C0E22 /* SDL_thread.c in Sources */,
 				DB31402C17554B71006C0E22 /* SDL_timer.c in Sources */,
 				DB31402D17554B71006C0E22 /* SDL_systimer.c in Sources */,
+				A704171C20F09AC900A82227 /* SDL_hidapi_switch.c in Sources */,
 				DB31402E17554B71006C0E22 /* SDL_cocoaclipboard.m in Sources */,
 				DB31402F17554B71006C0E22 /* SDL_cocoaevents.m in Sources */,
 				DB31403017554B71006C0E22 /* SDL_cocoakeyboard.m in Sources */,
@@ -2906,6 +2991,7 @@
 				5C2EF6A41FC98B39003F5197 /* SDL_yuv.c in Sources */,
 				5C2EF6F31FC9D182003F5197 /* SDL_cocoaopengles.m in Sources */,
 				DB31403317554B71006C0E22 /* SDL_cocoaopengl.m in Sources */,
+				A704172520F09AC900A82227 /* SDL_hidapi_xboxone.c in Sources */,
 				DB31403417554B71006C0E22 /* SDL_cocoashape.m in Sources */,
 				DB31403517554B71006C0E22 /* SDL_cocoavideo.m in Sources */,
 				DB31403617554B71006C0E22 /* SDL_cocoawindow.m in Sources */,
@@ -2914,6 +3000,8 @@
 				5C2EF6A61FC98B6C003F5197 /* yuv_rgb.c in Sources */,
 				DB31403917554B71006C0E22 /* SDL_blit.c in Sources */,
 				DB31403A17554B71006C0E22 /* SDL_blit_0.c in Sources */,
+				A704172220F09AC900A82227 /* SDL_hidapi_ps4.c in Sources */,
+				A704170B20F09A9800A82227 /* hid.c in Sources */,
 				DB31403B17554B71006C0E22 /* SDL_blit_1.c in Sources */,
 				DB31403C17554B71006C0E22 /* SDL_blit_A.c in Sources */,
 				DB31403D17554B71006C0E22 /* SDL_blit_auto.c in Sources */,
@@ -2922,6 +3010,7 @@
 				DB31404017554B71006C0E22 /* SDL_blit_slow.c in Sources */,
 				DB31404117554B71006C0E22 /* SDL_bmp.c in Sources */,
 				DB31404217554B71006C0E22 /* SDL_clipboard.c in Sources */,
+				A704171620F09AC900A82227 /* SDL_hidapijoystick.c in Sources */,
 				DB31404317554B71006C0E22 /* SDL_fillrect.c in Sources */,
 				DB31404417554B71006C0E22 /* SDL_pixels.c in Sources */,
 				DB31404517554B71006C0E22 /* SDL_rect.c in Sources */,
@@ -3019,6 +3108,7 @@
 					/usr/X11R6/include,
 					"$(VULKAN_SDK)/include",
 					../../src/video/khronos,
+					../../src/hidapi/hidapi,
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.6;
 				SDKROOT = macosx;
@@ -3114,6 +3204,7 @@
 					/usr/X11R6/include,
 					"$(VULKAN_SDK)/include",
 					../../src/video/khronos,
+					../../src/hidapi/hidapi,
 				);
 				MACOSX_DEPLOYMENT_TARGET = 10.6;
 				ONLY_ACTIVE_ARCH = YES;

+ 19 - 0
android-project/app/src/main/java/org/libsdl/app/HIDDevice.java

@@ -0,0 +1,19 @@
+package org.libsdl.app;
+
+interface HIDDevice
+{
+    public int getId();
+    public int getVendorId();
+    public int getProductId();
+    public String getSerialNumber();
+    public int getVersion();
+    public String getManufacturerName();
+    public String getProductName();
+    public boolean open();
+    public int sendFeatureReport(byte[] report);
+    public int sendOutputReport(byte[] report);
+    public boolean getFeatureReport(byte[] report);
+    public void setFrozen(boolean frozen);
+    public void close();
+    public void shutdown();
+}

+ 640 - 0
android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java

@@ -0,0 +1,640 @@
+package org.libsdl.app;
+
+import android.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothGattService;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+
+import java.lang.Runnable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.UUID;
+
+class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
+
+    private static final String TAG = "hidapi";
+    private HIDDeviceManager mManager;
+    private BluetoothDevice mDevice;
+    private int mDeviceId;
+    private BluetoothGatt mGatt;
+    private boolean mIsRegistered = false;
+    private boolean mIsConnected = false;
+    private boolean mIsChromebook = false;
+    private boolean mIsReconnecting = false;
+    private boolean mFrozen = false;
+    private LinkedList<GattOperation> mOperations;
+    GattOperation mCurrentOperation = null;
+    private Handler mHandler;
+
+    private static final int TRANSPORT_AUTO = 0;
+    private static final int TRANSPORT_BREDR = 1;
+    private static final int TRANSPORT_LE = 2;
+
+    private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
+
+    static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
+    static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
+    static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
+    static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
+
+    static class GattOperation {
+        private enum Operation {
+            CHR_READ,
+            CHR_WRITE,
+            ENABLE_NOTIFICATION
+        }
+
+        Operation mOp;
+        UUID mUuid;
+        byte[] mValue;
+        BluetoothGatt mGatt;
+        boolean mResult = true;
+
+        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
+            mGatt = gatt;
+            mOp = operation;
+            mUuid = uuid;
+        }
+
+        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
+            mGatt = gatt;
+            mOp = operation;
+            mUuid = uuid;
+            mValue = value;
+        }
+
+        public void run() {
+            // This is executed in main thread
+            BluetoothGattCharacteristic chr;
+
+            switch (mOp) {
+                case CHR_READ:
+                    chr = getCharacteristic(mUuid);
+                    //Log.v(TAG, "Reading characteristic " + chr.getUuid());
+                    if (!mGatt.readCharacteristic(chr)) {
+                        Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
+                        mResult = false;
+                        break;
+                    }
+                    mResult = true;
+                    break;
+                case CHR_WRITE:
+                    chr = getCharacteristic(mUuid);
+                    //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
+                    chr.setValue(mValue);
+                    if (!mGatt.writeCharacteristic(chr)) {
+                        Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
+                        mResult = false;
+                        break;
+                    }
+                    mResult = true;
+                    break;
+                case ENABLE_NOTIFICATION:
+                    chr = getCharacteristic(mUuid);
+                    //Log.v(TAG, "Writing descriptor of " + chr.getUuid());
+                    if (chr != null) {
+                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
+                        if (cccd != null) {
+                            int properties = chr.getProperties();
+                            byte[] value;
+                            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
+                                value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
+                            } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
+                                value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
+                            } else {
+                                Log.e(TAG, "Unable to start notifications on input characteristic");
+                                mResult = false;
+                                return;
+                            }
+
+                            mGatt.setCharacteristicNotification(chr, true);
+                            cccd.setValue(value);
+                            if (!mGatt.writeDescriptor(cccd)) {
+                                Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
+                                mResult = false;
+                                return;
+                            }
+                            mResult = true;
+                        }
+                    }
+            }
+        }
+
+        public boolean finish() {
+            return mResult;
+        }
+
+        private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+            BluetoothGattService valveService = mGatt.getService(steamControllerService);
+            if (valveService == null)
+                return null;
+            return valveService.getCharacteristic(uuid);
+        }
+
+        static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
+            return new GattOperation(gatt, Operation.CHR_READ, uuid);
+        }
+
+        static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
+            return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
+        }
+
+        static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
+            return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
+        }
+    }
+
+    public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
+        mManager = manager;
+        mDevice = device;
+        mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
+        mIsRegistered = false;
+        mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
+        mOperations = new LinkedList<GattOperation>();
+        mHandler = new Handler(Looper.getMainLooper());
+
+        mGatt = connectGatt();
+        final HIDDeviceBLESteamController finalThis = this;
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finalThis.checkConnectionForChromebookIssue();
+            }
+        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
+    }
+
+    public String getIdentifier() {
+        return String.format("SteamController.%s", mDevice.getAddress());
+    }
+
+    public BluetoothGatt getGatt() {
+        return mGatt;
+    }
+
+    // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
+    // of TRANSPORT_LE.  Let's force ourselves to connect low energy.
+    private BluetoothGatt connectGatt(boolean managed) {
+        try {
+            Method m = mDevice.getClass().getDeclaredMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
+            return (BluetoothGatt) m.invoke(mDevice, mManager.getContext(), managed, this, TRANSPORT_LE);
+        } catch (Exception e) {
+            return mDevice.connectGatt(mManager.getContext(), managed, this);
+        }
+    }
+
+    private BluetoothGatt connectGatt() {
+        return connectGatt(false);
+    }
+
+    protected int getConnectionState() {
+
+        Context context = mManager.getContext();
+        if (context == null) {
+            // We are lacking any context to get our Bluetooth information.  We'll just assume disconnected.
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (btManager == null) {
+            // This device doesn't support Bluetooth.  We should never be here, because how did
+            // we instantiate a device to start with?
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+
+        return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
+    }
+
+    public void reconnect() {
+
+        if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+            mGatt.disconnect();
+            mGatt = connectGatt();
+        }
+
+    }
+
+    protected void checkConnectionForChromebookIssue() {
+        if (!mIsChromebook) {
+            // We only do this on Chromebooks, because otherwise it's really annoying to just attempt
+            // over and over.
+            return;
+        }
+
+        int connectionState = getConnectionState();
+
+        switch (connectionState) {
+            case BluetoothProfile.STATE_CONNECTED:
+                if (!mIsConnected) {
+                    // We are in the Bad Chromebook Place.  We can force a disconnect
+                    // to try to recover.
+                    Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback.  Forcing a reconnect.");
+                    mIsReconnecting = true;
+                    mGatt.disconnect();
+                    mGatt = connectGatt(false);
+                    break;
+                }
+                else if (!isRegistered()) {
+                    if (mGatt.getServices().size() > 0) {
+                        Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration.  Trying to recover.");
+                        probeService(this);
+                    }
+                    else {
+                        Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services.  Trying to recover.");
+                        mIsReconnecting = true;
+                        mGatt.disconnect();
+                        mGatt = connectGatt(false);
+                        break;
+                    }
+                }
+                else {
+                    Log.v(TAG, "Chromebook: We are connected, and registered.  Everything's good!");
+                    return;
+                }
+                break;
+
+            case BluetoothProfile.STATE_DISCONNECTED:
+                Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us.  Attempting a disconnect/reconnect, but we may not be able to recover.");
+
+                mIsReconnecting = true;
+                mGatt.disconnect();
+                mGatt = connectGatt(false);
+                break;
+
+            case BluetoothProfile.STATE_CONNECTING:
+                Log.v(TAG, "Chromebook: We're still trying to connect.  Waiting a bit longer.");
+                break;
+        }
+
+        final HIDDeviceBLESteamController finalThis = this;
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finalThis.checkConnectionForChromebookIssue();
+            }
+        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
+    }
+
+    private boolean isRegistered() {
+        return mIsRegistered;
+    }
+
+    private void setRegistered() {
+        mIsRegistered = true;
+    }
+
+    private boolean probeService(HIDDeviceBLESteamController controller) {
+
+        if (isRegistered()) {
+            return true;
+        }
+
+        if (!mIsConnected) {
+            return false;
+        }
+
+        Log.v(TAG, "probeService controller=" + controller);
+
+        for (BluetoothGattService service : mGatt.getServices()) {
+            if (service.getUuid().equals(steamControllerService)) {
+                Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
+
+                for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
+                    if (chr.getUuid().equals(inputCharacteristic)) {
+                        Log.v(TAG, "Found input characteristic");
+                        // Start notifications
+                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
+                        if (cccd != null) {
+                            enableNotification(chr.getUuid());
+                        }
+                    }
+                }
+                return true;
+            }
+        }
+
+        if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
+            Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
+            mIsConnected = false;
+            mIsReconnecting = true;
+            mGatt.disconnect();
+            mGatt = connectGatt(false);
+        }
+
+        return false;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void finishCurrentGattOperation() {
+        GattOperation op = null;
+        synchronized (mOperations) {
+            if (mCurrentOperation != null) {
+                op = mCurrentOperation;
+                mCurrentOperation = null;
+            }
+        }
+        if (op != null) {
+            boolean result = op.finish(); // TODO: Maybe in main thread as well?
+
+            // Our operation failed, let's add it back to the beginning of our queue.
+            if (!result) {
+                mOperations.addFirst(op);
+            }
+        }
+        executeNextGattOperation();
+    }
+
+    private void executeNextGattOperation() {
+        synchronized (mOperations) {
+            if (mCurrentOperation != null)
+                return;
+
+            if (mOperations.isEmpty())
+                return;
+
+            mCurrentOperation = mOperations.removeFirst();
+        }
+
+        // Run in main thread
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mOperations) {
+                    if (mCurrentOperation == null) {
+                        Log.e(TAG, "Current operation null in executor?");
+                        return;
+                    }
+
+                    mCurrentOperation.run();
+                    // now wait for the GATT callback and when it comes, finish this operation
+                }
+            }
+        });
+    }
+
+    private void queueGattOperation(GattOperation op) {
+        synchronized (mOperations) {
+            mOperations.add(op);
+        }
+        executeNextGattOperation();
+    }
+
+    private void enableNotification(UUID chrUuid) {
+        GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
+        queueGattOperation(op);
+    }
+
+    public void writeCharacteristic(UUID uuid, byte[] value) {
+        GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
+        queueGattOperation(op);
+    }
+
+    public void readCharacteristic(UUID uuid) {
+        GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
+        queueGattOperation(op);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////  BluetoothGattCallback overridden methods
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
+        //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
+        mIsReconnecting = false;
+        if (newState == 2) {
+            mIsConnected = true;
+            // Run directly, without GattOperation
+            if (!isRegistered()) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        mGatt.discoverServices();
+                    }
+                });
+            }
+        } 
+        else if (newState == 0) {
+            mIsConnected = false;
+        }
+
+        // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
+    }
+
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+        //Log.v(TAG, "onServicesDiscovered status=" + status);
+        if (status == 0) {
+            if (gatt.getServices().size() == 0) {
+                Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
+                mIsReconnecting = true;
+                mIsConnected = false;
+                gatt.disconnect();
+                mGatt = connectGatt(false);
+            }
+            else {
+                probeService(this);
+            }
+        }
+    }
+
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
+
+        if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
+            mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
+        }
+
+        finishCurrentGattOperation();
+    }
+
+    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
+
+        if (characteristic.getUuid().equals(reportCharacteristic)) {
+            // Only register controller with the native side once it has been fully configured
+            if (!isRegistered()) {
+                Log.v(TAG, "Registering Steam Controller with ID: " + getId());
+                mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0);
+                setRegistered();
+            }
+        }
+
+        finishCurrentGattOperation();
+    }
+
+    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+    // Enable this for verbose logging of controller input reports
+        //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
+
+        if (characteristic.getUuid().equals(inputCharacteristic)) {
+            mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
+        }
+    }
+
+    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        //Log.v(TAG, "onDescriptorRead status=" + status);
+    }
+
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
+        //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
+
+        if (chr.getUuid().equals(inputCharacteristic)) {
+            boolean hasWrittenInputDescriptor = true;
+            BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
+            if (reportChr != null) {
+                Log.v(TAG, "Writing report characteristic to enter valve mode");
+                reportChr.setValue(enterValveMode);
+                gatt.writeCharacteristic(reportChr);
+            }
+        }
+
+        finishCurrentGattOperation();
+    }
+
+    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+        //Log.v(TAG, "onReliableWriteCompleted status=" + status);
+    }
+
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+        //Log.v(TAG, "onReadRemoteRssi status=" + status);
+    }
+
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+        //Log.v(TAG, "onMtuChanged status=" + status);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////// Public API
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public int getId() {
+        return mDeviceId;
+    }
+
+    @Override
+    public int getVendorId() {
+        // Valve Corporation
+        final int VALVE_USB_VID = 0x28DE;
+        return VALVE_USB_VID;
+    }
+
+    @Override
+    public int getProductId() {
+        // We don't have an easy way to query from the Bluetooth device, but we know what it is
+        final int D0G_BLE2_PID = 0x1106;
+        return D0G_BLE2_PID;
+    }
+
+    @Override
+    public String getSerialNumber() {
+        // This will be read later via feature report by Steam
+        return "12345";
+    }
+
+    @Override
+    public int getVersion() {
+        return 0;
+    }
+
+    @Override
+    public String getManufacturerName() {
+        return "Valve Corporation";
+    }
+
+    @Override
+    public String getProductName() {
+        return "Steam Controller";
+    }
+
+    @Override
+    public boolean open() {
+        return true;
+    }
+
+    @Override
+    public int sendFeatureReport(byte[] report) {
+        if (!isRegistered()) {
+            Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
+            if (mIsConnected) {
+                probeService(this);
+            }
+            return -1;
+        }
+
+        // We need to skip the first byte, as that doesn't go over the air
+	byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
+        //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
+        writeCharacteristic(reportCharacteristic, actual_report);
+        return report.length;
+    }
+
+    @Override
+    public int sendOutputReport(byte[] report) {
+        if (!isRegistered()) {
+            Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
+            if (mIsConnected) {
+                probeService(this);
+            }
+            return -1;
+        }
+
+        //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
+        writeCharacteristic(reportCharacteristic, report);
+        return report.length;
+    }
+
+    @Override
+    public boolean getFeatureReport(byte[] report) {
+        if (!isRegistered()) {
+            Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
+            if (mIsConnected) {
+                probeService(this);
+            }
+            return false;
+        }
+
+        //Log.v(TAG, "getFeatureReport");
+        readCharacteristic(reportCharacteristic);
+        return true;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public void setFrozen(boolean frozen) {
+        mFrozen = frozen;
+    }
+
+    @Override
+    public void shutdown() {
+        BluetoothGatt g = mGatt;
+        if (g != null) {
+            g.disconnect();
+            g.close();
+            mGatt = null;
+        }
+        mManager = null;
+        mIsRegistered = false;
+        mIsConnected = false;
+        mOperations.clear();
+    }
+
+}
+

+ 614 - 0
android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java

@@ -0,0 +1,614 @@
+package org.libsdl.app;
+
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.util.Log;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.hardware.usb.*;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HIDDeviceManager {
+    private static final String TAG = "hidapi";
+    private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
+
+    protected Context mContext;
+    private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();
+    private HashMap<UsbDevice, HIDDeviceUSB> mUSBDevices = new HashMap<UsbDevice, HIDDeviceUSB>();
+    private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();
+    private int mNextDeviceId = 0;
+    private SharedPreferences mSharedPreferences = null;
+    private boolean mIsChromebook = false;
+    private UsbManager mUsbManager;
+    private Handler mHandler;
+    private BluetoothManager mBluetoothManager;
+    private List<BluetoothDevice> mLastBluetoothDevices;
+
+    private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                handleUsbDeviceAttached(usbDevice);
+            } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                handleUsbDeviceDetached(usbDevice);
+            } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
+                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
+            }
+        }
+    };
+
+    private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            // Bluetooth device was connected. If it was a Steam Controller, handle it
+            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                Log.d(TAG, "Bluetooth device connected: " + device);
+
+                if (isSteamController(device)) {
+                    connectBluetoothDevice(device);
+                }
+            }
+
+            // Bluetooth device was disconnected, remove from controller manager (if any)
+            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                Log.d(TAG, "Bluetooth device disconnected: " + device);
+
+                disconnectBluetoothDevice(device);
+            }
+        }
+    };
+
+    public HIDDeviceManager(Context context) {
+        mContext = context;
+
+        HIDDeviceRegisterCallback(this);
+
+        mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
+        mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
+
+//        if (shouldClear) {
+//            SharedPreferences.Editor spedit = mSharedPreferences.edit();
+//            spedit.clear();
+//            spedit.commit();
+//        }
+//        else
+        {
+            mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
+        }
+
+        initializeUSB();
+        initializeBluetooth();
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public int getDeviceIDForIdentifier(String identifier) {
+        SharedPreferences.Editor spedit = mSharedPreferences.edit();
+
+        int result = mSharedPreferences.getInt(identifier, 0);
+        if (result == 0) {
+            result = mNextDeviceId++;
+            spedit.putInt("next_device_id", mNextDeviceId);
+        }
+
+        spedit.putInt(identifier, result);
+        spedit.commit();
+        return result;
+    }
+
+    protected void initializeUSB() {
+        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
+
+        /*
+        // Logging
+        for (UsbDevice device : mUsbManager.getDeviceList().values()) {
+            Log.i(TAG,"Path: " + device.getDeviceName());
+            Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
+            Log.i(TAG,"Product: " + device.getProductName());
+            Log.i(TAG,"ID: " + device.getDeviceId());
+            Log.i(TAG,"Class: " + device.getDeviceClass());
+            Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
+            Log.i(TAG,"Vendor ID " + device.getVendorId());
+            Log.i(TAG,"Product ID: " + device.getProductId());
+            Log.i(TAG,"Interface count: " + device.getInterfaceCount());
+            Log.i(TAG,"---------------------------------------");
+
+            // Get interface details
+            for (int index = 0; index < device.getInterfaceCount(); index++) {
+                UsbInterface mUsbInterface = device.getInterface(index);
+                Log.i(TAG,"  *****     *****");
+                Log.i(TAG,"  Interface index: " + index);
+                Log.i(TAG,"  Interface ID: " + mUsbInterface.getId());
+                Log.i(TAG,"  Interface class: " + mUsbInterface.getInterfaceClass());
+                Log.i(TAG,"  Interface subclass: " + mUsbInterface.getInterfaceSubclass());
+                Log.i(TAG,"  Interface protocol: " + mUsbInterface.getInterfaceProtocol());
+                Log.i(TAG,"  Endpoint count: " + mUsbInterface.getEndpointCount());
+
+                // Get endpoint details 
+                for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
+                {
+                    UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
+                    Log.i(TAG,"    ++++   ++++   ++++");
+                    Log.i(TAG,"    Endpoint index: " + epi);
+                    Log.i(TAG,"    Attributes: " + mEndpoint.getAttributes());
+                    Log.i(TAG,"    Direction: " + mEndpoint.getDirection());
+                    Log.i(TAG,"    Number: " + mEndpoint.getEndpointNumber());
+                    Log.i(TAG,"    Interval: " + mEndpoint.getInterval());
+                    Log.i(TAG,"    Packet size: " + mEndpoint.getMaxPacketSize());
+                    Log.i(TAG,"    Type: " + mEndpoint.getType());
+                }
+            }
+        }
+        Log.i(TAG," No more devices connected.");
+        */
+
+        // Register for USB broadcasts and permission completions
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+        filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
+        mContext.registerReceiver(mUsbBroadcast, filter);
+
+        for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
+            handleUsbDeviceAttached(usbDevice);
+        }
+    }
+
+    UsbManager getUSBManager() {
+        return mUsbManager;
+    }
+
+    protected void shutdownUSB() {
+        mContext.unregisterReceiver(mUsbBroadcast);
+    }
+
+    protected boolean isHIDDeviceUSB(UsbDevice usbDevice) {
+        for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); ++interface_number) {
+            if (isHIDDeviceInterface(usbDevice, interface_number)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isHIDDeviceInterface(UsbDevice usbDevice, int interface_number) {
+        UsbInterface usbInterface = usbDevice.getInterface(interface_number);
+        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
+            return true;
+        }
+        if (interface_number == 0) {
+            if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
+        final int XB360_IFACE_SUBCLASS = 93;
+        final int XB360_IFACE_PROTOCOL = 1; // Wired only
+        final int[] SUPPORTED_VENDORS = {
+            0x0079, // GPD Win 2
+            0x044f, // Thrustmaster
+            0x045e, // Microsoft
+            0x046d, // Logitech
+            0x056e, // Elecom
+            0x06a3, // Saitek
+            0x0738, // Mad Catz
+            0x07ff, // Mad Catz
+            0x0e6f, // Unknown
+            0x0f0d, // Hori
+            0x11c9, // Nacon
+            0x12ab, // Unknown
+            0x1430, // RedOctane
+            0x146b, // BigBen
+            0x1532, // Razer Sabertooth
+            0x15e4, // Numark
+            0x162e, // Joytech
+            0x1689, // Razer Onza
+            0x1bad, // Harmonix
+            0x24c6, // PowerA
+        };
+
+        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
+            usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
+            usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL) {
+            int vendor_id = usbDevice.getVendorId();
+            for (int supportedVid : SUPPORTED_VENDORS) {
+                if (vendor_id == supportedVid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
+        final int XB1_IFACE_SUBCLASS = 71;
+        final int XB1_IFACE_PROTOCOL = 208;
+        final int[] SUPPORTED_VENDORS = {
+            0x045e, // Microsoft
+            0x0738, // Mad Catz
+            0x0e6f, // Unknown
+            0x0f0d, // Hori
+            0x1532, // Razer Wildcat
+            0x24c6, // PowerA
+        };
+
+        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
+            usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
+            usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
+            int vendor_id = usbDevice.getVendorId();
+            for (int supportedVid : SUPPORTED_VENDORS) {
+                if (vendor_id == supportedVid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected void handleUsbDeviceAttached(UsbDevice usbDevice) {
+        if (isHIDDeviceUSB(usbDevice)) {
+            connectHIDDeviceUSB(usbDevice);
+        }
+    }
+
+    protected void handleUsbDeviceDetached(UsbDevice usbDevice) {
+        HIDDeviceUSB device = mUSBDevices.get(usbDevice);
+        if (device == null)
+            return;
+
+        int id = device.getId();
+        mUSBDevices.remove(usbDevice);
+        mDevicesById.remove(id);
+        device.shutdown();
+        HIDDeviceDisconnected(id);
+    }
+
+    protected void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
+        HIDDeviceUSB device = mUSBDevices.get(usbDevice);
+        if (device == null)
+            return;
+
+        boolean opened = false;
+        if (permission_granted) {
+            opened = device.open();
+        }
+        HIDDeviceOpenResult(device.getId(), opened);
+    }
+
+    protected void connectHIDDeviceUSB(UsbDevice usbDevice) {
+        synchronized (this) {
+            for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); interface_number++) {
+                if (isHIDDeviceInterface(usbDevice, interface_number)) {
+                    HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_number);
+                    int id = device.getId();
+                    mUSBDevices.put(usbDevice, device);
+                    mDevicesById.put(id, device);
+                    HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), interface_number);
+                    break;
+                }
+            }
+        }
+    }
+
+    protected void initializeBluetooth() {
+        Log.d(TAG, "Initializing Bluetooth");
+
+        // Find bonded bluetooth controllers and create SteamControllers for them
+        mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (mBluetoothManager == null) {
+            // This device doesn't support Bluetooth.
+            return;
+        }
+
+        BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
+        if (btAdapter == null) {
+            // This device has Bluetooth support in the codebase, but has no available adapters.
+            return;
+        }
+
+        // Get our bonded devices.
+        for (BluetoothDevice device : btAdapter.getBondedDevices()) {
+
+            Log.d(TAG, "Bluetooth device available: " + device);
+            if (isSteamController(device)) {
+                connectBluetoothDevice(device);
+            }
+
+        }
+
+        // NOTE: These don't work on Chromebooks, to my undying dismay.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+        mContext.registerReceiver(mBluetoothBroadcast, filter);
+
+        if (mIsChromebook) {
+            mHandler = new Handler(Looper.getMainLooper());
+            mLastBluetoothDevices = new ArrayList<>();
+
+            // final HIDDeviceManager finalThis = this;
+            // mHandler.postDelayed(new Runnable() {
+            //     @Override
+            //     public void run() {
+            //         finalThis.chromebookConnectionHandler();
+            //     }
+            // }, 5000);
+        }
+    }
+
+    protected void shutdownBluetooth() {
+        mContext.unregisterReceiver(mBluetoothBroadcast);
+    }
+
+    // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
+    // This function provides a sort of dummy version of that, watching for changes in the
+    // connected devices and attempting to add controllers as things change.
+    public void chromebookConnectionHandler() {
+        if (!mIsChromebook) {
+            return;
+        }
+
+        ArrayList<BluetoothDevice> disconnected = new ArrayList<>();
+        ArrayList<BluetoothDevice> connected = new ArrayList<>();
+
+        List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
+
+        for (BluetoothDevice bluetoothDevice : currentConnected) {
+            if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
+                connected.add(bluetoothDevice);
+            }
+        }
+        for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
+            if (!currentConnected.contains(bluetoothDevice)) {
+                disconnected.add(bluetoothDevice);
+            }
+        }
+
+        mLastBluetoothDevices = currentConnected;
+
+        for (BluetoothDevice bluetoothDevice : disconnected) {
+            disconnectBluetoothDevice(bluetoothDevice);
+        }
+        for (BluetoothDevice bluetoothDevice : connected) {
+            connectBluetoothDevice(bluetoothDevice);
+        }
+
+        final HIDDeviceManager finalThis = this;
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finalThis.chromebookConnectionHandler();
+            }
+        }, 10000);
+    }
+
+    public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
+        Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
+        synchronized (this) {
+            if (mBluetoothDevices.containsKey(bluetoothDevice)) {
+                Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
+
+                HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
+                device.reconnect();
+
+                return false;
+            }
+            HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
+            int id = device.getId();
+            mBluetoothDevices.put(bluetoothDevice, device);
+            mDevicesById.put(id, device);
+
+            // The Steam Controller will mark itself connected once initialization is complete
+        }
+        return true;
+    }
+
+    public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
+        synchronized (this) {
+            HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
+            if (device == null)
+                return;
+
+            int id = device.getId();
+            mBluetoothDevices.remove(bluetoothDevice);
+            mDevicesById.remove(id);
+            device.shutdown();
+            HIDDeviceDisconnected(id);
+        }
+    }
+
+    public boolean isSteamController(BluetoothDevice bluetoothDevice) {
+        // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.
+        if (bluetoothDevice == null) {
+            return false;
+        }
+
+        // If the device has no local name, we really don't want to try an equality check against it.
+        if (bluetoothDevice.getName() == null) {
+            return false;
+        }
+
+        return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
+    }
+
+    public void close() {
+        shutdownUSB();
+        shutdownBluetooth();
+        synchronized (this) {
+            for (HIDDevice device : mDevicesById.values()) {
+                device.shutdown();
+            }
+            mDevicesById.clear();
+            mBluetoothDevices.clear();
+            HIDDeviceReleaseCallback();
+        }
+    }
+
+    public void setFrozen(boolean frozen) {
+        synchronized (this) {
+            for (HIDDevice device : mDevicesById.values()) {
+                device.setFrozen(frozen);
+            }
+        }        
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private HIDDevice getDevice(int id) {
+        synchronized (this) {
+            HIDDevice result = mDevicesById.get(id);
+            if (result == null) {
+                Log.v(TAG, "No device for id: " + id);
+                Log.v(TAG, "Available devices: " + mDevicesById.keySet());
+            }
+            return result;
+        }
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////// JNI interface functions
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    boolean openDevice(int deviceID) {
+        // Look to see if this is a USB device and we have permission to access it
+        for (HIDDeviceUSB device : mUSBDevices.values()) {
+            if (deviceID == device.getId()) {
+                UsbDevice usbDevice = device.getDevice();
+                if (!mUsbManager.hasPermission(usbDevice)) {
+                    HIDDeviceOpenPending(deviceID);
+                    try {
+                        mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0));
+                    } catch (Exception e) {
+                        Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
+                        HIDDeviceOpenResult(deviceID, false);
+                    }
+                    return false;
+                }
+                break;
+            }
+        }
+
+        try {
+            Log.v(TAG, "openDevice deviceID=" + deviceID);
+            HIDDevice device;
+            device = getDevice(deviceID);
+            if (device == null) {
+                HIDDeviceDisconnected(deviceID);
+                return false;
+            }
+
+            return device.open();
+        } catch (Exception e) {
+            Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+        }
+        return false;
+    }
+
+    int sendOutputReport(int deviceID, byte[] report) {
+        try {
+            Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
+            HIDDevice device;
+            device = getDevice(deviceID);
+            if (device == null) {
+                HIDDeviceDisconnected(deviceID);
+                return -1;
+            }
+
+            return device.sendOutputReport(report);
+        } catch (Exception e) {
+            Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+        }
+        return -1;
+    }
+
+    int sendFeatureReport(int deviceID, byte[] report) {
+        try {
+            Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
+            HIDDevice device;
+            device = getDevice(deviceID);
+            if (device == null) {
+                HIDDeviceDisconnected(deviceID);
+                return -1;
+            }
+
+            return device.sendFeatureReport(report);
+        } catch (Exception e) {
+            Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+        }
+        return -1;
+    }
+
+    boolean getFeatureReport(int deviceID, byte[] report) {
+        try {
+            Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
+            HIDDevice device;
+            device = getDevice(deviceID);
+            if (device == null) {
+                HIDDeviceDisconnected(deviceID);
+                return false;
+            }
+
+            return device.getFeatureReport(report);
+        } catch (Exception e) {
+            Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+        }
+        return false;
+    }
+
+    void closeDevice(int deviceID) {
+        try {
+            Log.v(TAG, "closeDevice deviceID=" + deviceID);
+            HIDDevice device;
+            device = getDevice(deviceID);
+            if (device == null) {
+                HIDDeviceDisconnected(deviceID);
+                return;
+            }
+
+            device.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+        }
+    }
+
+
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+    /////////////// Native methods
+    //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private native void HIDDeviceRegisterCallback(Object callbackHandler);
+    private native void HIDDeviceReleaseCallback();
+
+    native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number);
+    native void HIDDeviceOpenPending(int deviceID);
+    native void HIDDeviceOpenResult(int deviceID, boolean opened);
+    native void HIDDeviceDisconnected(int deviceID);
+
+    native void HIDDeviceInputReport(int deviceID, byte[] report);
+    native void HIDDeviceFeatureReport(int deviceID, byte[] report);
+}

+ 298 - 0
android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java

@@ -0,0 +1,298 @@
+package org.libsdl.app;
+
+import android.hardware.usb.*;
+import android.os.Build;
+import android.util.Log;
+import java.util.Arrays;
+
+class HIDDeviceUSB implements HIDDevice {
+
+    private static final String TAG = "hidapi";
+
+    protected HIDDeviceManager mManager;
+    protected UsbDevice mDevice;
+    protected int mInterface;
+    protected int mDeviceId;
+    protected UsbDeviceConnection mConnection;
+    protected UsbEndpoint mInputEndpoint;
+    protected UsbEndpoint mOutputEndpoint;
+    protected InputThread mInputThread;
+    protected boolean mRunning;
+    protected boolean mFrozen;
+
+    public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_number) {
+        mManager = manager;
+        mDevice = usbDevice;
+        mInterface = interface_number;
+        mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
+        mRunning = false;
+    }
+
+    public String getIdentifier() {
+        return String.format("%s/%x/%x", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId());
+    }
+
+    @Override
+    public int getId() {
+        return mDeviceId;
+    }
+
+    @Override
+    public int getVendorId() {
+        return mDevice.getVendorId();
+    }
+
+    @Override
+    public int getProductId() {
+        return mDevice.getProductId();
+    }
+
+    @Override
+    public String getSerialNumber() {
+        String result = null;
+        if (Build.VERSION.SDK_INT >= 21) {
+            result = mDevice.getSerialNumber();
+        }
+        if (result == null) {
+            result = "";
+        }
+        return result;
+    }
+
+    @Override
+    public int getVersion() {
+        return 0;
+    }
+
+    @Override
+    public String getManufacturerName() {
+        String result = null;
+        if (Build.VERSION.SDK_INT >= 21) {
+            result = mDevice.getManufacturerName();
+        }
+        if (result == null) {
+            result = String.format("%x", getVendorId());
+        }
+        return result;
+    }
+
+    @Override
+    public String getProductName() {
+        String result = null;
+        if (Build.VERSION.SDK_INT >= 21) {
+            result = mDevice.getProductName();
+        }
+        if (result == null) {
+            result = String.format("%x", getProductId());
+        }
+        return result;
+    }
+
+    public UsbDevice getDevice() {
+        return mDevice;
+    }
+
+    public String getDeviceName() {
+        return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
+    }
+
+    @Override
+    public boolean open() {
+        mConnection = mManager.getUSBManager().openDevice(mDevice);
+        if (mConnection == null) {
+            Log.w(TAG, "Unable to open USB device " + getDeviceName());
+            return false;
+        }
+
+        // Force claim all interfaces
+        for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
+            UsbInterface iface = mDevice.getInterface(i);
+
+            if (!mConnection.claimInterface(iface, true)) {
+                Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
+                close();
+                return false;
+            }
+        }
+
+        // Find the endpoints
+        UsbInterface iface = mDevice.getInterface(mInterface);
+        for (int j = 0; j < iface.getEndpointCount(); j++) {
+            UsbEndpoint endpt = iface.getEndpoint(j);
+            switch (endpt.getDirection()) {
+            case UsbConstants.USB_DIR_IN:
+                if (mInputEndpoint == null) {
+                    mInputEndpoint = endpt;
+                }
+                break;
+            case UsbConstants.USB_DIR_OUT:
+                if (mOutputEndpoint == null) {
+                    mOutputEndpoint = endpt;
+                }
+                break;
+            }
+        }
+
+        // Make sure the required endpoints were present
+        if (mInputEndpoint == null || mOutputEndpoint == null) {
+            Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
+            close();
+            return false;
+        }
+
+        // Start listening for input
+        mRunning = true;
+        mInputThread = new InputThread();
+        mInputThread.start();
+
+        return true;
+    }
+
+    @Override
+    public int sendFeatureReport(byte[] report) {
+        int res = -1;
+        int offset = 0;
+        int length = report.length;
+        boolean skipped_report_id = false;
+        byte report_number = report[0];
+
+        if (report_number == 0x0) {
+            ++offset;
+            --length;
+            skipped_report_id = true;
+        }
+
+        res = mConnection.controlTransfer(
+            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
+            0x09/*HID set_report*/,
+            (3/*HID feature*/ << 8) | report_number,
+            0,
+            report, offset, length,
+            1000/*timeout millis*/);
+
+        if (res < 0) {
+            Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
+            return -1;
+        }
+
+        if (skipped_report_id) {
+            ++length;
+        }
+        return length;
+    }
+
+    @Override
+    public int sendOutputReport(byte[] report) {
+        int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
+        if (r != report.length) {
+            Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
+        }
+        return r;
+    }
+
+    @Override
+    public boolean getFeatureReport(byte[] report) {
+        int res = -1;
+        int offset = 0;
+        int length = report.length;
+        boolean skipped_report_id = false;
+        byte report_number = report[0];
+
+        if (report_number == 0x0) {
+            /* Offset the return buffer by 1, so that the report ID
+               will remain in byte 0. */
+            ++offset;
+            --length;
+            skipped_report_id = true;
+        }
+
+        res = mConnection.controlTransfer(
+            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
+            0x01/*HID get_report*/,
+            (3/*HID feature*/ << 8) | report_number,
+            0,
+            report, offset, length,
+            1000/*timeout millis*/);
+
+        if (res < 0) {
+            Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
+            return false;
+        }
+
+        if (skipped_report_id) {
+            ++res;
+            ++length;
+        }
+
+        byte[] data;
+        if (res == length) {
+            data = report;
+        } else {
+            data = Arrays.copyOfRange(report, 0, res);
+        }
+        mManager.HIDDeviceFeatureReport(mDeviceId, data);
+
+        return true;
+    }
+
+    @Override
+    public void close() {
+        mRunning = false;
+        if (mInputThread != null) {
+            while (mInputThread.isAlive()) {
+                mInputThread.interrupt();
+                try {
+                    mInputThread.join();
+                } catch (InterruptedException e) {
+                    // Keep trying until we're done
+                }
+            }
+            mInputThread = null;
+        }
+        if (mConnection != null) {
+            for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
+                UsbInterface iface = mDevice.getInterface(i);
+                mConnection.releaseInterface(iface);
+            }
+            mConnection.close();
+            mConnection = null;
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        close();
+        mManager = null;
+    }
+
+    @Override
+    public void setFrozen(boolean frozen) {
+        mFrozen = frozen;
+    }
+
+    protected class InputThread extends Thread {
+        @Override
+        public void run() {
+            int packetSize = mInputEndpoint.getMaxPacketSize();
+            byte[] packet = new byte[packetSize];
+            while (mRunning) {
+                int r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
+                if (r < 0) {
+                    // Could be a timeout or an I/O error
+                }
+                if (r > 0) {
+                    byte[] data;
+                    if (r == packetSize) {
+                        data = packet;
+                    } else {
+                        data = Arrays.copyOfRange(packet, 0, r);
+                    }
+
+                    if (!mFrozen) {
+                        mManager.HIDDeviceInputReport(mDeviceId, data);
+                    }
+                }
+            }
+        }
+    }
+}

+ 17 - 5
android-project/app/src/main/java/org/libsdl/app/SDLActivity.java

@@ -80,7 +80,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
     protected static Hashtable<Integer, Object> mCursors;
     protected static int mLastCursorID;
     protected static SDLGenericMotionListener_API12 mMotionListener;
-
+    protected static HIDDeviceManager mHIDDeviceManager;
 
     // This is what SDL runs in. It invokes SDL_main(), eventually
     protected static Thread mSDLThread;
@@ -241,6 +241,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
             mClipboardHandler = new SDLClipboardHandler_Old();
         }
 
+        mHIDDeviceManager = new HIDDeviceManager(this);
+
         // Set up the surface
         mSurface = new SDLSurface(getApplication());
 
@@ -276,6 +278,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
            return;
         }
 
+        if (mHIDDeviceManager != null) {
+            mHIDDeviceManager.setFrozen(true);
+        }
+
         SDLActivity.handleNativeState();
     }
 
@@ -290,6 +296,10 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
            return;
         }
 
+        if (mHIDDeviceManager != null) {
+            mHIDDeviceManager.setFrozen(false);
+        }
+
         SDLActivity.handleNativeState();
     }
 
@@ -330,6 +340,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
     protected void onDestroy() {
         Log.v(TAG, "onDestroy()");
 
+        if (mHIDDeviceManager != null) {
+            mHIDDeviceManager.close();
+            mHIDDeviceManager = null;
+        }
+
         if (SDLActivity.mBrokenLibraries) {
            super.onDestroy();
            // Reset everything in case the user re opens the app
@@ -466,10 +481,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
     /* The native thread has finished */
     public static void handleNativeExit() {
         SDLActivity.mSDLThread = null;
-
-        // Make sure we currently have a singleton before we try to call it.
-        if (mSingleton != null)
-            mSingleton.finish();
+        mSingleton.finish();
     }
 
 

+ 109 - 12
configure

@@ -868,6 +868,7 @@ enable_pthread_sem
 enable_directx
 enable_wasapi
 enable_sdl_dlopen
+enable_hidapi
 enable_clock_gettime
 enable_rpath
 enable_render_d3d
@@ -1623,6 +1624,8 @@ Optional Features:
   --enable-directx        use DirectX for Windows audio/video [[default=yes]]
   --enable-wasapi         use the Windows WASAPI audio driver [[default=yes]]
   --enable-sdl-dlopen     use dlopen for shared object loading [[default=yes]]
+  --enable-hidapi         use HIDAPI for low level joystick drivers
+                          [[default=no]]
   --enable-clock_gettime  use clock_gettime() instead of gettimeofday() on
                           UNIX [[default=yes]]
   --enable-rpath          use an rpath when linking SDL [[default=yes]]
@@ -16773,7 +16776,7 @@ $as_echo "#define HAVE_SA_SIGACTION 1" >>confdefs.h
 fi
 
 
-	    for ac_header in libunwind.h
+        for ac_header in libunwind.h
 do :
   ac_fn_c_check_header_mongrel "$LINENO" "libunwind.h" "ac_cv_header_libunwind_h" "$ac_includes_default"
 if test "x$ac_cv_header_libunwind_h" = xyes; then :
@@ -18377,7 +18380,7 @@ fi
         { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PulseAudio $PULSEAUDIO_REQUIRED_VERSION support" >&5
 $as_echo_n "checking for PulseAudio $PULSEAUDIO_REQUIRED_VERSION support... " >&6; }
         if test x$PKG_CONFIG != xno; then
-        if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSEAUDIO_REQUIRED_VERSION libpulse-simple; then
+            if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSEAUDIO_REQUIRED_VERSION libpulse-simple; then
                 PULSEAUDIO_CFLAGS=`$PKG_CONFIG --cflags libpulse-simple`
                 PULSEAUDIO_LIBS=`$PKG_CONFIG --libs libpulse-simple`
                 audio_pulseaudio=yes
@@ -20463,7 +20466,7 @@ $as_echo_n "checking for XGenericEvent... " >&6; }
             cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
-              	#include <X11/Xlib.h>
+                #include <X11/Xlib.h>
 
 int
 main ()
@@ -20807,13 +20810,13 @@ $as_echo "#define SDL_VIDEO_DRIVER_X11_XINPUT2 1" >>confdefs.h
 
                 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for xinput2 multitouch" >&5
 $as_echo_n "checking for xinput2 multitouch... " >&6; }
-            	have_xinput2_multitouch=no
-            	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+                have_xinput2_multitouch=no
+                cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
-              		#include <X11/Xlib.h>
-             		#include <X11/Xproto.h>
-			#include <X11/extensions/XInput2.h>
+                    #include <X11/Xlib.h>
+                    #include <X11/Xproto.h>
+                    #include <X11/extensions/XInput2.h>
 
 int
 main ()
@@ -20828,14 +20831,14 @@ XITouchClassInfo *t;
 _ACEOF
 if ac_fn_c_try_compile "$LINENO"; then :
 
-            	have_xinput2_multitouch=yes
-            	$as_echo "#define SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1" >>confdefs.h
+                    have_xinput2_multitouch=yes
+                    $as_echo "#define SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH 1" >>confdefs.h
 
-                SUMMARY_video_x11="${SUMMARY_video_x11} xinput2_multitouch"
+                    SUMMARY_video_x11="${SUMMARY_video_x11} xinput2_multitouch"
 
 fi
 rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-            	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $have_xinput2_multitouch" >&5
+                { $as_echo "$as_me:${as_lineno-$LINENO}: result: $have_xinput2_multitouch" >&5
 $as_echo "$have_xinput2_multitouch" >&6; }
             fi
             # Check whether --enable-video-x11-xrandr was given.
@@ -23728,6 +23731,93 @@ $as_echo "#define SDL_JOYSTICK_USBHID 1" >>confdefs.h
     esac
 }
 
+CheckHIDAPI()
+{
+    # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers,
+    # so we'll just use libusb when it's available.
+    #
+    # Except that libusb requires root permissions to open devices, so that's not generally useful, and we'll disable this by default.
+    # Check whether --enable-hidapi was given.
+if test "${enable_hidapi+set}" = set; then :
+  enableval=$enable_hidapi;
+else
+  enable_hidapi=no
+fi
+
+    if test x$enable_joystick = xyes -a x$enable_hidapi = xyes; then
+        hidapi_support=no
+        # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_PKG_CONFIG" && ac_cv_path_PKG_CONFIG="no"
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+        if test x$PKG_CONFIG != xno; then
+            LIBUSB_CFLAGS=`$PKG_CONFIG --cflags libusb-1.0`
+            LIBUSB_LDFLAGS=`$PKG_CONFIG --libs libusb-1.0`
+            save_CFLAGS="$CFLAGS"
+            CFLAGS="$save_CFLAGS $LIBUSB_CFLAGS"
+            ac_fn_c_check_header_mongrel "$LINENO" "libusb.h" "ac_cv_header_libusb_h" "$ac_includes_default"
+if test "x$ac_cv_header_libusb_h" = xyes; then :
+  have_libusb_h=yes
+fi
+
+
+            CFLAGS="$save_CFLAGS"
+        fi
+        if test x$have_libusb_h = xyes; then
+            hidapi_support=yes
+
+$as_echo "#define SDL_JOYSTICK_HIDAPI 1" >>confdefs.h
+
+            EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi"
+            SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c"
+            SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c"
+            EXTRA_CFLAGS="$EXTRA_CFLAGS $LIBUSB_CFLAGS"
+            EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LDFLAGS"
+        fi
+        { $as_echo "$as_me:${as_lineno-$LINENO}: checking for hidapi support" >&5
+$as_echo_n "checking for hidapi support... " >&6; }
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hidapi_support" >&5
+$as_echo "$hidapi_support" >&6; }
+    fi
+}
+
 CheckClockGettime()
 {
     # Check whether --enable-clock_gettime was given.
@@ -23939,6 +24029,7 @@ case "$host" in
         esac
         CheckTslib
         CheckUSBHID
+        CheckHIDAPI
         CheckPTHREAD
         CheckClockGettime
         CheckLinuxVersion
@@ -24514,7 +24605,13 @@ $as_echo "#define SDL_AUDIO_DRIVER_COREAUDIO 1" >>confdefs.h
 
 $as_echo "#define SDL_JOYSTICK_IOKIT 1" >>confdefs.h
 
+
+$as_echo "#define SDL_JOYSTICK_HIDAPI 1" >>confdefs.h
+
             SOURCES="$SOURCES $srcdir/src/joystick/darwin/*.c"
+            SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c"
+            SOURCES="$SOURCES $srcdir/src/hidapi/mac/hid.c"
+            EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi"
             have_joystick=yes
         fi
         # Set up files for the haptic library

+ 55 - 15
configure.in

@@ -285,7 +285,7 @@ if test x$enable_libc = xyes; then
 
     AC_CHECK_MEMBER(struct sigaction.sa_sigaction,[AC_DEFINE([HAVE_SA_SIGACTION], 1, [ ])], ,[#include <signal.h>])
 
-	dnl Check for additional non-standard headers
+    dnl Check for additional non-standard headers
     AC_CHECK_HEADERS(libunwind.h)
 fi
 
@@ -963,7 +963,7 @@ AC_HELP_STRING([--enable-pulseaudio], [use PulseAudio [[default=yes]]]),
         AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
         AC_MSG_CHECKING(for PulseAudio $PULSEAUDIO_REQUIRED_VERSION support)
         if test x$PKG_CONFIG != xno; then
-        if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSEAUDIO_REQUIRED_VERSION libpulse-simple; then
+            if $PKG_CONFIG --atleast-pkgconfig-version 0.7 && $PKG_CONFIG --atleast-version $PULSEAUDIO_REQUIRED_VERSION libpulse-simple; then
                 PULSEAUDIO_CFLAGS=`$PKG_CONFIG --cflags libpulse-simple`
                 PULSEAUDIO_LIBS=`$PKG_CONFIG --libs libpulse-simple`
                 audio_pulseaudio=yes
@@ -1748,7 +1748,7 @@ AC_HELP_STRING([--enable-x11-shared], [dynamically load X11 support [[default=ma
             AC_MSG_CHECKING([for XGenericEvent])
             have_XGenericEvent=no
             AC_TRY_COMPILE([
-              	#include <X11/Xlib.h>
+                #include <X11/Xlib.h>
             ],[
 Display *display;
 XEvent event;
@@ -1862,20 +1862,20 @@ AC_HELP_STRING([--enable-video-x11-xinput], [enable X11 XInput extension for man
                 SUMMARY_video_x11="${SUMMARY_video_x11} xinput2"
                 AC_DEFINE(SDL_VIDEO_DRIVER_X11_XINPUT2, 1, [ ])
                 AC_MSG_CHECKING(for xinput2 multitouch)
-            	have_xinput2_multitouch=no
-            	AC_TRY_COMPILE([
-              		#include <X11/Xlib.h>
-             		#include <X11/Xproto.h>
-			#include <X11/extensions/XInput2.h>
-            	],[
+                have_xinput2_multitouch=no
+                AC_TRY_COMPILE([
+                    #include <X11/Xlib.h>
+                    #include <X11/Xproto.h>
+                    #include <X11/extensions/XInput2.h>
+                    ],[
 int event_type = XI_TouchBegin;
 XITouchClassInfo *t;
-            	],[
-            	have_xinput2_multitouch=yes
-            	AC_DEFINE([SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH], 1, [])
-                SUMMARY_video_x11="${SUMMARY_video_x11} xinput2_multitouch"
-            	])
-            	AC_MSG_RESULT($have_xinput2_multitouch)
+                    ],[
+                    have_xinput2_multitouch=yes
+                    AC_DEFINE([SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH], 1, [])
+                    SUMMARY_video_x11="${SUMMARY_video_x11} xinput2_multitouch"
+                    ])
+                AC_MSG_RESULT($have_xinput2_multitouch)
             fi
             AC_ARG_ENABLE(video-x11-xrandr,
 AC_HELP_STRING([--enable-video-x11-xrandr], [enable X11 Xrandr extension for fullscreen [[default=yes]]]),
@@ -3265,6 +3265,41 @@ CheckUSBHID()
     esac
 }
 
+dnl Check for HIDAPI joystick drivers
+CheckHIDAPI()
+{
+    # The hidraw support doesn't catch Xbox, PS4 and Nintendo controllers,
+    # so we'll just use libusb when it's available.
+    #
+    # Except that libusb requires root permissions to open devices, so that's not generally useful, and we'll disable this by default.
+    AC_ARG_ENABLE(hidapi,
+AC_HELP_STRING([--enable-hidapi], [use HIDAPI for low level joystick drivers [[default=no]]]),
+                  , enable_hidapi=no)
+    if test x$enable_joystick = xyes -a x$enable_hidapi = xyes; then
+        hidapi_support=no
+        AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+        if test x$PKG_CONFIG != xno; then
+            LIBUSB_CFLAGS=`$PKG_CONFIG --cflags libusb-1.0`
+            LIBUSB_LDFLAGS=`$PKG_CONFIG --libs libusb-1.0`
+            save_CFLAGS="$CFLAGS"
+            CFLAGS="$save_CFLAGS $LIBUSB_CFLAGS"
+            AC_CHECK_HEADER(libusb.h, have_libusb_h=yes)
+            CFLAGS="$save_CFLAGS"
+        fi
+        if test x$have_libusb_h = xyes; then
+            hidapi_support=yes
+            AC_DEFINE(SDL_JOYSTICK_HIDAPI, 1, [ ])
+            EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi"
+            SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c"
+            SOURCES="$SOURCES $srcdir/src/hidapi/libusb/hid.c"
+            EXTRA_CFLAGS="$EXTRA_CFLAGS $LIBUSB_CFLAGS"
+            EXTRA_LDFLAGS="$EXTRA_LDFLAGS $LIBUSB_LDFLAGS"
+        fi
+        AC_MSG_CHECKING(for hidapi support)
+        AC_MSG_RESULT($hidapi_support)
+    fi
+}
+
 dnl Check for clock_gettime()
 CheckClockGettime()
 {
@@ -3386,6 +3421,7 @@ case "$host" in
         esac
         CheckTslib
         CheckUSBHID
+        CheckHIDAPI
         CheckPTHREAD
         CheckClockGettime
         CheckLinuxVersion
@@ -3811,7 +3847,11 @@ AC_HELP_STRING([--enable-render-d3d], [enable the Direct3D render driver [[defau
         # Set up files for the joystick library
         if test x$enable_joystick = xyes; then
             AC_DEFINE(SDL_JOYSTICK_IOKIT, 1, [ ])
+            AC_DEFINE(SDL_JOYSTICK_HIDAPI, 1, [ ])
             SOURCES="$SOURCES $srcdir/src/joystick/darwin/*.c"
+            SOURCES="$SOURCES $srcdir/src/joystick/hidapi/*.c"
+            SOURCES="$SOURCES $srcdir/src/hidapi/mac/hid.c"
+            EXTRA_CFLAGS="$EXTRA_CFLAGS -I$srcdir/src/hidapi/hidapi"
             have_joystick=yes
         fi
         # Set up files for the haptic library

+ 1 - 0
include/SDL_config.h.in

@@ -279,6 +279,7 @@
 #undef SDL_JOYSTICK_WINMM
 #undef SDL_JOYSTICK_USBHID
 #undef SDL_JOYSTICK_USBHID_MACHINE_JOYSTICK_H
+#undef SDL_JOYSTICK_HIDAPI
 #undef SDL_JOYSTICK_EMSCRIPTEN
 #undef SDL_HAPTIC_DUMMY
 #undef SDL_HAPTIC_ANDROID

+ 1 - 0
include/SDL_config_android.h

@@ -134,6 +134,7 @@
 
 /* Enable various input drivers */
 #define SDL_JOYSTICK_ANDROID    1
+#define SDL_JOYSTICK_HIDAPI    1
 #define SDL_HAPTIC_ANDROID    1
 
 /* Enable various shared object loading systems */

+ 1 - 0
include/SDL_config_iphoneos.h

@@ -137,6 +137,7 @@
 
 /* Enable MFi joystick support */
 #define SDL_JOYSTICK_MFI 1
+#define SDL_JOYSTICK_HIDAPI 1
 
 /* Enable Unix style SO loading */
 #define SDL_LOADSO_DLOPEN 1

+ 1 - 0
include/SDL_config_macosx.h

@@ -137,6 +137,7 @@
 
 /* Enable various input drivers */
 #define SDL_JOYSTICK_IOKIT  1
+#define SDL_JOYSTICK_HIDAPI  1
 #define SDL_HAPTIC_IOKIT    1
 
 /* Enable various shared object loading systems */

+ 1 - 0
include/SDL_config_windows.h

@@ -190,6 +190,7 @@ typedef unsigned int uintptr_t;
 /* Enable various input drivers */
 #define SDL_JOYSTICK_DINPUT 1
 #define SDL_JOYSTICK_XINPUT 1
+#define SDL_JOYSTICK_HIDAPI 1
 #define SDL_HAPTIC_DINPUT   1
 #define SDL_HAPTIC_XINPUT   1
 

+ 13 - 0
include/SDL_gamecontroller.h

@@ -353,6 +353,19 @@ SDL_GameControllerGetBindForButton(SDL_GameController *gamecontroller,
 extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *gamecontroller,
                                                           SDL_GameControllerButton button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param gamecontroller The controller to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a controller previously opened with SDL_GameControllerOpen().
  */

+ 3 - 3
include/SDL_haptic.h

@@ -656,8 +656,8 @@ typedef struct SDL_HapticRamp
  * This struct is exclusively for the ::SDL_HAPTIC_LEFTRIGHT effect.
  *
  * The Left/Right effect is used to explicitly control the large and small
- * motors, commonly found in modern game controllers. One motor is high
- * frequency, the other is low frequency.
+ * motors, commonly found in modern game controllers. The small (right) motor
+ * is high frequency, and the large (left) motor is low frequency.
  *
  * \sa SDL_HAPTIC_LEFTRIGHT
  * \sa SDL_HapticEffect
@@ -668,7 +668,7 @@ typedef struct SDL_HapticLeftRight
     Uint16 type;            /**< ::SDL_HAPTIC_LEFTRIGHT */
 
     /* Replay */
-    Uint32 length;          /**< Duration of the effect. */
+    Uint32 length;          /**< Duration of the effect in milliseconds. */
 
     /* Rumble */
     Uint16 large_magnitude; /**< Control of the large controller motor. */

+ 78 - 0
include/SDL_hints.h

@@ -465,6 +465,84 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS "SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"
 
+/**
+ *  \brief  A variable controlling whether the HIDAPI joystick drivers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI drivers are not used
+ *    "1"       - HIDAPI drivers are used (the default)
+ *
+ *  This variable is the default for all drivers, but can be overridden by the hints for specific drivers below.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for PS4 controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Steam Controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for Nintendo Switch controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for XBox 360 controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOX360 "SDL_JOYSTICK_HIDAPI_XBOX360"
+
+/**
+ *  \brief  A variable controlling whether the HIDAPI driver for XBox One controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOXONE "SDL_JOYSTICK_HIDAPI_XBOXONE"
+
+/**
+ *  \brief  A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
+ *
+ *  The variable can be set to the following values:
+ *    "0"       - Do not scan for Steam Controllers
+ *    "1"       - Scan for Steam Controllers (the default)
+ *
+ *  The default value is "1".  This hint must be set before initializing the joystick subsystem.
+ */
+#define SDL_HINT_ENABLE_STEAM_CONTROLLERS "SDL_ENABLE_STEAM_CONTROLLERS"
+
+
 /**
  *  \brief If set to "0" then never set the top most bit on a SDL Window, even if the video mode expects it.
  *      This is a debugging aid for developers and not expected to be used by end users. The default is "1"

+ 17 - 4
include/SDL_joystick.h

@@ -97,10 +97,10 @@ typedef enum
 typedef enum
 {
     SDL_JOYSTICK_POWER_UNKNOWN = -1,
-    SDL_JOYSTICK_POWER_EMPTY,
-    SDL_JOYSTICK_POWER_LOW,
-    SDL_JOYSTICK_POWER_MEDIUM,
-    SDL_JOYSTICK_POWER_FULL,
+    SDL_JOYSTICK_POWER_EMPTY,   /* <= 5% */
+    SDL_JOYSTICK_POWER_LOW,     /* <= 20% */
+    SDL_JOYSTICK_POWER_MEDIUM,  /* <= 70% */
+    SDL_JOYSTICK_POWER_FULL,    /* <= 100% */
     SDL_JOYSTICK_POWER_WIRED,
     SDL_JOYSTICK_POWER_MAX
 } SDL_JoystickPowerLevel;
@@ -361,6 +361,19 @@ extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick * joystick,
 extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
                                                     int button);
 
+/**
+ *  Trigger a rumble effect
+ *  Each call to this function cancels any previous rumble effect, and calling it with 0 intensity stops any rumbling.
+ *
+ *  \param joystick The joystick to vibrate
+ *  \param low_frequency_rumble The intensity of the low frequency (left) rumble motor, from 0 to 0xFFFF
+ *  \param high_frequency_rumble The intensity of the high frequency (right) rumble motor, from 0 to 0xFFFF
+ *  \param duration_ms The duration of the rumble effect, in milliseconds
+ *
+ *  \return 0, or -1 if rumble isn't supported on this joystick
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
 /**
  *  Close a joystick previously opened with SDL_JoystickOpen().
  */

+ 1 - 0
include/SDL_stdinc.h

@@ -450,6 +450,7 @@ extern DECLSPEC void *SDLCALL SDL_memcpy(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_
 extern DECLSPEC void *SDLCALL SDL_memmove(SDL_OUT_BYTECAP(len) void *dst, SDL_IN_BYTECAP(len) const void *src, size_t len);
 extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len);
 
+extern DECLSPEC wchar_t *SDLCALL SDL_wcsdup(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslen(const wchar_t *wstr);
 extern DECLSPEC size_t SDLCALL SDL_wcslcpy(SDL_OUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);
 extern DECLSPEC size_t SDLCALL SDL_wcslcat(SDL_INOUT_Z_CAP(maxlen) wchar_t *dst, const wchar_t *src, size_t maxlen);

+ 3 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -677,3 +677,6 @@
 #define SDL_AndroidBackButton SDL_AndroidBackButton_REAL
 #define SDL_exp SDL_exp_REAL
 #define SDL_expf SDL_expf_REAL
+#define SDL_wcsdup SDL_wcsdup_REAL
+#define SDL_GameControllerRumble SDL_GameControllerRumble_REAL
+#define SDL_JoystickRumble SDL_JoystickRumble_REAL

+ 3 - 2
src/dynapi/SDL_dynapi_procs.h

@@ -715,9 +715,10 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_HasAVX512F,(void),(),return)
 #ifdef __ANDROID__
 SDL_DYNAPI_PROC(SDL_bool,SDL_IsChromebook,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_IsDeXMode,(void),(),return)
-#endif
-#ifdef __ANDROID__
 SDL_DYNAPI_PROC(void,SDL_AndroidBackButton,(void),(),return)
 #endif
 SDL_DYNAPI_PROC(double,SDL_exp,(double a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_expf,(float a),(a),return)
+SDL_DYNAPI_PROC(wchar_t*,SDL_wcsdup,(const wchar_t *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_GameControllerRumble,(SDL_GameController *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(int,SDL_JoystickRumble,(SDL_Joystick *a, Uint16 b, Uint16 c, Uint32 d),(a,b,c,d),return)

+ 21 - 8
src/joystick/SDL_gamecontroller.c

@@ -102,6 +102,7 @@ typedef struct _ControllerMapping_t
 static SDL_JoystickGUID s_zeroGUID;
 static ControllerMapping_t *s_pSupportedControllers = NULL;
 static ControllerMapping_t *s_pDefaultMapping = NULL;
+static ControllerMapping_t *s_pHIDAPIMapping = NULL;
 static ControllerMapping_t *s_pXInputMapping = NULL;
 
 /* The SDL game controller structure */
@@ -430,6 +431,10 @@ static ControllerMapping_t *SDL_PrivateGetControllerMappingForGUID(SDL_JoystickG
         }
         pSupportedController = pSupportedController->next;
     }
+    if (guid->data[14] == 'h') {
+        /* This is a HIDAPI device */
+        return s_pHIDAPIMapping;
+    }
 #if SDL_JOYSTICK_XINPUT
     if (guid->data[14] == 'x') {
         /* This is an XInput device */
@@ -1130,6 +1135,7 @@ SDL_PrivateGameControllerAddMapping(const char *mappingString, SDL_ControllerMap
     char *pchGUID;
     SDL_JoystickGUID jGUID;
     SDL_bool is_default_mapping = SDL_FALSE;
+    SDL_bool is_hidapi_mapping = SDL_FALSE;
     SDL_bool is_xinput_mapping = SDL_FALSE;
     SDL_bool existing = SDL_FALSE;
     ControllerMapping_t *pControllerMapping;
@@ -1144,6 +1150,8 @@ SDL_PrivateGameControllerAddMapping(const char *mappingString, SDL_ControllerMap
     }
     if (!SDL_strcasecmp(pchGUID, "default")) {
         is_default_mapping = SDL_TRUE;
+    } else if (!SDL_strcasecmp(pchGUID, "hidapi")) {
+        is_hidapi_mapping = SDL_TRUE;
     } else if (!SDL_strcasecmp(pchGUID, "xinput")) {
         is_xinput_mapping = SDL_TRUE;
     }
@@ -1160,6 +1168,8 @@ SDL_PrivateGameControllerAddMapping(const char *mappingString, SDL_ControllerMap
     } else {
         if (is_default_mapping) {
             s_pDefaultMapping = pControllerMapping;
+        } else if (is_hidapi_mapping) {
+            s_pHIDAPIMapping = pControllerMapping;
         } else if (is_xinput_mapping) {
             s_pXInputMapping = pControllerMapping;
         }
@@ -1458,7 +1468,6 @@ SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid)
     }
 
     SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL);
-    vidpid = MAKE_VIDPID(vendor, product);
 
     if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", SDL_FALSE)) {
         /* We shouldn't ignore Steam's virtual gamepad since it's using the hints to filter out the real controllers so it can remap input for the virtual controller */
@@ -1476,6 +1485,8 @@ SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid)
         }
     }
 
+    vidpid = MAKE_VIDPID(vendor, product);
+
     if (SDL_allowed_controllers.num_entries > 0) {
         for (i = 0; i < SDL_allowed_controllers.num_entries; ++i) {
             if (vidpid == SDL_allowed_controllers.entries[i]) {
@@ -1503,22 +1514,18 @@ SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUID guid)
 SDL_GameController *
 SDL_GameControllerOpen(int device_index)
 {
+    SDL_JoystickID instance_id;
     SDL_GameController *gamecontroller;
     SDL_GameController *gamecontrollerlist;
     ControllerMapping_t *pSupportedController = NULL;
 
     SDL_LockJoysticks();
 
-    if ((device_index < 0) || (device_index >= SDL_NumJoysticks())) {
-        SDL_SetError("There are %d joysticks available", SDL_NumJoysticks());
-        SDL_UnlockJoysticks();
-        return (NULL);
-    }
-
     gamecontrollerlist = SDL_gamecontrollers;
     /* If the controller is already open, return it */
+    instance_id = SDL_JoystickGetDeviceInstanceID(device_index);
     while (gamecontrollerlist) {
-        if (SDL_SYS_GetInstanceIdOfDeviceIndex(device_index) == gamecontrollerlist->joystick->instance_id) {
+        if (instance_id == gamecontrollerlist->joystick->instance_id) {
                 gamecontroller = gamecontrollerlist;
                 ++gamecontroller->ref_count;
                 SDL_UnlockJoysticks();
@@ -1834,6 +1841,12 @@ SDL_GameControllerButtonBind SDL_GameControllerGetBindForButton(SDL_GameControll
 }
 
 
+int
+SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    return SDL_JoystickRumble(SDL_GameControllerGetJoystick(gamecontroller), low_frequency_rumble, high_frequency_rumble, duration_ms);
+}
+
 void
 SDL_GameControllerClose(SDL_GameController * gamecontroller)
 {

+ 2 - 0
src/joystick/SDL_gamecontrollerdb.h

@@ -555,6 +555,7 @@ static const char *s_ControllerMappings [] =
     "05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     "050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     "050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
+    "050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,", /* Extremely slow in Bluetooth mode on Android */
     "050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     "050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,",
     "050000004c050000cc090000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,",
@@ -575,6 +576,7 @@ static const char *s_ControllerMappings [] =
 #if defined(SDL_JOYSTICK_EMSCRIPTEN)
     "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
 #endif
+    "hidapi,*,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     NULL
 };
 

+ 302 - 74
src/joystick/SDL_joystick.c

@@ -23,6 +23,7 @@
 /* This is the joystick API for Simple DirectMedia Layer */
 
 #include "SDL.h"
+#include "SDL_atomic.h"
 #include "SDL_events.h"
 #include "SDL_sysjoystick.h"
 #include "SDL_assert.h"
@@ -33,11 +34,45 @@
 #endif
 #include "../video/SDL_sysvideo.h"
 
+/* This is included in only one place because it has a large static list of controllers */
+#include "controller_type.h"
 
+#ifdef __WIN32__
+/* Needed for checking for input remapping programs */
+#include "../../core/windows/SDL_windows.h"
+
+#undef UNICODE          /* We want ASCII functions */
+#include <tlhelp32.h>
+#endif
+
+static SDL_JoystickDriver *SDL_joystick_drivers[] = {
+#if defined(SDL_JOYSTICK_DINPUT) || defined(SDL_JOYSTICK_XINPUT)
+    &SDL_WINDOWS_JoystickDriver,
+#endif
+#ifdef SDL_JOYSTICK_LINUX
+    &SDL_LINUX_JoystickDriver,
+#endif
+#ifdef SDL_JOYSTICK_IOKIT
+    &SDL_DARWIN_JoystickDriver,
+#endif
+#if defined(__IPHONEOS__) || defined(__TVOS__)
+    &SDL_IOS_JoystickDriver,
+#endif
+#ifdef SDL_JOYSTICK_ANDROID
+    &SDL_ANDROID_JoystickDriver,
+#endif
+#ifdef SDL_JOYSTICK_HIDAPI
+    &SDL_HIDAPI_JoystickDriver,
+#endif
+#if defined(SDL_JOYSTICK_DUMMY) || defined(SDL_JOYSTICK_DISABLED)
+    &SDL_DUMMY_JoystickDriver
+#endif
+};
 static SDL_bool SDL_joystick_allows_background_events = SDL_FALSE;
 static SDL_Joystick *SDL_joysticks = NULL;
 static SDL_bool SDL_updating_joystick = SDL_FALSE;
 static SDL_mutex *SDL_joystick_lock = NULL; /* This needs to support recursive locks */
+static SDL_atomic_t SDL_next_joystick_instance_id;
 
 void
 SDL_LockJoysticks(void)
@@ -69,7 +104,7 @@ SDL_JoystickAllowBackgroundEventsChanged(void *userdata, const char *name, const
 int
 SDL_JoystickInit(void)
 {
-    int status;
+    int i, status;
 
     SDL_GameControllerInitMappings();
 
@@ -88,11 +123,13 @@ SDL_JoystickInit(void)
     }
 #endif /* !SDL_EVENTS_DISABLED */
 
-    status = SDL_SYS_JoystickInit();
-    if (status >= 0) {
-        status = 0;
+    status = -1;
+    for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+        if (SDL_joystick_drivers[i]->Init() >= 0) {
+            status = 0;
+        }
     }
-    return (status);
+    return status;
 }
 
 /*
@@ -101,7 +138,48 @@ SDL_JoystickInit(void)
 int
 SDL_NumJoysticks(void)
 {
-    return SDL_SYS_NumJoysticks();
+    int i, total_joysticks = 0;
+    SDL_LockJoysticks();
+    for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+        total_joysticks += SDL_joystick_drivers[i]->GetCount();
+    }
+    SDL_UnlockJoysticks();
+    return total_joysticks;
+}
+
+/*
+ * Return the next available joystick instance ID
+ * This may be called by drivers from multiple threads, unprotected by any locks
+ */
+SDL_JoystickID SDL_GetNextJoystickInstanceID()
+{
+    return SDL_AtomicIncRef(&SDL_next_joystick_instance_id);
+}
+
+/*
+ * Get the driver and device index for an API device index
+ * This should be called while the joystick lock is held, to prevent another thread from updating the list
+ */
+SDL_bool
+SDL_GetDriverAndJoystickIndex(int device_index, SDL_JoystickDriver **driver, int *driver_index)
+{
+    int i, num_joysticks, total_joysticks = 0;
+
+    if (device_index >= 0) {
+        for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+            num_joysticks = SDL_joystick_drivers[i]->GetCount();
+            if (device_index < num_joysticks) {
+                *driver = SDL_joystick_drivers[i];
+                *driver_index = device_index;
+                return SDL_TRUE;
+            }
+            device_index -= num_joysticks;
+            total_joysticks += num_joysticks;
+        }
+    }
+
+    SDL_SetError("There are %d joysticks available", total_joysticks);
+    return SDL_FALSE;
 }
 
 /*
@@ -127,11 +205,17 @@ SDL_FixupJoystickName(const char *name)
 const char *
 SDL_JoystickNameForIndex(int device_index)
 {
-    if (device_index < 0 || device_index >= SDL_NumJoysticks()) {
-        SDL_SetError("There are %d joysticks available", SDL_NumJoysticks());
-        return (NULL);
+    SDL_JoystickDriver *driver;
+    const char *name = NULL;
+
+    SDL_LockJoysticks();
+    if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
+        name = SDL_FixupJoystickName(driver->GetDeviceName(device_index));
     }
-    return SDL_FixupJoystickName(SDL_SYS_JoystickNameForDeviceIndex(device_index));
+    SDL_UnlockJoysticks();
+
+    /* FIXME: Really we should reference count this name so it doesn't go away after unlock */
+    return name;
 }
 
 /*
@@ -176,27 +260,30 @@ SDL_JoystickAxesCenteredAtZero(SDL_Joystick *joystick)
 SDL_Joystick *
 SDL_JoystickOpen(int device_index)
 {
+    SDL_JoystickDriver *driver;
+    SDL_JoystickID instance_id;
     SDL_Joystick *joystick;
     SDL_Joystick *joysticklist;
     const char *joystickname = NULL;
 
-    if ((device_index < 0) || (device_index >= SDL_NumJoysticks())) {
-        SDL_SetError("There are %d joysticks available", SDL_NumJoysticks());
-        return (NULL);
-    }
-
     SDL_LockJoysticks();
 
+    if (!SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
+        SDL_UnlockJoysticks();
+        return NULL;
+    }
+
     joysticklist = SDL_joysticks;
     /* If the joystick is already open, return it
      * it is important that we have a single joystick * for each instance id
      */
+    instance_id = driver->GetDeviceInstanceID(device_index);
     while (joysticklist) {
-        if (SDL_JoystickGetDeviceInstanceID(device_index) == joysticklist->instance_id) {
+        if (instance_id == joysticklist->instance_id) {
                 joystick = joysticklist;
                 ++joystick->ref_count;
                 SDL_UnlockJoysticks();
-                return (joystick);
+                return joystick;
         }
         joysticklist = joysticklist->next;
     }
@@ -208,18 +295,23 @@ SDL_JoystickOpen(int device_index)
         SDL_UnlockJoysticks();
         return NULL;
     }
+    joystick->driver = driver;
+    joystick->instance_id = instance_id;
 
-    if (SDL_SYS_JoystickOpen(joystick, device_index) < 0) {
+    if (driver->Open(joystick, device_index) < 0) {
         SDL_free(joystick);
         SDL_UnlockJoysticks();
         return NULL;
     }
 
-    joystickname = SDL_SYS_JoystickNameForDeviceIndex(device_index);
-    if (joystickname)
+    joystickname = driver->GetDeviceName(device_index);
+    if (joystickname) {
         joystick->name = SDL_strdup(joystickname);
-    else
+    } else {
         joystick->name = NULL;
+    }
+
+    joystick->guid = driver->GetDeviceGUID(device_index);
 
     if (joystick->naxes > 0) {
         joystick->axes = (SDL_JoystickAxisInfo *) SDL_calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo));
@@ -263,9 +355,9 @@ SDL_JoystickOpen(int device_index)
 
     SDL_UnlockJoysticks();
 
-    SDL_SYS_JoystickUpdate(joystick);
+    driver->Update(joystick);
 
-    return (joystick);
+    return joystick;
 }
 
 
@@ -294,9 +386,9 @@ int
 SDL_JoystickNumAxes(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
-    return (joystick->naxes);
+    return joystick->naxes;
 }
 
 /*
@@ -306,9 +398,9 @@ int
 SDL_JoystickNumHats(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
-    return (joystick->nhats);
+    return joystick->nhats;
 }
 
 /*
@@ -318,9 +410,9 @@ int
 SDL_JoystickNumBalls(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
-    return (joystick->nballs);
+    return joystick->nballs;
 }
 
 /*
@@ -330,9 +422,9 @@ int
 SDL_JoystickNumButtons(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
-    return (joystick->nbuttons);
+    return joystick->nbuttons;
 }
 
 /*
@@ -344,7 +436,7 @@ SDL_JoystickGetAxis(SDL_Joystick * joystick, int axis)
     Sint16 state;
 
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (0);
+        return 0;
     }
     if (axis < joystick->naxes) {
         state = joystick->axes[axis].value;
@@ -352,7 +444,7 @@ SDL_JoystickGetAxis(SDL_Joystick * joystick, int axis)
         SDL_SetError("Joystick only has %d axes", joystick->naxes);
         state = 0;
     }
-    return (state);
+    return state;
 }
 
 /*
@@ -383,7 +475,7 @@ SDL_JoystickGetHat(SDL_Joystick * joystick, int hat)
     Uint8 state;
 
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (0);
+        return 0;
     }
     if (hat < joystick->nhats) {
         state = joystick->hats[hat];
@@ -391,7 +483,7 @@ SDL_JoystickGetHat(SDL_Joystick * joystick, int hat)
         SDL_SetError("Joystick only has %d hats", joystick->nhats);
         state = 0;
     }
-    return (state);
+    return state;
 }
 
 /*
@@ -403,7 +495,7 @@ SDL_JoystickGetBall(SDL_Joystick * joystick, int ball, int *dx, int *dy)
     int retval;
 
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
 
     retval = 0;
@@ -419,7 +511,7 @@ SDL_JoystickGetBall(SDL_Joystick * joystick, int ball, int *dx, int *dy)
     } else {
         return SDL_SetError("Joystick only has %d balls", joystick->nballs);
     }
-    return (retval);
+    return retval;
 }
 
 /*
@@ -431,7 +523,7 @@ SDL_JoystickGetButton(SDL_Joystick * joystick, int button)
     Uint8 state;
 
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (0);
+        return 0;
     }
     if (button < joystick->nbuttons) {
         state = joystick->buttons[button];
@@ -439,7 +531,7 @@ SDL_JoystickGetButton(SDL_Joystick * joystick, int button)
         SDL_SetError("Joystick only has %d buttons", joystick->nbuttons);
         state = 0;
     }
-    return (state);
+    return state;
 }
 
 /*
@@ -453,7 +545,7 @@ SDL_JoystickGetAttached(SDL_Joystick * joystick)
         return SDL_FALSE;
     }
 
-    return SDL_SYS_JoystickAttached(joystick);
+    return joystick->driver->IsAttached(joystick);
 }
 
 /*
@@ -463,10 +555,10 @@ SDL_JoystickID
 SDL_JoystickInstanceID(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (-1);
+        return -1;
     }
 
-    return (joystick->instance_id);
+    return joystick->instance_id;
 }
 
 /*
@@ -495,12 +587,21 @@ const char *
 SDL_JoystickName(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (NULL);
+        return NULL;
     }
 
     return SDL_FixupJoystickName(joystick->name);
 }
 
+int
+SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    if (!SDL_PrivateJoystickValid(joystick)) {
+        return -1;
+    }
+    return joystick->driver->Rumble(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
+}
+
 /*
  * Close a joystick previously opened with SDL_JoystickOpen()
  */
@@ -510,7 +611,7 @@ SDL_JoystickClose(SDL_Joystick * joystick)
     SDL_Joystick *joysticklist;
     SDL_Joystick *joysticklistprev;
 
-    if (!joystick) {
+    if (!SDL_PrivateJoystickValid(joystick)) {
         return;
     }
 
@@ -527,7 +628,7 @@ SDL_JoystickClose(SDL_Joystick * joystick)
         return;
     }
 
-    SDL_SYS_JoystickClose(joystick);
+    joystick->driver->Close(joystick);
     joystick->hwdata = NULL;
 
     joysticklist = SDL_joysticks;
@@ -561,6 +662,8 @@ SDL_JoystickClose(SDL_Joystick * joystick)
 void
 SDL_JoystickQuit(void)
 {
+    int i;
+
     /* Make sure we're not getting called in the middle of updating joysticks */
     SDL_assert(!SDL_updating_joystick);
 
@@ -573,7 +676,9 @@ SDL_JoystickQuit(void)
     }
 
     /* Quit the joystick setup */
-    SDL_SYS_JoystickQuit();
+    for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+       SDL_joystick_drivers[i]->Quit();
+    }
 
     SDL_UnlockJoysticks();
 
@@ -609,10 +714,16 @@ SDL_PrivateJoystickShouldIgnoreEvent()
 
 /* These are global for SDL_sysjoystick.c and SDL_events.c */
 
-void SDL_PrivateJoystickAdded(int device_index)
+void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
 {
 #if !SDL_EVENTS_DISABLED
     SDL_Event event;
+    int device_index;
+
+    device_index = SDL_JoystickGetDeviceIndexFromInstanceID(device_instance);
+    if (device_index < 0) {
+        return;
+    }
 
     event.type = SDL_JOYDEVICEADDED;
 
@@ -722,7 +833,7 @@ SDL_PrivateJoystickAxis(SDL_Joystick * joystick, Uint8 axis, Sint16 value)
         posted = SDL_PushEvent(&event) == 1;
     }
 #endif /* !SDL_EVENTS_DISABLED */
-    return (posted);
+    return posted;
 }
 
 int
@@ -762,7 +873,7 @@ SDL_PrivateJoystickHat(SDL_Joystick * joystick, Uint8 hat, Uint8 value)
         posted = SDL_PushEvent(&event) == 1;
     }
 #endif /* !SDL_EVENTS_DISABLED */
-    return (posted);
+    return posted;
 }
 
 int
@@ -798,7 +909,7 @@ SDL_PrivateJoystickBall(SDL_Joystick * joystick, Uint8 ball,
         posted = SDL_PushEvent(&event) == 1;
     }
 #endif /* !SDL_EVENTS_DISABLED */
-    return (posted);
+    return posted;
 }
 
 int
@@ -817,7 +928,7 @@ SDL_PrivateJoystickButton(SDL_Joystick * joystick, Uint8 button, Uint8 state)
         break;
     default:
         /* Invalid state -- bail */
-        return (0);
+        return 0;
     }
 #endif /* !SDL_EVENTS_DISABLED */
 
@@ -850,12 +961,13 @@ SDL_PrivateJoystickButton(SDL_Joystick * joystick, Uint8 button, Uint8 state)
         posted = SDL_PushEvent(&event) == 1;
     }
 #endif /* !SDL_EVENTS_DISABLED */
-    return (posted);
+    return posted;
 }
 
 void
 SDL_JoystickUpdate(void)
 {
+    int i;
     SDL_Joystick *joystick;
 
     SDL_LockJoysticks();
@@ -872,15 +984,13 @@ SDL_JoystickUpdate(void)
     SDL_UnlockJoysticks();
 
     for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
-        SDL_SYS_JoystickUpdate(joystick);
+        joystick->driver->Update(joystick);
 
         if (joystick->delayed_guide_button) {
             SDL_GameControllerHandleDelayedGuideButton(joystick);
         }
 
         if (joystick->force_recentering) {
-            int i;
-
             /* Tell the app that everything is centered/unpressed... */
             for (i = 0; i < joystick->naxes; i++) {
                 if (joystick->axes[i].has_initial_value) {
@@ -914,7 +1024,9 @@ SDL_JoystickUpdate(void)
     /* this needs to happen AFTER walking the joystick list above, so that any
        dangling hardware data from removed devices can be free'd
      */
-    SDL_SYS_JoystickDetect();
+    for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+        SDL_joystick_drivers[i]->Detect();
+    }
 
     SDL_UnlockJoysticks();
 }
@@ -947,7 +1059,7 @@ SDL_JoystickEventState(int state)
         }
         break;
     }
-    return (state);
+    return state;
 #endif /* SDL_EVENTS_DISABLED */
 }
 
@@ -963,7 +1075,7 @@ void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *prod
         /* guid16[4] is product ID */
         guid16[5] == 0x0000
         /* guid16[6] is product version */
-    ) {
+   ) {
         if (vendor) {
             *vendor = guid16[2];
         }
@@ -986,6 +1098,43 @@ void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *prod
     }
 }
 
+SDL_bool
+SDL_IsJoystickPS4(Uint16 vendor, Uint16 product)
+{
+    return (GuessControllerType(vendor, product) == k_eControllerType_PS4Controller);
+}
+
+SDL_bool
+SDL_IsJoystickNintendoSwitchPro(Uint16 vendor, Uint16 product)
+{
+    return (GuessControllerType(vendor, product) == k_eControllerType_SwitchProController);
+}
+
+SDL_bool
+SDL_IsJoystickSteamController(Uint16 vendor, Uint16 product)
+{
+    return BIsSteamController(GuessControllerType(vendor, product)) ? SDL_TRUE : SDL_FALSE;
+}
+
+SDL_bool
+SDL_IsJoystickXbox360(Uint16 vendor, Uint16 product)
+{
+	/* Filter out some bogus values here */
+	if (vendor == 0x0000 && product == 0x0000) {
+		return SDL_FALSE;
+	}
+	if (vendor == 0x0001 && product == 0x0001) {
+		return SDL_FALSE;
+	}
+    return (GuessControllerType(vendor, product) == k_eControllerType_XBox360Controller);
+}
+
+SDL_bool
+SDL_IsJoystickXboxOne(Uint16 vendor, Uint16 product)
+{
+    return (GuessControllerType(vendor, product) == k_eControllerType_XBoxOneController);
+}
+
 static SDL_bool SDL_IsJoystickProductWheel(Uint32 vidpid)
 {
     static Uint32 wheel_joysticks[] = {
@@ -1092,19 +1241,80 @@ static SDL_JoystickType SDL_GetJoystickGUIDType(SDL_JoystickGUID guid)
         return SDL_JOYSTICK_TYPE_THROTTLE;
     }
 
+    if (GuessControllerType(vendor, product) != k_eControllerType_UnknownNonSteamController) {
+        return SDL_JOYSTICK_TYPE_GAMECONTROLLER;
+    }
+
     return SDL_JOYSTICK_TYPE_UNKNOWN;
 }
 
+static SDL_bool SDL_IsPS4RemapperRunning(void)
+{
+#ifdef __WIN32__
+    const char *mapper_processes[] = {
+        "DS4Windows.exe",
+        "InputMapper.exe",
+    };
+    int i;
+    PROCESSENTRY32 pe32;
+    SDL_bool found = SDL_FALSE;
+
+    /* Take a snapshot of all processes in the system */
+    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    if (hProcessSnap != INVALID_HANDLE_VALUE) {
+        pe32.dwSize = sizeof(PROCESSENTRY32);
+        if (Process32First(hProcessSnap, &pe32)) {
+            do
+            {
+                for (i = 0; i < SDL_arraysize(mapper_processes); ++i) {
+                    if (SDL_strcasecmp(pe32.szExeFile, mapper_processes[i]) == 0) {
+                        found = SDL_TRUE;
+                    }
+                }
+            } while (Process32Next(hProcessSnap, &pe32) && !found);
+        }
+        CloseHandle(hProcessSnap);
+    }
+    return found;
+#else
+    return SDL_FALSE;
+#endif
+}
+
+SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid)
+{
+    Uint16 vendor;
+    Uint16 product;
+
+    SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL);
+
+    if (SDL_IsJoystickPS4(vendor, product) && SDL_IsPS4RemapperRunning()) {
+        return SDL_TRUE;
+    }
+
+    if (SDL_IsGameControllerNameAndGUID(name, guid) &&
+        SDL_ShouldIgnoreGameController(name, guid)) {
+        return SDL_TRUE;
+    }
+
+    return SDL_FALSE;
+}
+
 /* return the guid for this index */
 SDL_JoystickGUID SDL_JoystickGetDeviceGUID(int device_index)
 {
-    if (device_index < 0 || device_index >= SDL_NumJoysticks()) {
-        SDL_JoystickGUID emptyGUID;
-        SDL_SetError("There are %d joysticks available", SDL_NumJoysticks());
-        SDL_zero(emptyGUID);
-        return emptyGUID;
+    SDL_JoystickDriver *driver;
+    SDL_JoystickGUID guid;
+
+    SDL_LockJoysticks();
+    if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
+        guid = driver->GetDeviceGUID(device_index);
+    } else {
+        SDL_zero(guid);
     }
-    return SDL_SYS_JoystickGetDeviceGUID(device_index);
+    SDL_UnlockJoysticks();
+
+    return guid;
 }
 
 Uint16 SDL_JoystickGetDeviceVendor(int device_index)
@@ -1150,11 +1360,33 @@ SDL_JoystickType SDL_JoystickGetDeviceType(int device_index)
 
 SDL_JoystickID SDL_JoystickGetDeviceInstanceID(int device_index)
 {
-    if (device_index < 0 || device_index >= SDL_NumJoysticks()) {
-        SDL_SetError("There are %d joysticks available", SDL_NumJoysticks());
-        return -1;
+    SDL_JoystickDriver *driver;
+    SDL_JoystickID instance_id = -1;
+
+    SDL_LockJoysticks();
+    if (SDL_GetDriverAndJoystickIndex(device_index, &driver, &device_index)) {
+        instance_id = driver->GetDeviceInstanceID(device_index);
+    }
+    SDL_UnlockJoysticks();
+
+    return instance_id;
+}
+
+int SDL_JoystickGetDeviceIndexFromInstanceID(SDL_JoystickID instance_id)
+{
+    int i, num_joysticks, device_index = -1;
+
+    SDL_LockJoysticks();
+    num_joysticks = SDL_NumJoysticks();
+    for (i = 0; i < num_joysticks; ++i) {
+        if (SDL_JoystickGetDeviceInstanceID(i) == instance_id) {
+            device_index = i;
+            break;
+        }
     }
-    return SDL_SYS_GetInstanceIdOfDeviceIndex(device_index);
+    SDL_UnlockJoysticks();
+
+    return device_index;
 }
 
 SDL_JoystickGUID SDL_JoystickGetGUID(SDL_Joystick * joystick)
@@ -1164,7 +1396,7 @@ SDL_JoystickGUID SDL_JoystickGetGUID(SDL_Joystick * joystick)
         SDL_zero(emptyGUID);
         return emptyGUID;
     }
-    return SDL_SYS_JoystickGetGUID(joystick);
+    return joystick->guid;
 }
 
 Uint16 SDL_JoystickGetVendor(SDL_Joystick * joystick)
@@ -1229,7 +1461,6 @@ void SDL_JoystickGetGUIDString(SDL_JoystickGUID guid, char *pszGUID, int cbGUID)
     *pszGUID = '\0';
 }
 
-
 /*-----------------------------------------------------------------------------
  * Purpose: Returns the 4 bit nibble for a hex character
  * Input  : c -
@@ -1254,7 +1485,6 @@ static unsigned char nibble(char c)
     return 0;
 }
 
-
 /* convert the string version of a joystick guid to the struct */
 SDL_JoystickGUID SDL_JoystickGetGUIDFromString(const char *pchGUID)
 {
@@ -1277,19 +1507,17 @@ SDL_JoystickGUID SDL_JoystickGetGUIDFromString(const char *pchGUID)
     return guid;
 }
 
-
 /* update the power level for this joystick */
 void SDL_PrivateJoystickBatteryLevel(SDL_Joystick * joystick, SDL_JoystickPowerLevel ePowerLevel)
 {
     joystick->epowerlevel = ePowerLevel;
 }
 
-
 /* return its power level */
 SDL_JoystickPowerLevel SDL_JoystickCurrentPowerLevel(SDL_Joystick * joystick)
 {
     if (!SDL_PrivateJoystickValid(joystick)) {
-        return (SDL_JOYSTICK_POWER_UNKNOWN);
+        return SDL_JOYSTICK_POWER_UNKNOWN;
     }
     return joystick->epowerlevel;
 }

+ 30 - 1
src/joystick/SDL_joystick_c.h

@@ -23,19 +23,48 @@
 /* Useful functions and variables from SDL_joystick.c */
 #include "SDL_joystick.h"
 
+struct _SDL_JoystickDriver;
+
 /* Initialization and shutdown functions */
 extern int SDL_JoystickInit(void);
 extern void SDL_JoystickQuit(void);
 
+/* Function to get the next available joystick instance ID */
+extern SDL_JoystickID SDL_GetNextJoystickInstanceID(void);
+
 /* Initialization and shutdown functions */
 extern int SDL_GameControllerInitMappings(void);
 extern void SDL_GameControllerQuitMappings(void);
 extern int SDL_GameControllerInit(void);
 extern void SDL_GameControllerQuit(void);
 
+/* Function to get the joystick driver and device index for an API device index */
+extern SDL_bool SDL_GetDriverAndJoystickIndex(int device_index, struct _SDL_JoystickDriver **driver, int *driver_index);
+
+/* Function to return the device index for a joystick ID, or -1 if not found */
+extern int SDL_JoystickGetDeviceIndexFromInstanceID(SDL_JoystickID instance_id);
+
 /* Function to extract information from an SDL joystick GUID */
 extern void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint16 *product, Uint16 *version);
 
+/* Function to return whether a joystick is a PS4 controller */
+extern SDL_bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id);
+
+/* Function to return whether a joystick is a Nintendo Switch Pro controller */
+extern SDL_bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id);
+
+/* Function to return whether a joystick is a Steam Controller */
+extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
+
+/* Function to return whether a joystick is an Xbox 360 controller */
+extern SDL_bool SDL_IsJoystickXbox360(Uint16 vendor_id, Uint16 product_id);
+
+/* Function to return whether a joystick is an Xbox One controller */
+extern SDL_bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id);
+
+/* Function to return whether a joystick should be ignored */
+extern SDL_bool SDL_ShouldIgnoreJoystick(const char *name, SDL_JoystickGUID guid);
+
 /* Function to return whether a joystick name and GUID is a game controller  */
 extern SDL_bool SDL_IsGameControllerNameAndGUID(const char *name, SDL_JoystickGUID guid);
 
@@ -46,7 +75,7 @@ extern SDL_bool SDL_ShouldIgnoreGameController(const char *name, SDL_JoystickGUI
 extern void SDL_GameControllerHandleDelayedGuideButton(SDL_Joystick *joystick);
 
 /* Internal event queueing functions */
-extern void SDL_PrivateJoystickAdded(int device_index);
+extern void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance);
 extern void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance);
 extern int SDL_PrivateJoystickAxis(SDL_Joystick * joystick,
                                    Uint8 axis, Sint16 value);

+ 79 - 52
src/joystick/SDL_sysjoystick.h

@@ -42,6 +42,7 @@ struct _SDL_Joystick
 {
     SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */
     char *name;                 /* Joystick name - system dependent */
+    SDL_JoystickGUID guid;      /* Joystick guid */
 
     int naxes;                  /* Number of axis controls on the joystick */
     SDL_JoystickAxisInfo *axes;
@@ -58,69 +59,95 @@ struct _SDL_Joystick
     int nbuttons;               /* Number of buttons on the joystick */
     Uint8 *buttons;             /* Current button states */
 
-    struct joystick_hwdata *hwdata;     /* Driver dependent information */
-
-    int ref_count;              /* Reference count for multiple opens */
-
     SDL_bool is_game_controller;
     SDL_bool delayed_guide_button; /* SDL_TRUE if this device has the guide button event delayed */
     SDL_bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */
     SDL_JoystickPowerLevel epowerlevel; /* power level of this joystick, SDL_JOYSTICK_POWER_UNKNOWN if not supported */
-    struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
-};
-
-/* Macro to combine a USB vendor ID and product ID into a single Uint32 value */
-#define MAKE_VIDPID(VID, PID)   (((Uint32)(VID))<<16|(PID))
-
-/* Function to scan the system for joysticks.
- * Joystick 0 should be the system default joystick.
- * This function should return the number of available joysticks, or -1
- * on an unrecoverable fatal error.
- */
-extern int SDL_SYS_JoystickInit(void);
-
-/* Function to return the number of joystick devices plugged in right now */
-extern int SDL_SYS_NumJoysticks(void);
+    struct _SDL_JoystickDriver *driver;
 
-/* Function to cause any queued joystick insertions to be processed */
-extern void SDL_SYS_JoystickDetect(void);
-
-/* Function to get the device-dependent name of a joystick */
-extern const char *SDL_SYS_JoystickNameForDeviceIndex(int device_index);
-
-/* Function to get the current instance id of the joystick located at device_index */
-extern SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index);
-
-/* Function to open a joystick for use.
-   The joystick to open is specified by the device index.
-   This should fill the nbuttons and naxes fields of the joystick structure.
-   It returns 0, or -1 if there is an error.
- */
-extern int SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index);
+    struct joystick_hwdata *hwdata;     /* Driver dependent information */
 
-/* Function to query if the joystick is currently attached
- * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
- */
-extern SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick * joystick);
+    int ref_count;              /* Reference count for multiple opens */
 
-/* Function to update the state of a joystick - called as a device poll.
- * This function shouldn't update the joystick structure directly,
- * but instead should call SDL_PrivateJoystick*() to deliver events
- * and update joystick device state.
- */
-extern void SDL_SYS_JoystickUpdate(SDL_Joystick * joystick);
+    struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
+};
 
-/* Function to close a joystick after use */
-extern void SDL_SYS_JoystickClose(SDL_Joystick * joystick);
+#if defined(__IPHONEOS__) || defined(__ANDROID__)
+#define HAVE_STEAMCONTROLLERS
+#define USE_STEAMCONTROLLER_HIDAPI
+#elif defined(__LINUX__)
+#define HAVE_STEAMCONTROLLERS
+#define USE_STEAMCONTROLLER_LINUX
+#endif
 
-/* Function to perform any system-specific joystick related cleanup */
-extern void SDL_SYS_JoystickQuit(void);
+/* Device bus definitions */
+#define SDL_HARDWARE_BUS_USB        0x03
+#define SDL_HARDWARE_BUS_BLUETOOTH  0x05
 
-/* Function to return the stable GUID for a plugged in device */
-extern SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID(int device_index);
+/* Macro to combine a USB vendor ID and product ID into a single Uint32 value */
+#define MAKE_VIDPID(VID, PID)   (((Uint32)(VID))<<16|(PID))
 
-/* Function to return the stable GUID for a opened joystick */
-extern SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick);
+typedef struct _SDL_JoystickDriver
+{
+    /* Function to scan the system for joysticks.
+     * Joystick 0 should be the system default joystick.
+     * This function should return 0, or -1 on an unrecoverable error.
+     */
+    int (*Init)(void);
+
+    /* Function to return the number of joystick devices plugged in right now */
+    int (*GetCount)(void);
+
+    /* Function to cause any queued joystick insertions to be processed */
+    void (*Detect)(void);
+
+    /* Function to get the device-dependent name of a joystick */
+    const char *(*GetDeviceName)(int device_index);
+
+    /* Function to return the stable GUID for a plugged in device */
+    SDL_JoystickGUID (*GetDeviceGUID)(int device_index);
+
+    /* Function to get the current instance id of the joystick located at device_index */
+    SDL_JoystickID (*GetDeviceInstanceID)(int device_index);
+
+    /* Function to open a joystick for use.
+       The joystick to open is specified by the device index.
+       This should fill the nbuttons and naxes fields of the joystick structure.
+       It returns 0, or -1 if there is an error.
+     */
+    int (*Open)(SDL_Joystick * joystick, int device_index);
+
+    /* Function to query if the joystick is currently attached
+     * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
+     */
+    SDL_bool (*IsAttached)(SDL_Joystick * joystick);
+
+    /* Rumble functionality */
+    int (*Rumble)(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
+    /* Function to update the state of a joystick - called as a device poll.
+     * This function shouldn't update the joystick structure directly,
+     * but instead should call SDL_PrivateJoystick*() to deliver events
+     * and update joystick device state.
+     */
+    void (*Update)(SDL_Joystick * joystick);
+
+    /* Function to close a joystick after use */
+    void (*Close)(SDL_Joystick * joystick);
+
+    /* Function to perform any system-specific joystick related cleanup */
+    void (*Quit)(void);
+
+} SDL_JoystickDriver;
+
+/* The available joystick drivers */
+extern SDL_JoystickDriver SDL_ANDROID_JoystickDriver;
+extern SDL_JoystickDriver SDL_DARWIN_JoystickDriver;
+extern SDL_JoystickDriver SDL_DUMMY_JoystickDriver;
+extern SDL_JoystickDriver SDL_HIDAPI_JoystickDriver;
+extern SDL_JoystickDriver SDL_IOS_JoystickDriver;
+extern SDL_JoystickDriver SDL_LINUX_JoystickDriver;
+extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver;
 
 #endif /* SDL_sysjoystick_h_ */
 

+ 62 - 139
src/joystick/android/SDL_sysjoystick.c

@@ -36,7 +36,7 @@
 #include "../SDL_joystick_c.h"
 #include "../../events/SDL_keyboard_c.h"
 #include "../../core/android/SDL_android.h"
-#include "../steam/SDL_steamcontroller.h"
+#include "../hidapi/SDL_hidapijoystick_c.h"
 
 #include "android/keycodes.h"
 
@@ -69,7 +69,6 @@ static SDL_joylist_item * JoystickByDeviceId(int device_id);
 static SDL_joylist_item *SDL_joylist = NULL;
 static SDL_joylist_item *SDL_joylist_tail = NULL;
 static int numjoysticks = 0;
-static int instance_counter = 0;
 
 
 /* Public domain CRC implementation adapted from:
@@ -326,7 +325,6 @@ Android_OnHat(int device_id, int hat_id, int x, int y)
 int
 Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, SDL_bool is_accelerometer, int button_mask, int naxes, int nhats, int nballs)
 {
-    const Uint16 BUS_BLUETOOTH = 0x05;
     SDL_joylist_item *item;
     SDL_JoystickGUID guid;
     Uint16 *guid16 = (Uint16 *)guid.data;
@@ -344,7 +342,14 @@ Android_AddJoystick(int device_id, const char *name, const char *desc, int vendo
     if (JoystickByDeviceId(device_id) != NULL || name == NULL) {
         return -1;
     }
-    
+
+#ifdef SDL_JOYSTICK_HIDAPI
+    if (HIDAPI_IsDevicePresent(vendor_id, product_id)) {
+        /* The HIDAPI driver is taking care of this device */
+        return -1;
+    }
+#endif
+
 #ifdef DEBUG_JOYSTICK
     SDL_Log("Joystick: %s, descriptor %s, vendor = 0x%.4x, product = 0x%.4x, %d axes, %d hats\n", name, desc, vendor_id, product_id, naxes, nhats);
 #endif
@@ -378,7 +383,7 @@ Android_AddJoystick(int device_id, const char *name, const char *desc, int vendo
 
     /* We only need 16 bits for each of these; space them out to fill 128. */
     /* Byteswap so devices get same GUID on little/big endian platforms. */
-    *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
+    *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
     *guid16++ = 0;
 
     if (vendor_id && product_id) {
@@ -424,7 +429,7 @@ Android_AddJoystick(int device_id, const char *name, const char *desc, int vendo
     item->naxes = naxes;
     item->nhats = nhats;
     item->nballs = nballs;
-    item->device_instance = instance_counter++;
+    item->device_instance = SDL_GetNextJoystickInstanceID();
     if (SDL_joylist_tail == NULL) {
         SDL_joylist = SDL_joylist_tail = item;
     } else {
@@ -435,7 +440,7 @@ Android_AddJoystick(int device_id, const char *name, const char *desc, int vendo
     /* Need to increment the joystick count before we post the event */
     ++numjoysticks;
 
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
+    SDL_PrivateJoystickAdded(item->device_instance);
 
 #ifdef DEBUG_JOYSTICK
     SDL_Log("Added joystick %s with device_id %d", name, device_id);
@@ -492,104 +497,29 @@ Android_RemoveJoystick(int device_id)
 }
 
 
-static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickGUID guid, int *device_instance)
-{
-    SDL_joylist_item *item;
-    
-    item = (SDL_joylist_item *)SDL_calloc(1, sizeof (SDL_joylist_item));
-    if (item == NULL) {
-        return SDL_FALSE;
-    }
-
-    *device_instance = item->device_instance = instance_counter++;
-    item->device_id = -1;
-    item->name = SDL_strdup(name);
-    item->guid = guid;
-    SDL_GetSteamControllerInputs(&item->nbuttons,
-                                 &item->naxes,
-                                 &item->nhats);
-    item->m_bSteamController = SDL_TRUE;
-
-    if (SDL_joylist_tail == NULL) {
-        SDL_joylist = SDL_joylist_tail = item;
-    } else {
-        SDL_joylist_tail->next = item;
-        SDL_joylist_tail = item;
-    }
-
-    /* Need to increment the joystick count before we post the event */
-    ++numjoysticks;
-
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
-
-    return SDL_TRUE;
-}
-
-static void SteamControllerDisconnectedCallback(int device_instance)
-{
-    SDL_joylist_item *item = SDL_joylist;
-    SDL_joylist_item *prev = NULL;
-    
-    while (item != NULL) {
-        if (item->device_instance == device_instance) {
-            break;
-        }
-        prev = item;
-        item = item->next;
-    }
-    
-    if (item == NULL) {
-        return;
-    }
-
-    if (item->joystick) {
-        item->joystick->hwdata = NULL;
-    }
-        
-    if (prev != NULL) {
-        prev->next = item->next;
-    } else {
-        SDL_assert(SDL_joylist == item);
-        SDL_joylist = item->next;
-    }
-    if (item == SDL_joylist_tail) {
-        SDL_joylist_tail = prev;
-    }
-
-    /* Need to decrement the joystick count before we post the event */
-    --numjoysticks;
-
-    SDL_PrivateJoystickRemoved(item->device_instance);
-
-    SDL_free(item->name);
-    SDL_free(item);
-}
+static void ANDROID_JoystickDetect();
 
-int
-SDL_SYS_JoystickInit(void)
+static int
+ANDROID_JoystickInit(void)
 {
-    SDL_SYS_JoystickDetect();
+    ANDROID_JoystickDetect();
     
     if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
         /* Default behavior, accelerometer as joystick */
         Android_AddJoystick(ANDROID_ACCELEROMETER_DEVICE_ID, ANDROID_ACCELEROMETER_NAME, ANDROID_ACCELEROMETER_NAME, 0, 0, SDL_TRUE, 0, 3, 0, 0);
     }
-   
-    SDL_InitSteamControllers(SteamControllerConnectedCallback,
-                             SteamControllerDisconnectedCallback);
-
-    return (numjoysticks);
+    return 0;
 
 }
 
-int
-SDL_SYS_NumJoysticks(void)
+static int
+ANDROID_JoystickGetCount(void)
 {
     return numjoysticks;
 }
 
-void
-SDL_SYS_JoystickDetect(void)
+static void
+ANDROID_JoystickDetect(void)
 {
     /* Support for device connect/disconnect is API >= 16 only,
      * so we poll every three seconds
@@ -600,8 +530,6 @@ SDL_SYS_JoystickDetect(void)
         timeout = SDL_GetTicks() + 3000;
         Android_JNI_PollInputDevices();
     }
-
-    SDL_UpdateSteamControllers();
 }
 
 static SDL_joylist_item *
@@ -635,7 +563,7 @@ JoystickByDeviceId(int device_id)
     }
     
     /* Joystick not found, try adding it */
-    SDL_SYS_JoystickDetect();
+    ANDROID_JoystickDetect();
     
     while (item != NULL) {
         if (item->device_id == device_id) {
@@ -647,26 +575,26 @@ JoystickByDeviceId(int device_id)
     return NULL;
 }
 
-/* Function to get the device-dependent name of a joystick */
-const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+static const char *
+ANDROID_JoystickGetDeviceName(int device_index)
 {
     return JoystickByDevIndex(device_index)->name;
 }
 
-/* Function to perform the mapping from device index to the instance id for this index */
-SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickGUID
+ANDROID_JoystickGetDeviceGUID(int device_index)
+{
+    return JoystickByDevIndex(device_index)->guid;
+}
+
+static SDL_JoystickID
+ANDROID_JoystickGetDeviceInstanceID(int device_index)
 {
     return JoystickByDevIndex(device_index)->device_instance;
 }
 
-/* Function to open a joystick for use.
-   The joystick to open is specified by the device index.
-   This should fill the nbuttons and naxes fields of the joystick structure.
-   It returns 0, or -1 if there is an error.
- */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static int
+ANDROID_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
     SDL_joylist_item *item = JoystickByDevIndex(device_index);
 
@@ -689,14 +617,20 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
     return (0);
 }
 
-/* Function to determine if this joystick is attached to the system right now */
-SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
+static SDL_bool
+ANDROID_JoystickIsAttached(SDL_Joystick *joystick)
 {
     return joystick->hwdata != NULL;
 }
 
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static int
+ANDROID_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    return SDL_Unsupported();
+}
+
+static void
+ANDROID_JoystickUpdate(SDL_Joystick * joystick)
 {
     SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
 
@@ -704,11 +638,6 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
         return;
     }
  
-    if (item->m_bSteamController) {
-        SDL_UpdateSteamController(joystick);
-        return;
-    }
-
     if (item->is_accelerometer) {
         int i;
         Sint16 value;
@@ -729,9 +658,8 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
     }
 }
 
-/* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static void
+ANDROID_JoystickClose(SDL_Joystick * joystick)
 {
     SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
     if (item) {
@@ -739,9 +667,8 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick)
     }
 }
 
-/* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+ANDROID_JoystickQuit(void)
 {
 /* We don't have any way to scan for joysticks at init, so don't wipe the list
  * of joysticks here in case this is a reinit.
@@ -759,28 +686,24 @@ SDL_SYS_JoystickQuit(void)
     SDL_joylist = SDL_joylist_tail = NULL;
 
     numjoysticks = 0;
-    instance_counter = 0;
 #endif /* 0 */
-
-    SDL_QuitSteamControllers();
-}
-
-SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID(int device_index)
-{
-    return JoystickByDevIndex(device_index)->guid;
 }
 
-SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
+SDL_JoystickDriver SDL_ANDROID_JoystickDriver =
 {
-    SDL_JoystickGUID guid;
-    
-    if (joystick->hwdata != NULL) {
-        return ((SDL_joylist_item*)joystick->hwdata)->guid;
-    }
-    
-    SDL_zero(guid);
-    return guid;
-}
+    ANDROID_JoystickInit,
+    ANDROID_JoystickGetCount,
+    ANDROID_JoystickDetect,
+    ANDROID_JoystickGetDeviceName,
+    ANDROID_JoystickGetDeviceGUID,
+    ANDROID_JoystickGetDeviceInstanceID,
+    ANDROID_JoystickOpen,
+    ANDROID_JoystickIsAttached,
+    ANDROID_JoystickRumble,
+    ANDROID_JoystickUpdate,
+    ANDROID_JoystickClose,
+    ANDROID_JoystickQuit,
+};
 
 #endif /* SDL_JOYSTICK_ANDROID */
 

+ 0 - 3
src/joystick/android/SDL_sysjoystick_c.h

@@ -47,9 +47,6 @@ typedef struct SDL_joylist_item
     int nbuttons, naxes, nhats, nballs;
     int dpad_state;
     
-    /* Steam Controller support */
-    SDL_bool m_bSteamController;
-
     struct SDL_joylist_item *next;
 } SDL_joylist_item;
 

+ 267 - 85
src/joystick/darwin/SDL_sysjoystick.c

@@ -22,29 +22,79 @@
 
 #ifdef SDL_JOYSTICK_IOKIT
 
-#include <IOKit/hid/IOHIDLib.h>
-
-/* For force feedback testing. */
-#include <ForceFeedback/ForceFeedback.h>
-#include <ForceFeedback/ForceFeedbackConstants.h>
-
+#include "SDL_events.h"
 #include "SDL_joystick.h"
 #include "../SDL_sysjoystick.h"
 #include "../SDL_joystick_c.h"
 #include "SDL_sysjoystick_c.h"
-#include "SDL_events.h"
+#include "../hidapi/SDL_hidapijoystick_c.h"
 #include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
 
+
 #define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
 
+#define CONVERT_MAGNITUDE(x)    (((x)*10000) / 0x7FFF)
+
 /* The base object of the HID Manager API */
 static IOHIDManagerRef hidman = NULL;
 
 /* Linked list of all available devices */
 static recDevice *gpDeviceList = NULL;
 
-/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
-static int s_joystick_instance_id = -1;
+void FreeRumbleEffectData(FFEFFECT *effect)
+{
+    if (!effect) {
+        return;
+    }
+    SDL_free(effect->rgdwAxes);
+    SDL_free(effect->rglDirection);
+    SDL_free(effect->lpvTypeSpecificParams);
+    SDL_free(effect);
+}
+
+FFEFFECT *CreateRumbleEffectData(Sint16 magnitude, Uint32 duration_ms)
+{
+    FFEFFECT *effect;
+    FFPERIODIC *periodic;
+
+    /* Create the effect */
+    effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
+    if (!effect) {
+        return NULL;
+    }
+    effect->dwSize = sizeof(*effect);
+    effect->dwGain = 10000;
+    effect->dwFlags = FFEFF_OBJECTOFFSETS;
+    effect->dwDuration = duration_ms * 1000; /* In microseconds. */
+    effect->dwTriggerButton = FFEB_NOTRIGGER;
+
+    effect->cAxes = 2;
+    effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
+    if (!effect->rgdwAxes) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+
+    effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
+    if (!effect->rglDirection) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+    effect->dwFlags |= FFEFF_CARTESIAN;
+
+    periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
+    if (!periodic) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+    periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
+    periodic->dwPeriod = 1000000;
+
+    effect->cbTypeSpecificParams = sizeof(*periodic);
+    effect->lpvTypeSpecificParams = periodic;
+
+    return effect;
+}
 
 static recDevice *GetDeviceForIndex(int device_index)
 {
@@ -157,6 +207,19 @@ JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
     recDevice *device = (recDevice *) ctx;
     device->removed = SDL_TRUE;
     device->deviceRef = NULL; // deviceRef was invalidated due to the remove
+    if (device->ffeffect_ref) {
+        FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
+        device->ffeffect_ref = NULL;
+    }
+    if (device->ffeffect) {
+        FreeRumbleEffectData(device->ffeffect);
+        device->ffeffect = NULL;
+    }
+    if (device->ffdevice) {
+        FFReleaseDevice(device->ffdevice);
+        device->ffdevice = NULL;
+        device->ff_initialized = SDL_FALSE;
+    }
 #if SDL_HAPTIC_IOKIT
     MacHaptic_MaybeRemoveDevice(device->ffservice);
 #endif
@@ -333,8 +396,6 @@ AddHIDElement(const void *value, void *parameter)
 static SDL_bool
 GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
 {
-    const Uint16 BUS_USB = 0x03;
-    const Uint16 BUS_BLUETOOTH = 0x05;
     Sint32 vendor = 0;
     Sint32 product = 0;
     Sint32 version = 0;
@@ -389,10 +450,17 @@ GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
         CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
     }
 
+#ifdef SDL_JOYSTICK_HIDAPI
+    if (HIDAPI_IsDevicePresent(vendor, product)) {
+        /* The HIDAPI driver is taking care of this device */
+        return 0;
+    }
+#endif
+
     SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
 
     if (vendor && product) {
-        *guid16++ = SDL_SwapLE16(BUS_USB);
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
         *guid16++ = 0;
         *guid16++ = SDL_SwapLE16((Uint16)vendor);
         *guid16++ = 0;
@@ -401,7 +469,7 @@ GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
         *guid16++ = SDL_SwapLE16((Uint16)version);
         *guid16++ = 0;
     } else {
-        *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
         *guid16++ = 0;
         SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
     }
@@ -444,7 +512,6 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic
     }
 
     device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
-
     if (!device) {
         SDL_OutOfMemory();
         return;
@@ -455,8 +522,7 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic
         return;   /* not a device we care about, probably. */
     }
 
-    if (SDL_IsGameControllerNameAndGUID(device->product, device->guid) &&
-        SDL_ShouldIgnoreGameController(device->product, device->guid)) {
+    if (SDL_ShouldIgnoreJoystick(device->product, device->guid)) {
         SDL_free(device);
         return;
     }
@@ -466,16 +532,16 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic
     IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
 
     /* Allocate an instance ID for this device */
-    device->instance_id = ++s_joystick_instance_id;
+    device->instance_id = SDL_GetNextJoystickInstanceID();
 
     /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
     ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
-#if SDL_HAPTIC_IOKIT
     if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
         device->ffservice = ioservice;
+#if SDL_HAPTIC_IOKIT
         MacHaptic_MaybeAddDevice(ioservice);
-    }
 #endif
+    }
 
     /* Add device to the end of the list */
     if ( !gpDeviceList ) {
@@ -492,7 +558,7 @@ JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDevic
         ++device_index;  /* bump by one since we counted by pNext. */
     }
 
-    SDL_PrivateJoystickAdded(device_index);
+    SDL_PrivateJoystickAdded(device->instance_id);
 }
 
 static SDL_bool
@@ -577,13 +643,8 @@ CreateHIDManager(void)
 }
 
 
-/* Function to scan the system for joysticks.
- * Joystick 0 should be the system default joystick.
- * This function should return the number of available joysticks, or -1
- * on an unrecoverable fatal error.
- */
-int
-SDL_SYS_JoystickInit(void)
+static int
+DARWIN_JoystickInit(void)
 {
     if (gpDeviceList) {
         return SDL_SetError("Joystick: Device list already inited.");
@@ -593,12 +654,11 @@ SDL_SYS_JoystickInit(void)
         return SDL_SetError("Joystick: Couldn't initialize HID Manager");
     }
 
-    return SDL_SYS_NumJoysticks();
+    return 0;
 }
 
-/* Function to return the number of joystick devices plugged in right now */
-int
-SDL_SYS_NumJoysticks(void)
+static int
+DARWIN_JoystickGetCount(void)
 {
     recDevice *device = gpDeviceList;
     int nJoySticks = 0;
@@ -613,10 +673,8 @@ SDL_SYS_NumJoysticks(void)
     return nJoySticks;
 }
 
-/* Function to cause any queued joystick insertions to be processed
- */
-void
-SDL_SYS_JoystickDetect(void)
+static void
+DARWIN_JoystickDetect(void)
 {
     recDevice *device = gpDeviceList;
     while (device) {
@@ -627,37 +685,43 @@ SDL_SYS_JoystickDetect(void)
         }
     }
 
-	/* run this after the checks above so we don't set device->removed and delete the device before
-	   SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
-	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
-		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
-	}
+    /* run this after the checks above so we don't set device->removed and delete the device before
+       DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
+    while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
+        /* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
+    }
 }
 
 /* Function to get the device-dependent name of a joystick */
 const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+DARWIN_JoystickGetDeviceName(int device_index)
 {
     recDevice *device = GetDeviceForIndex(device_index);
     return device ? device->product : "UNKNOWN";
 }
 
-/* Function to return the instance id of the joystick at device_index
- */
-SDL_JoystickID
-SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickGUID
+DARWIN_JoystickGetDeviceGUID( int device_index )
+{
+    recDevice *device = GetDeviceForIndex(device_index);
+    SDL_JoystickGUID guid;
+    if (device) {
+        guid = device->guid;
+    } else {
+        SDL_zero(guid);
+    }
+    return guid;
+}
+
+static SDL_JoystickID
+DARWIN_JoystickGetDeviceInstanceID(int device_index)
 {
     recDevice *device = GetDeviceForIndex(device_index);
     return device ? device->instance_id : 0;
 }
 
-/* Function to open a joystick for use.
- * The joystick to open is specified by the device index.
- * This should fill the nbuttons and naxes fields of the joystick structure.
- * It returns 0, or -1 if there is an error.
- */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static int
+DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
     recDevice *device = GetDeviceForIndex(device_index);
 
@@ -672,22 +736,144 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
     return 0;
 }
 
-/* Function to query if the joystick is currently attached
- * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
- */
-SDL_bool
-SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
+static SDL_bool
+DARWIN_JoystickIsAttached(SDL_Joystick * joystick)
 {
     return joystick->hwdata != NULL;
 }
 
-/* Function to update the state of a joystick - called as a device poll.
- * This function shouldn't update the joystick structure directly,
- * but instead should call SDL_PrivateJoystick*() to deliver events
- * and update joystick device state.
+/*
+ * Like strerror but for force feedback errors.
  */
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static const char *
+FFStrError(unsigned int err)
+{
+    switch (err) {
+    case FFERR_DEVICEFULL:
+        return "device full";
+    /* This should be valid, but for some reason isn't defined... */
+    /* case FFERR_DEVICENOTREG:
+        return "device not registered"; */
+    case FFERR_DEVICEPAUSED:
+        return "device paused";
+    case FFERR_DEVICERELEASED:
+        return "device released";
+    case FFERR_EFFECTPLAYING:
+        return "effect playing";
+    case FFERR_EFFECTTYPEMISMATCH:
+        return "effect type mismatch";
+    case FFERR_EFFECTTYPENOTSUPPORTED:
+        return "effect type not supported";
+    case FFERR_GENERIC:
+        return "undetermined error";
+    case FFERR_HASEFFECTS:
+        return "device has effects";
+    case FFERR_INCOMPLETEEFFECT:
+        return "incomplete effect";
+    case FFERR_INTERNAL:
+        return "internal fault";
+    case FFERR_INVALIDDOWNLOADID:
+        return "invalid download id";
+    case FFERR_INVALIDPARAM:
+        return "invalid parameter";
+    case FFERR_MOREDATA:
+        return "more data";
+    case FFERR_NOINTERFACE:
+        return "interface not supported";
+    case FFERR_NOTDOWNLOADED:
+        return "effect is not downloaded";
+    case FFERR_NOTINITIALIZED:
+        return "object has not been initialized";
+    case FFERR_OUTOFMEMORY:
+        return "out of memory";
+    case FFERR_UNPLUGGED:
+        return "device is unplugged";
+    case FFERR_UNSUPPORTED:
+        return "function call unsupported";
+    case FFERR_UNSUPPORTEDAXIS:
+        return "axis unsupported";
+
+    default:
+        return "unknown error";
+    }
+}
+
+static int
+DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude, Uint32 duration_ms)
+{
+    HRESULT result;
+
+    if (!device->ffdevice) {
+        result = FFCreateDevice(device->ffservice, &device->ffdevice);
+        if (result != FF_OK) {
+            return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
+        }
+    }
+
+    /* Reset and then enable actuators */
+    result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
+    if (result != FF_OK) {
+        return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
+    }
+
+    result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
+    if (result != FF_OK) {
+        return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
+    }
+
+    /* Create the effect */
+    device->ffeffect = CreateRumbleEffectData(magnitude, duration_ms);
+    if (!device->ffeffect) {
+        return SDL_OutOfMemory();
+    }
+
+    result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
+                               device->ffeffect, &device->ffeffect_ref);
+    if (result != FF_OK) {
+        return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
+    }
+    return 0;
+}
+
+static int
+DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    HRESULT result;
+    recDevice *device = joystick->hwdata;
+
+    /* Scale and average the two rumble strengths */
+    Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
+
+    if (!device->ffservice) {
+        return SDL_Unsupported();
+    }
+
+    if (device->ff_initialized) {
+        FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
+        device->ffeffect->dwDuration = duration_ms * 1000; /* In microseconds. */
+        periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
+
+        result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
+                                    (FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
+        if (result != FF_OK) {
+            return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
+        }
+    } else {
+        if (DARWIN_JoystickInitRumble(device, magnitude, duration_ms) < 0) {
+            return -1;
+        }
+        device->ff_initialized = SDL_TRUE;
+    }
+
+    result = FFEffectStart(device->ffeffect_ref, 1, 0);
+    if (result != FF_OK) {
+        return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
+    }
+    return 0;
+}
+
+static void
+DARWIN_JoystickUpdate(SDL_Joystick * joystick)
 {
     recDevice *device = joystick->hwdata;
     recElement *element;
@@ -792,15 +978,13 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
     }
 }
 
-/* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static void
+DARWIN_JoystickClose(SDL_Joystick * joystick)
 {
 }
 
-/* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+DARWIN_JoystickQuit(void)
 {
     while (FreeDevice(gpDeviceList)) {
         /* spin */
@@ -814,23 +998,21 @@ SDL_SYS_JoystickQuit(void)
     }
 }
 
-
-SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
+SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
 {
-    recDevice *device = GetDeviceForIndex(device_index);
-    SDL_JoystickGUID guid;
-    if (device) {
-        guid = device->guid;
-    } else {
-        SDL_zero(guid);
-    }
-    return guid;
-}
-
-SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
-{
-    return joystick->hwdata->guid;
-}
+    DARWIN_JoystickInit,
+    DARWIN_JoystickGetCount,
+    DARWIN_JoystickDetect,
+    DARWIN_JoystickGetDeviceName,
+    DARWIN_JoystickGetDeviceGUID,
+    DARWIN_JoystickGetDeviceInstanceID,
+    DARWIN_JoystickOpen,
+    DARWIN_JoystickIsAttached,
+    DARWIN_JoystickRumble,
+    DARWIN_JoystickUpdate,
+    DARWIN_JoystickClose,
+    DARWIN_JoystickQuit,
+};
 
 #endif /* SDL_JOYSTICK_IOKIT */
 

+ 6 - 0
src/joystick/darwin/SDL_sysjoystick_c.h

@@ -24,6 +24,8 @@
 #define SDL_JOYSTICK_IOKIT_H
 
 #include <IOKit/hid/IOHIDLib.h>
+#include <ForceFeedback/ForceFeedback.h>
+#include <ForceFeedback/ForceFeedbackConstants.h>
 
 struct recElement
 {
@@ -45,6 +47,10 @@ struct joystick_hwdata
 {
     IOHIDDeviceRef deviceRef;   /* HIDManager device handle */
     io_service_t ffservice;     /* Interface for force feedback, 0 = no ff */
+    FFDeviceObjectReference ffdevice;
+    FFEFFECT *ffeffect;
+    FFEffectObjectReference ffeffect_ref;
+    SDL_bool ff_initialized;
 
     char product[256];          /* name of product */
     uint32_t usage;                 /* usage page from IOUSBHID Parser.h which defines general usage */

+ 49 - 56
src/joystick/dummy/SDL_sysjoystick.c

@@ -28,100 +28,93 @@
 #include "../SDL_sysjoystick.h"
 #include "../SDL_joystick_c.h"
 
-/* Function to scan the system for joysticks.
- * It should return 0, or -1 on an unrecoverable fatal error.
- */
-int
-SDL_SYS_JoystickInit(void)
+
+static int
+DUMMY_JoystickInit(void)
 {
     return 0;
 }
 
-int
-SDL_SYS_NumJoysticks(void)
+static int
+DUMMY_JoystickGetCount(void)
 {
     return 0;
 }
 
-void
-SDL_SYS_JoystickDetect(void)
+static void
+DUMMY_JoystickDetect(void)
 {
 }
 
-/* Function to get the device-dependent name of a joystick */
-const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+static const char *
+DUMMY_JoystickGetDeviceName(int device_index)
 {
-    SDL_SetError("Logic error: No joysticks available");
-    return (NULL);
+    return NULL;
 }
 
-/* Function to perform the mapping from device index to the instance id for this index */
-SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickGUID
+DUMMY_JoystickGetDeviceGUID(int device_index)
 {
-    return device_index;
+    SDL_JoystickGUID guid;
+    SDL_zero(guid);
+    return guid;
 }
 
-/* Function to open a joystick for use.
-   The joystick to open is specified by the device index.
-   This should fill the nbuttons and naxes fields of the joystick structure.
-   It returns 0, or -1 if there is an error.
- */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static SDL_JoystickID
+DUMMY_JoystickGetDeviceInstanceID(int device_index)
 {
-    return SDL_SetError("Logic error: No joysticks available");
+    return -1;
 }
 
-/* Function to determine if this joystick is attached to the system right now */
-SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
+static int
+DUMMY_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
-    return SDL_TRUE;
+    return SDL_SetError("Logic error: No joysticks available");
 }
 
-/* Function to update the state of a joystick - called as a device poll.
- * This function shouldn't update the joystick structure directly,
- * but instead should call SDL_PrivateJoystick*() to deliver events
- * and update joystick device state.
- */
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static SDL_bool
+DUMMY_JoystickIsAttached(SDL_Joystick *joystick)
 {
+    return SDL_FALSE;
 }
 
-/* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static int
+DUMMY_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
 {
+    return SDL_Unsupported();
 }
 
-/* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+DUMMY_JoystickUpdate(SDL_Joystick * joystick)
 {
 }
 
-SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
+static void
+DUMMY_JoystickClose(SDL_Joystick * joystick)
 {
-    SDL_JoystickGUID guid;
-    /* the GUID is just the first 16 chars of the name for now */
-    const char *name = SDL_SYS_JoystickNameForDeviceIndex( device_index );
-    SDL_zero( guid );
-    SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
-    return guid;
 }
 
-
-SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
+static void
+DUMMY_JoystickQuit(void)
 {
-    SDL_JoystickGUID guid;
-    /* the GUID is just the first 16 chars of the name for now */
-    const char *name = joystick->name;
-    SDL_zero( guid );
-    SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
-    return guid;
 }
 
+SDL_JoystickDriver SDL_DUMMY_JoystickDriver =
+{
+    DUMMY_JoystickInit,
+    DUMMY_JoystickGetCount,
+    DUMMY_JoystickDetect,
+    DUMMY_JoystickGetDeviceName,
+    DUMMY_JoystickGetDeviceGUID,
+    DUMMY_JoystickGetDeviceInstanceID,
+    DUMMY_JoystickOpen,
+    DUMMY_JoystickIsAttached,
+    DUMMY_JoystickRumble,
+    DUMMY_JoystickUpdate,
+    DUMMY_JoystickClose,
+    DUMMY_JoystickQuit,
+};
+
 #endif /* SDL_JOYSTICK_DUMMY || SDL_JOYSTICK_DISABLED */
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 533 - 0
src/joystick/hidapi/SDL_hidapi_ps4.c

@@ -0,0 +1,533 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_events.h"
+#include "SDL_timer.h"
+#include "SDL_joystick.h"
+#include "SDL_gamecontroller.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+#ifdef SDL_JOYSTICK_HIDAPI_PS4
+
+#define SONY_USB_VID        0x054C
+#define SONY_DS4_PID        0x05C4
+#define SONY_DS4_DONGLE_PID 0x0BA0
+#define SONY_DS4_SLIM_PID   0x09CC
+
+#define USB_PACKET_LENGTH   64
+
+#define VOLUME_CHECK_INTERVAL_MS    (10 * 1000)
+
+typedef enum
+{
+    k_EPS4ReportIdUsbState = 1,
+    k_EPS4ReportIdUsbEffects = 5,
+    k_EPS4ReportIdBluetoothState = 17,
+    k_EPS4ReportIdBluetoothEffects = 17,
+    k_EPS4ReportIdDisconnectMessage = 226,
+} EPS4ReportId;
+
+typedef enum 
+{
+    k_ePS4FeatureReportIdGyroCalibration_USB = 0x02,
+    k_ePS4FeatureReportIdGyroCalibration_BT = 0x05,
+    k_ePS4FeatureReportIdSerialNumber = 0x12,
+} EPS4FeatureReportID;
+
+typedef struct
+{
+    Uint8 ucLeftJoystickX;
+    Uint8 ucLeftJoystickY;
+    Uint8 ucRightJoystickX;
+    Uint8 ucRightJoystickY;
+    Uint8 rgucButtonsHatAndCounter[ 3 ];
+    Uint8 ucTriggerLeft;
+    Uint8 ucTriggerRight;
+    Uint8 _rgucPad0[ 3 ];
+    Sint16 sGyroX;
+    Sint16 sGyroY;
+    Sint16 sGyroZ;
+    Sint16 sAccelX;
+    Sint16 sAccelY;
+    Sint16 sAccelZ;
+    Uint8 _rgucPad1[ 5 ];
+    Uint8 ucBatteryLevel;
+    Uint8 _rgucPad2[ 4 ];
+    Uint8 ucTrackpadCounter1;
+    Uint8 rgucTrackpadData1[ 3 ];
+    Uint8 ucTrackpadCounter2;
+    Uint8 rgucTrackpadData2[ 3 ];
+} PS4StatePacket_t;
+
+typedef struct
+{
+    Uint8 ucRumbleRight;
+    Uint8 ucRumbleLeft;
+    Uint8 ucLedRed;
+    Uint8 ucLedGreen;
+    Uint8 ucLedBlue;
+    Uint8 ucLedDelayOn;
+    Uint8 ucLedDelayOff;
+    Uint8 _rgucPad0[ 8 ];
+    Uint8 ucVolumeLeft;
+    Uint8 ucVolumeRight;
+    Uint8 ucVolumeMic;
+    Uint8 ucVolumeSpeaker;
+} DS4EffectsState_t;
+
+typedef struct {
+    SDL_bool is_dongle;
+    SDL_bool is_bluetooth;
+    SDL_bool audio_supported;
+    Uint8 volume;
+    Uint32 last_volume_check;
+    Uint32 rumble_expiration;
+    PS4StatePacket_t last_state;
+} SDL_DriverPS4_Context;
+
+
+/* Public domain CRC implementation adapted from:
+   http://home.thep.lu.se/~bjorn/crc/crc32_simple.c
+*/
+static Uint32 crc32_for_byte(Uint32 r)
+{
+    int i;
+    for(i = 0; i < 8; ++i) {
+        r = (r & 1? 0: (Uint32)0xEDB88320L) ^ r >> 1;
+    }
+    return r ^ (Uint32)0xFF000000L;
+}
+
+static Uint32 crc32(Uint32 crc, const void *data, int count)
+{
+    int i;
+    for(i = 0; i < count; ++i) {
+        crc = crc32_for_byte((Uint8)crc ^ ((const Uint8*)data)[i]) ^ crc >> 8;
+    }
+    return crc;
+}
+
+#ifdef __WIN32__
+#include "../../core/windows/SDL_windows.h"
+
+/* Define Vista for the Audio related includes below to work */
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_VISTA
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_VISTA
+#define COBJMACROS
+#include <Mmdeviceapi.h>
+#include <Audioclient.h>
+#include <Endpointvolume.h>
+
+#undef DEFINE_GUID
+#define DEFINE_GUID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const GUID n = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
+DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
+DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
+DEFINE_GUID(IID_IAudioEndpointVolume, 0x5CDF2C82, 0x841E, 0x4546, 0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x22, 0x9A);
+#endif
+
+
+
+static float GetSystemVolume(void)
+{
+    float volume = -1.0f;    /* Return this if we can't get system volume */
+
+#ifdef __WIN32__
+    HRESULT hr = WIN_CoInitialize();
+    if (SUCCEEDED(hr)) {
+        IMMDeviceEnumerator *pEnumerator;
+
+        /* This should gracefully fail on XP and succeed on everything Vista and above */
+        hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID*)&pEnumerator);
+        if (SUCCEEDED(hr)) {
+            IMMDevice *pDevice;
+
+            hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eRender, eConsole, &pDevice);
+            if (SUCCEEDED(hr)) {
+                IAudioEndpointVolume *pEndpointVolume;
+
+                hr = IMMDevice_Activate(pDevice, &IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (LPVOID*)&pEndpointVolume);
+                if (SUCCEEDED(hr)) {
+                    IAudioEndpointVolume_GetMasterVolumeLevelScalar(pEndpointVolume, &volume);
+                    IUnknown_Release(pEndpointVolume);
+                }
+                IUnknown_Release(pDevice);
+            }
+            IUnknown_Release(pEnumerator);
+        }
+        WIN_CoUninitialize();
+    }
+#endif /* __WIN32__ */
+
+    return volume;
+}
+
+static uint8_t GetPlaystationVolumeFromFloat(float fVolume)
+{
+    const int k_nVolumeFitRatio = 15;
+    const int k_nVolumeFitOffset = 9;
+    float fVolLog;
+
+    if (fVolume > 1.0f || fVolume < 0.0f) {
+        fVolume = 0.30f;
+    }
+    fVolLog = SDL_logf(fVolume * 100);
+
+    return (Uint8)((fVolLog * k_nVolumeFitRatio) + k_nVolumeFitOffset);
+}
+
+static SDL_bool
+HIDAPI_DriverPS4_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, int interface_number, Uint16 usage_page, Uint16 usage)
+{
+    /* The Revolution Pro Controller exposes multiple interfaces on Windows */
+    const Uint16 NACON_USB_VID = 0x146b;
+    if (vendor_id == NACON_USB_VID && usage_page != 0 && usage_page != 1) {
+        return SDL_FALSE;
+    }
+
+    return SDL_IsJoystickPS4(vendor_id, product_id);
+}
+
+static const char *
+HIDAPI_DriverPS4_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+{
+    if (vendor_id == SONY_USB_VID) {
+        return "PS4 Controller";
+    }
+    return NULL;
+}
+
+static SDL_bool ReadFeatureReport(hid_device *dev, Uint8 report_id, Uint8 *data, size_t size)
+{
+    Uint8 report[USB_PACKET_LENGTH + 1];
+
+    SDL_memset(report, 0, sizeof(report));
+    report[0] = report_id;
+    if (hid_get_feature_report(dev, report, sizeof(report)) < 0) {
+        return SDL_FALSE;
+    }
+    SDL_memcpy(data, report, SDL_min(size, sizeof(report)));
+    return SDL_TRUE;
+}
+
+static SDL_bool CheckUSBConnected(hid_device *dev)
+{
+    int i;
+    Uint8 data[16];
+
+    /* This will fail if we're on Bluetooth */
+    if (ReadFeatureReport(dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data))) {
+        for (i = 0; i < sizeof(data); ++i) {
+            if (data[i] != 0x00) {
+                return SDL_TRUE;
+            }
+        }
+        /* Maybe the dongle without a connected controller? */
+    }
+    return SDL_FALSE;
+}
+
+static int HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
+static SDL_bool
+HIDAPI_DriverPS4_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+{
+    SDL_DriverPS4_Context *ctx;
+
+    ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    *context = ctx;
+
+    /* Check for type of connection */
+    ctx->is_dongle = (vendor_id == SONY_USB_VID && product_id == SONY_DS4_DONGLE_PID);
+    if (ctx->is_dongle) {
+        ctx->is_bluetooth = SDL_FALSE;
+    } else if (vendor_id == SONY_USB_VID) {
+        ctx->is_bluetooth = !CheckUSBConnected(dev);
+    } else {
+        /* Third party controllers appear to all be wired */
+        ctx->is_bluetooth = SDL_FALSE;
+    }
+#ifdef DEBUG_PS4
+    SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", ctx->is_bluetooth ? "TRUE" : "FALSE");
+#endif
+
+    /* Check to see if audio is supported */
+    if (vendor_id == SONY_USB_VID &&
+        (product_id == SONY_DS4_SLIM_PID || product_id == SONY_DS4_DONGLE_PID )) {
+        ctx->audio_supported = SDL_TRUE;
+    }
+
+    /* Initialize LED and effect state */
+    HIDAPI_DriverPS4_Rumble(joystick, dev, ctx, 0, 0, 0);
+
+    /* Initialize the joystick capabilities */
+    joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
+    joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+
+    return SDL_TRUE;
+}
+
+static int
+HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context;
+    DS4EffectsState_t *effects;
+    Uint8 data[78];
+    int report_size, offset;
+
+    /* In order to send rumble, we have to send a complete effect packet */
+    SDL_memset(data, 0, sizeof(data));
+
+    if (ctx->is_bluetooth) {
+        data[0] = k_EPS4ReportIdBluetoothEffects;
+        data[1] = 0xC0 | 0x04;  /* Magic value HID + CRC, also sets interval to 4ms for samples */
+        data[3] = 0x03;  /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */
+
+        report_size = 78;
+        offset = 6;
+    } else {
+        data[0] = k_EPS4ReportIdUsbEffects;
+        data[1] = 0x07;  /* Magic value */
+
+        report_size = 32;
+        offset = 4;
+    }
+    effects = (DS4EffectsState_t *)&data[offset];
+
+    effects->ucRumbleLeft = (low_frequency_rumble >> 8);
+    effects->ucRumbleRight = (high_frequency_rumble >> 8);
+
+    effects->ucLedRed = 0;
+    effects->ucLedGreen = 0;
+    effects->ucLedBlue = 80;
+
+    if (ctx->audio_supported) {
+        Uint32 now = SDL_GetTicks();
+        if (!ctx->last_volume_check ||
+            SDL_TICKS_PASSED(now, ctx->last_volume_check + VOLUME_CHECK_INTERVAL_MS)) {
+            ctx->volume = GetPlaystationVolumeFromFloat(GetSystemVolume());
+            ctx->last_volume_check = now;
+        }
+
+        effects->ucVolumeRight = ctx->volume;
+        effects->ucVolumeLeft = ctx->volume;
+        effects->ucVolumeSpeaker = ctx->volume;
+        effects->ucVolumeMic = 0xFF;
+    }
+
+    if (ctx->is_bluetooth) {
+        /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */
+        Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */
+        Uint32 unCRC;
+        unCRC = crc32(0, &ubHdr, 1);
+        unCRC = crc32(unCRC, data, (Uint32)(report_size - sizeof(unCRC)));
+        SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
+    }
+
+    if (hid_write(dev, data, report_size) != report_size) {
+        return SDL_SetError("Couldn't send rumble packet");
+    }
+
+    if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
+        ctx->rumble_expiration = SDL_GetTicks() + duration_ms;
+    } else {
+        ctx->rumble_expiration = 0;
+    }
+    return 0;
+}
+
+static void
+HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet)
+{
+    Sint16 axis;
+
+    if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {
+        {
+            Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);
+
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        }
+        {
+            Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);
+            SDL_bool dpad_up = SDL_FALSE;
+            SDL_bool dpad_down = SDL_FALSE;
+            SDL_bool dpad_left = SDL_FALSE;
+            SDL_bool dpad_right = SDL_FALSE;
+
+            switch (data) {
+            case 0:
+                dpad_up = SDL_TRUE;
+                break;
+            case 1:
+                dpad_up = SDL_TRUE;
+                dpad_right = SDL_TRUE;
+                break;
+            case 2:
+                dpad_right = SDL_TRUE;
+                break;
+            case 3:
+                dpad_right = SDL_TRUE;
+                dpad_down = SDL_TRUE;
+                break;
+            case 4:
+                dpad_down = SDL_TRUE;
+                break;
+            case 5:
+                dpad_left = SDL_TRUE;
+                dpad_down = SDL_TRUE;
+                break;
+            case 6:
+                dpad_left = SDL_TRUE;
+                break;
+            case 7:
+                dpad_up = SDL_TRUE;
+                dpad_left = SDL_TRUE;
+                break;
+            default:
+                break;
+            }
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right);
+            SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left);
+        }
+    }
+
+    if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {
+        Uint8 data = packet->rgucButtonsHatAndCounter[1];
+
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x80) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {
+        Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);
+
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    axis = ((int)packet->ucTriggerLeft * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
+    axis = ((int)packet->ucTriggerRight * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
+    axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
+    axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis);
+    axis = ((int)packet->ucRightJoystickX * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
+    axis = ((int)packet->ucRightJoystickY * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
+
+    if (packet->ucBatteryLevel & 0x10) {
+        joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+    } else {
+        /* Battery level ranges from 0 to 10 */
+        int level = (packet->ucBatteryLevel & 0xF);
+        if (level == 0) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY;
+        } else if (level <= 2) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW;
+        } else if (level <= 7) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM;
+        } else {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
+        }
+    }
+
+    SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
+}
+
+static void
+HIDAPI_DriverPS4_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context;
+    Uint8 data[USB_PACKET_LENGTH];
+    int size;
+
+    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+        switch (data[0]) {
+        case k_EPS4ReportIdUsbState:
+            HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[1]);
+            break;
+        case k_EPS4ReportIdBluetoothState:
+            /* Bluetooth state packets have two additional bytes at the beginning */
+            HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[3]);
+            break;
+        default:
+#ifdef DEBUG_JOYSTICK
+            SDL_Log("Unknown PS4 packet: 0x%.2x\n", data[0]);
+#endif
+            break;
+        }
+    }
+
+    if (ctx->rumble_expiration) {
+        Uint32 now = SDL_GetTicks();
+        if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
+            HIDAPI_DriverPS4_Rumble(joystick, dev, context, 0, 0, 0);
+        }
+    }
+}
+
+static void
+HIDAPI_DriverPS4_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_free(context);
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
+{
+    SDL_HINT_JOYSTICK_HIDAPI_PS4,
+    SDL_TRUE,
+    HIDAPI_DriverPS4_IsSupportedDevice,
+    HIDAPI_DriverPS4_GetDeviceName,
+    HIDAPI_DriverPS4_Init,
+    HIDAPI_DriverPS4_Rumble,
+    HIDAPI_DriverPS4_Update,
+    HIDAPI_DriverPS4_Quit
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_PS4 */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 899 - 0
src/joystick/hidapi/SDL_hidapi_switch.c

@@ -0,0 +1,899 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_events.h"
+#include "SDL_timer.h"
+#include "SDL_joystick.h"
+#include "SDL_gamecontroller.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
+
+typedef enum {
+    k_eSwitchInputReportIDs_SubcommandReply       = 0x21,
+    k_eSwitchInputReportIDs_FullControllerState   = 0x30,
+    k_eSwitchInputReportIDs_SimpleControllerState = 0x3F,
+    k_eSwitchInputReportIDs_CommandAck            = 0x81,
+} ESwitchInputReportIDs;
+
+typedef enum {
+    k_eSwitchOutputReportIDs_RumbleAndSubcommand = 0x01,
+    k_eSwitchOutputReportIDs_Rumble              = 0x10,
+    k_eSwitchOutputReportIDs_Proprietary         = 0x80,
+} ESwitchOutputReportIDs;
+
+typedef enum {
+    k_eSwitchSubcommandIDs_BluetoothManualPair = 0x01,
+    k_eSwitchSubcommandIDs_RequestDeviceInfo   = 0x02,
+    k_eSwitchSubcommandIDs_SetInputReportMode  = 0x03,
+    k_eSwitchSubcommandIDs_SetHCIState         = 0x06,
+    k_eSwitchSubcommandIDs_SPIFlashRead        = 0x10,
+    k_eSwitchSubcommandIDs_SetPlayerLights     = 0x30,
+    k_eSwitchSubcommandIDs_SetHomeLight        = 0x38,
+    k_eSwitchSubcommandIDs_EnableIMU           = 0x40,
+    k_eSwitchSubcommandIDs_SetIMUSensitivity   = 0x41,
+    k_eSwitchSubcommandIDs_EnableVibration     = 0x48,
+} ESwitchSubcommandIDs;
+
+typedef enum {
+    k_eSwitchProprietaryCommandIDs_Handshake = 0x02,
+    k_eSwitchProprietaryCommandIDs_HighSpeed = 0x03,
+    k_eSwitchProprietaryCommandIDs_ForceUSB  = 0x04,
+    k_eSwitchProprietaryCommandIDs_ClearUSB  = 0x05,
+    k_eSwitchProprietaryCommandIDs_ResetMCU  = 0x06,
+} ESwitchProprietaryCommandIDs;
+
+typedef enum {
+    k_eSwitchDeviceInfoControllerType_JoyConLeft     = 0x1,
+    k_eSwitchDeviceInfoControllerType_JoyConRight    = 0x2,
+    k_eSwitchDeviceInfoControllerType_ProController  = 0x3,
+} ESwitchDeviceInfoControllerType;
+
+#define k_unSwitchOutputPacketDataLength 49
+#define k_unSwitchMaxOutputPacketLength  64
+#define k_unSwitchBluetoothPacketLength  k_unSwitchOutputPacketDataLength
+#define k_unSwitchUSBPacketLength        k_unSwitchMaxOutputPacketLength
+
+#define k_unSPIStickCalibrationStartOffset  0x603D
+#define k_unSPIStickCalibrationEndOffset    0x604E
+#define k_unSPIStickCalibrationLength       (k_unSPIStickCalibrationEndOffset - k_unSPIStickCalibrationStartOffset + 1)
+
+#pragma pack(1)
+typedef struct
+{
+    Uint8 rgucButtons[2];
+    Uint8 ucStickHat;
+    Sint16 sJoystickLeft[2];
+    Sint16 sJoystickRight[2];
+} SwitchSimpleStatePacket_t;
+
+typedef struct
+{
+    Uint8 ucCounter;
+    Uint8 ucBatteryAndConnection;
+    Uint8 rgucButtons[3];
+    Uint8 rgucJoystickLeft[3];
+    Uint8 rgucJoystickRight[3];
+    Uint8 ucVibrationCode;
+} SwitchControllerStatePacket_t;
+
+typedef struct
+{
+    SwitchControllerStatePacket_t controllerState;
+
+    struct {
+        Sint16 sAccelX;
+        Sint16 sAccelY;
+        Sint16 sAccelZ;
+
+        Sint16 sGyroX;
+        Sint16 sGyroY;
+        Sint16 sGyroZ;
+    } imuState[3];
+} SwitchStatePacket_t;
+
+typedef struct
+{
+    Uint32 unAddress;
+    Uint8 ucLength;
+} SwitchSPIOpData_t;
+
+typedef struct
+{
+    SwitchControllerStatePacket_t m_controllerState;
+
+    Uint8 ucSubcommandAck;
+    Uint8 ucSubcommandID;
+
+    #define k_unSubcommandDataBytes 35
+    union {
+        Uint8 rgucSubcommandData[ k_unSubcommandDataBytes ];
+
+        struct {
+            SwitchSPIOpData_t opData;
+            Uint8 rgucReadData[ k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t) ];
+        } spiReadData;
+
+        struct {
+            Uint8 rgucFirmwareVersion[2];
+            Uint8 ucDeviceType;
+            Uint8 ucFiller1;
+            Uint8 rgucMACAddress[6];
+            Uint8 ucFiller2;
+            Uint8 ucColorLocation;
+        } deviceInfo;
+    };
+} SwitchSubcommandInputPacket_t;
+
+typedef struct
+{
+    Uint8 rgucData[4];
+} SwitchRumbleData_t;
+
+typedef struct
+{
+    Uint8 ucPacketType;
+    Uint8 ucPacketNumber;
+    SwitchRumbleData_t rumbleData[2];
+} SwitchCommonOutputPacket_t;
+
+typedef struct
+{
+    SwitchCommonOutputPacket_t commonData;
+
+    Uint8 ucSubcommandID;
+    Uint8 rgucSubcommandData[ k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1 ];
+} SwitchSubcommandOutputPacket_t;
+
+typedef struct
+{
+    Uint8 ucPacketType;
+    Uint8 ucProprietaryID;
+
+    Uint8 rgucProprietaryData[ k_unSwitchOutputPacketDataLength - 1 - 1 ];
+} SwitchProprietaryOutputPacket_t;
+#pragma pack()
+
+typedef struct {
+    hid_device *dev;
+    SDL_bool m_bIsUsingBluetooth;
+    Uint8 m_nCommandNumber;
+    SwitchCommonOutputPacket_t m_RumblePacket;
+    Uint32 m_nRumbleExpiration;
+    Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];
+    SwitchSimpleStatePacket_t m_lastSimpleState;
+    SwitchStatePacket_t m_lastFullState;
+
+    struct StickCalibrationData {
+        struct {
+            Sint16 sCenter;
+            Sint16 sMin;
+            Sint16 sMax;
+        } axis[2];
+    } m_StickCalData[2];
+
+    struct StickExtents {
+        struct {
+            Sint16 sMin;
+            Sint16 sMax;
+        } axis[2];
+    } m_StickExtents[2];
+} SDL_DriverSwitch_Context;
+
+
+static SDL_bool
+HIDAPI_DriverSwitch_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, int interface_number, Uint16 usage_page, Uint16 usage)
+{
+    return SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id);
+}
+
+static const char *
+HIDAPI_DriverSwitch_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+{
+    /* Give a user friendly name for this controller */
+    if (SDL_IsJoystickNintendoSwitchPro(vendor_id, product_id)) {
+        return "Nintendo Switch Pro Controller";
+    }
+    return NULL;
+}
+
+static int ReadInput(SDL_DriverSwitch_Context *ctx)
+{
+    return hid_read_timeout(ctx->dev, ctx->m_rgucReadBuffer, sizeof(ctx->m_rgucReadBuffer), 0);
+}
+
+static int WriteOutput(SDL_DriverSwitch_Context *ctx, Uint8 *data, int size)
+{
+    return hid_write(ctx->dev, data, size);
+}
+
+static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs expectedID)
+{
+    /* Average response time for messages is ~30ms */
+    Uint32 TimeoutMs = 100;
+    Uint32 startTicks = SDL_GetTicks();
+
+    int nRead = 0;
+    while ((nRead = ReadInput(ctx)) != -1) {
+        if (nRead > 0) {
+            if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
+                SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[ 1 ];
+                if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) {
+                    return reply;
+                }
+            }
+        } else {
+            SDL_Delay(1);
+        }
+
+        if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) {
+            break;
+        }
+    }
+    return NULL;
+}
+
+static SDL_bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs expectedID)
+{
+    /* Average response time for messages is ~30ms */
+    Uint32 TimeoutMs = 100;
+    Uint32 startTicks = SDL_GetTicks();
+
+    int nRead = 0;
+    while ((nRead = ReadInput(ctx)) != -1) {
+        if (nRead > 0) {
+            if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[ 1 ] == expectedID) {
+                return SDL_TRUE;
+            }
+        } else {
+            SDL_Delay(1);
+        }
+
+        if (SDL_TICKS_PASSED(SDL_GetTicks(), startTicks + TimeoutMs)) {
+            break;
+        }
+    }
+    return SDL_FALSE;
+}
+
+static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandOutputPacket_t *outPacket)
+{
+    SDL_memset(outPacket, 0, sizeof(*outPacket));
+
+    outPacket->commonData.ucPacketType = k_eSwitchOutputReportIDs_RumbleAndSubcommand;
+    outPacket->commonData.ucPacketNumber = ctx->m_nCommandNumber;
+
+    SDL_memcpy(&outPacket->commonData.rumbleData, &ctx->m_RumblePacket.rumbleData, sizeof(ctx->m_RumblePacket.rumbleData));
+
+    outPacket->ucSubcommandID = ucCommandID;
+    SDL_memcpy(outPacket->rgucSubcommandData, pBuf, ucLen);
+
+    ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
+}
+
+static SDL_bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen)
+{
+    Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength];
+    const size_t unWriteSize = ctx->m_bIsUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength;
+
+    if (ucLen > k_unSwitchOutputPacketDataLength) {
+        return SDL_FALSE;
+    }
+
+    if (ucLen < unWriteSize) {
+        SDL_memcpy(rgucBuf, pBuf, ucLen);
+        SDL_memset(rgucBuf+ucLen, 0, unWriteSize-ucLen);
+        pBuf = rgucBuf;
+        ucLen = (Uint8)unWriteSize;
+    }
+    return (WriteOutput(ctx, (Uint8 *)pBuf, ucLen) >= 0);
+}
+
+static SDL_bool WriteSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommandIDs ucCommandID, Uint8 *pBuf, Uint8 ucLen, SwitchSubcommandInputPacket_t **ppReply)
+{
+    int nRetries = 5;
+    SwitchSubcommandInputPacket_t *reply = NULL;
+
+    while (!reply && nRetries--) {
+        SwitchSubcommandOutputPacket_t commandPacket;
+        ConstructSubcommand(ctx, ucCommandID, pBuf, ucLen, &commandPacket);
+
+        if (!WritePacket(ctx, &commandPacket, sizeof(commandPacket))) {
+            continue;
+        }
+
+        reply = ReadSubcommandReply(ctx, ucCommandID);
+    }
+
+    if (ppReply) {
+        *ppReply = reply;
+    }
+    return reply != NULL;
+}
+
+static SDL_bool WriteProprietary(SDL_DriverSwitch_Context *ctx, ESwitchProprietaryCommandIDs ucCommand, Uint8 *pBuf, Uint8 ucLen, SDL_bool waitForReply)
+{
+    int nRetries = 5;
+
+    while (nRetries--) {
+        SwitchProprietaryOutputPacket_t packet;
+
+        if ((!pBuf && ucLen > 0) || ucLen > sizeof(packet.rgucProprietaryData)) {
+            return SDL_FALSE;
+        }
+
+        packet.ucPacketType = k_eSwitchOutputReportIDs_Proprietary;
+        packet.ucProprietaryID = ucCommand;
+        SDL_memcpy(packet.rgucProprietaryData, pBuf, ucLen);
+
+        if (!WritePacket(ctx, &packet, sizeof(packet))) {
+            continue;
+        }
+
+        if (!waitForReply || ReadProprietaryReply(ctx, ucCommand)) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+static void SetNeutralRumble(SwitchRumbleData_t *pRumble)
+{
+    pRumble->rgucData[0] = 0x00;
+    pRumble->rgucData[1] = 0x01;
+    pRumble->rgucData[2] = 0x40;
+    pRumble->rgucData[3] = 0x40;
+}
+
+static void EncodeRumble(SwitchRumbleData_t *pRumble, Uint16 usHighFreq, Uint8 ucHighFreqAmp, Uint8 ucLowFreq, Uint16 usLowFreqAmp)
+{
+    if (ucHighFreqAmp > 0 || usLowFreqAmp > 0) {
+        // High-band frequency and low-band amplitude are actually nine-bits each so they
+        // take a bit from the high-band amplitude and low-band frequency bytes respectively
+        pRumble->rgucData[0] = usHighFreq & 0xFF;
+        pRumble->rgucData[1] = ucHighFreqAmp | ((usHighFreq >> 8) & 0x01);
+
+        pRumble->rgucData[2]  = ucLowFreq | ((usLowFreqAmp >> 8) & 0x80);
+        pRumble->rgucData[3]  = usLowFreqAmp & 0xFF;
+
+#ifdef DEBUG_RUMBLE
+        SDL_Log("Freq: %.2X %.2X  %.2X, Amp: %.2X  %.2X %.2X\n",
+            usHighFreq & 0xFF, ((usHighFreq >> 8) & 0x01), ucLowFreq,
+            ucHighFreqAmp, ((usLowFreqAmp >> 8) & 0x80), usLowFreqAmp & 0xFF);
+#endif
+    } else {
+        SetNeutralRumble(pRumble);
+    }
+}
+
+static SDL_bool WriteRumble(SDL_DriverSwitch_Context *ctx)
+{
+    /* Write into m_RumblePacket rather than a temporary buffer to allow the current rumble state
+     * to be retained for subsequent rumble or subcommand packets sent to the controller
+     */
+    ctx->m_RumblePacket.ucPacketType = k_eSwitchOutputReportIDs_Rumble;
+    ctx->m_RumblePacket.ucPacketNumber = ctx->m_nCommandNumber;
+    ctx->m_nCommandNumber = (ctx->m_nCommandNumber + 1) & 0xF;
+
+    return WritePacket(ctx, (Uint8 *)&ctx->m_RumblePacket, sizeof(ctx->m_RumblePacket));
+}
+
+static SDL_bool BTrySetupUSB(SDL_DriverSwitch_Context *ctx)
+{
+    /* We have to send a connection handshake to the controller when communicating over USB
+     * before we're able to send it other commands. Luckily this command is not supported
+     * over Bluetooth, so we can use the controller's lack of response as a way to
+     * determine if the connection is over USB or Bluetooth
+     */
+    if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) {
+        return SDL_FALSE;
+    }
+    if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_HighSpeed, NULL, 0, SDL_TRUE)) {
+        return SDL_FALSE;
+    }
+    if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Handshake, NULL, 0, SDL_TRUE)) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
+
+static SDL_bool SetVibrationEnabled(SDL_DriverSwitch_Context *ctx, Uint8 enabled)
+{
+    return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_EnableVibration, &enabled, sizeof(enabled), NULL);
+
+}
+static SDL_bool SetInputMode(SDL_DriverSwitch_Context *ctx, Uint8 input_mode)
+{
+    return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetInputReportMode, &input_mode, 1, NULL);
+}
+
+static SDL_bool SetHomeLED(SDL_DriverSwitch_Context *ctx, Uint8 brightness)
+{
+    Uint8 ucLedIntensity = 0;
+    Uint8 rgucBuffer[4];
+
+    if (brightness > 0) {
+        if (brightness < 65) {
+            ucLedIntensity = (brightness + 5) / 10;
+        } else {
+            ucLedIntensity = (Uint8)SDL_ceilf(0xF * SDL_powf((float)brightness / 100.f, 2.13f));
+        }
+    }
+
+    rgucBuffer[0] = (0x0 << 4) | 0x1;  /* 0 mini cycles (besides first), cycle duration 8ms */
+    rgucBuffer[1] = ((ucLedIntensity & 0xF) << 4) | 0x0;  /* LED start intensity (0x0-0xF), 0 cycles (LED stays on at start intensity after first cycle) */
+    rgucBuffer[2] = ((ucLedIntensity & 0xF) << 4) | 0x0;  /* First cycle LED intensity, 0x0 intensity for second cycle */
+    rgucBuffer[3] = (0x0 << 4) | 0x0;  /* 8ms fade transition to first cycle, 8ms first cycle LED duration */
+
+    return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetHomeLight, rgucBuffer, sizeof(rgucBuffer), NULL);
+}
+
+static SDL_bool SetSlotLED(SDL_DriverSwitch_Context *ctx, Uint8 slot)
+{
+    Uint8 led_data = (1 << slot);
+    return WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SetPlayerLights, &led_data, sizeof(led_data), NULL);
+}
+
+static SDL_bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx)
+{
+    Uint8 *pStickCal;
+    size_t stick, axis;
+    SwitchSubcommandInputPacket_t *reply = NULL;
+
+    /* Read Calibration Info */
+    SwitchSPIOpData_t readParams;
+    readParams.unAddress = k_unSPIStickCalibrationStartOffset;
+    readParams.ucLength = k_unSPIStickCalibrationLength;
+
+    if (!WriteSubcommand(ctx, k_eSwitchSubcommandIDs_SPIFlashRead, (uint8_t *)&readParams, sizeof(readParams), &reply)) {
+        return SDL_FALSE;
+    }
+
+    /* Stick calibration values are 12-bits each and are packed by bit
+     * For whatever reason the fields are in a different order for each stick
+     * Left:  X-Max, Y-Max, X-Center, Y-Center, X-Min, Y-Min
+     * Right: X-Center, Y-Center, X-Min, Y-Min, X-Max, Y-Max
+     */
+    pStickCal = reply->spiReadData.rgucReadData;
+
+    /* Left stick */
+    ctx->m_StickCalData[0].axis[0].sMax    = ((pStickCal[1] << 8) & 0xF00) | pStickCal[0];     /* X Axis max above center */
+    ctx->m_StickCalData[0].axis[1].sMax    = (pStickCal[2] << 4) | (pStickCal[1] >> 4);         /* Y Axis max above center */
+    ctx->m_StickCalData[0].axis[0].sCenter = ((pStickCal[4] << 8) & 0xF00) | pStickCal[3];     /* X Axis center */
+    ctx->m_StickCalData[0].axis[1].sCenter = (pStickCal[5] << 4) | (pStickCal[4] >> 4);        /* Y Axis center */
+    ctx->m_StickCalData[0].axis[0].sMin    = ((pStickCal[7] << 8) & 0xF00) | pStickCal[6];      /* X Axis min below center */
+    ctx->m_StickCalData[0].axis[1].sMin    = (pStickCal[8] << 4) | (pStickCal[7] >> 4);        /* Y Axis min below center */
+
+    /* Right stick */
+    ctx->m_StickCalData[1].axis[0].sCenter = ((pStickCal[10] << 8) & 0xF00) | pStickCal[9];     /* X Axis center */
+    ctx->m_StickCalData[1].axis[1].sCenter = (pStickCal[11] << 4) | (pStickCal[10] >> 4);      /* Y Axis center */
+    ctx->m_StickCalData[1].axis[0].sMin    = ((pStickCal[13] << 8) & 0xF00) | pStickCal[12];    /* X Axis min below center */
+    ctx->m_StickCalData[1].axis[1].sMin    = (pStickCal[14] << 4) | (pStickCal[13] >> 4);      /* Y Axis min below center */
+    ctx->m_StickCalData[1].axis[0].sMax    = ((pStickCal[16] << 8) & 0xF00) | pStickCal[15];    /* X Axis max above center */
+    ctx->m_StickCalData[1].axis[1].sMax    = (pStickCal[17] << 4) | (pStickCal[16] >> 4);      /* Y Axis max above center */
+
+    /* Filter out any values that were uninitialized (0xFFF) in the SPI read */
+    for (stick = 0; stick < 2; ++stick) {
+        for (axis = 0; axis < 2; ++axis) {
+            if (ctx->m_StickCalData[stick].axis[axis].sCenter == 0xFFF) {
+                ctx->m_StickCalData[stick].axis[axis].sCenter = 0;
+            }
+            if (ctx->m_StickCalData[stick].axis[axis].sMax == 0xFFF) {
+                ctx->m_StickCalData[stick].axis[axis].sMax = 0;
+            }
+            if (ctx->m_StickCalData[stick].axis[axis].sMin == 0xFFF) {
+                ctx->m_StickCalData[stick].axis[axis].sMin = 0;
+            }
+        }
+    }
+
+    if (ctx->m_bIsUsingBluetooth) {
+        for (stick = 0; stick < 2; ++stick) {
+            for(axis = 0; axis < 2; ++axis) {
+                ctx->m_StickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f);
+                ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(SDL_MAX_SINT16 * 0.5f);
+            }
+        }
+    } else {
+        for (stick = 0; stick < 2; ++stick) {
+            for(axis = 0; axis < 2; ++axis) {
+                ctx->m_StickExtents[stick].axis[axis].sMin = -(Sint16)(ctx->m_StickCalData[stick].axis[axis].sMin * 0.7f);
+                ctx->m_StickExtents[stick].axis[axis].sMax = (Sint16)(ctx->m_StickCalData[stick].axis[axis].sMax * 0.7f);
+            }
+        }
+    }
+    return SDL_TRUE;
+}
+
+static float fsel(float fComparand, float fValGE, float fLT)
+{
+    return fComparand >= 0 ? fValGE : fLT;
+}
+
+static float RemapVal(float val, float A, float B, float C, float D)
+{
+    if (A == B) {
+        return fsel(val - B , D , C);
+    }
+    return C + (D - C) * (val - A) / (B - A);
+}
+
+static Sint16 ApplyStickCalibrationCentered(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue, Sint16 sCenter)
+{
+    sRawValue -= sCenter;
+
+    if (sRawValue > ctx->m_StickExtents[nStick].axis[nAxis].sMax) {
+        ctx->m_StickExtents[nStick].axis[nAxis].sMax = sRawValue;
+    }
+    if (sRawValue < ctx->m_StickExtents[nStick].axis[nAxis].sMin) {
+        ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue;
+    }
+
+    if (sRawValue > 0) {
+        return (Sint16)(RemapVal(sRawValue, 0, ctx->m_StickExtents[nStick].axis[nAxis].sMax, 0, SDL_MAX_SINT16));
+    } else {
+        return (Sint16)(RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, 0, SDL_MIN_SINT16, 0));
+    }
+}
+
+static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)
+{
+    return ApplyStickCalibrationCentered(ctx, nStick, nAxis, sRawValue, ctx->m_StickCalData[nStick].axis[nAxis].sCenter);
+}
+
+static SDL_bool
+HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+{
+    SDL_DriverSwitch_Context *ctx;
+    Uint8 input_mode;
+
+    ctx = (SDL_DriverSwitch_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    ctx->dev = dev;
+
+    *context = ctx;
+
+    /* Initialize rumble data */
+    SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
+    SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
+
+    /* Try setting up USB mode, and if that fails we're using Bluetooth */
+    if (!BTrySetupUSB(ctx)) {
+        ctx->m_bIsUsingBluetooth = SDL_TRUE;
+    }
+
+    if (!LoadStickCalibration(ctx)) {
+        SDL_SetError("Couldn't load stick calibration");
+        SDL_free(ctx);
+        return SDL_FALSE;
+    }
+
+    if (!SetVibrationEnabled(ctx, 1)) {
+        SDL_SetError("Couldn't enable vibration");
+        SDL_free(ctx);
+        return SDL_FALSE;
+    }
+
+    /* Set the desired input mode */
+    if (ctx->m_bIsUsingBluetooth) {
+        input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
+    } else {
+        input_mode = k_eSwitchInputReportIDs_FullControllerState;
+    }
+    if (!SetInputMode(ctx, input_mode)) {
+        SDL_SetError("Couldn't set input mode");
+        SDL_free(ctx);
+        return SDL_FALSE;
+    }
+
+    /* Start sending USB reports */
+    if (!ctx->m_bIsUsingBluetooth) {
+        /* ForceUSB doesn't generate an ACK, so don't wait for a reply */
+        if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) {
+            SDL_SetError("Couldn't start USB reports");
+            SDL_free(ctx);
+            return SDL_FALSE;
+        }
+    }
+
+    /* Set the LED state */
+    SetHomeLED(ctx, 100);
+    SetSlotLED(ctx, (joystick->instance_id % 4));
+
+    /* Initialize the joystick capabilities */
+    joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
+    joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+
+    return SDL_TRUE;
+}
+
+static int
+HIDAPI_DriverSwitch_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+
+    /* Experimentally determined rumble values. These will only matter on some controllers as tested ones
+     * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble
+     *
+     * More information about these values can be found here:
+     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+     */
+    const Uint16 k_usHighFreq = 0x0074;
+    const Uint8  k_ucHighFreqAmp = 0xBE;
+    const Uint8  k_ucLowFreq = 0x3D;
+    const Uint16 k_usLowFreqAmp = 0x806F;
+
+    if (low_frequency_rumble) {
+        EncodeRumble(&ctx->m_RumblePacket.rumbleData[0], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
+    } else {
+        SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
+    }
+
+    if (high_frequency_rumble) {
+        EncodeRumble(&ctx->m_RumblePacket.rumbleData[1], k_usHighFreq, k_ucHighFreqAmp, k_ucLowFreq, k_usLowFreqAmp);
+    } else {
+        SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
+    }
+
+    if (!WriteRumble(ctx)) {
+        SDL_SetError("Couldn't send rumble packet");
+        return -1;
+    }
+
+    if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
+        ctx->m_nRumbleExpiration = SDL_GetTicks() + duration_ms;
+    } else {
+        ctx->m_nRumbleExpiration = 0;
+    }
+    return 0;
+}
+
+static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
+{
+    /* 0x8000 is the neutral value for all joystick axes */
+    const Uint16 usJoystickCenter = 0x8000;
+    Sint16 axis;
+
+    if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) {
+        Uint8 data = packet->rgucButtons[0];
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+
+        axis = (data & 0x40) ? 32767 : -32768;
+        SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
+
+        axis = (data & 0x80) ? 32767 : -32768;
+        SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
+    }
+
+    if (packet->rgucButtons[1] != ctx->m_lastSimpleState.rgucButtons[1]) {
+        Uint8 data = packet->rgucButtons[1];
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    if (packet->ucStickHat != ctx->m_lastSimpleState.ucStickHat) {
+        SDL_bool dpad_up = SDL_FALSE;
+        SDL_bool dpad_down = SDL_FALSE;
+        SDL_bool dpad_left = SDL_FALSE;
+        SDL_bool dpad_right = SDL_FALSE;
+
+        switch (packet->ucStickHat) {
+        case 0:
+            dpad_up = SDL_TRUE;
+            break;
+        case 1:
+            dpad_up = SDL_TRUE;
+            dpad_right = SDL_TRUE;
+            break;
+        case 2:
+            dpad_right = SDL_TRUE;
+            break;
+        case 3:
+            dpad_right = SDL_TRUE;
+            dpad_down = SDL_TRUE;
+            break;
+        case 4:
+            dpad_down = SDL_TRUE;
+            break;
+        case 5:
+            dpad_left = SDL_TRUE;
+            dpad_down = SDL_TRUE;
+            break;
+        case 6:
+            dpad_left = SDL_TRUE;
+            break;
+        case 7:
+            dpad_up = SDL_TRUE;
+            dpad_left = SDL_TRUE;
+            break;
+        default:
+            break;
+        }
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left);
+    }
+
+    axis = ApplyStickCalibrationCentered(ctx, 0, 0, packet->sJoystickLeft[0], (Sint16)usJoystickCenter);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
+
+    axis = ApplyStickCalibrationCentered(ctx, 0, 1, packet->sJoystickLeft[1], (Sint16)usJoystickCenter);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis);
+
+    axis = ApplyStickCalibrationCentered(ctx, 1, 0, packet->sJoystickRight[0], (Sint16)usJoystickCenter);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
+
+    axis = ApplyStickCalibrationCentered(ctx, 1, 1, packet->sJoystickRight[1], (Sint16)usJoystickCenter);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
+
+    ctx->m_lastSimpleState = *packet;
+}
+
+static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet)
+{
+    Sint16 axis;
+
+    if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) {
+        Uint8 data = packet->controllerState.rgucButtons[0];
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        axis = (data & 0x80) ? 32767 : -32768;
+        SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
+    }
+
+    if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) {
+        Uint8 data = packet->controllerState.rgucButtons[1];
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {
+        Uint8 data = packet->controllerState.rgucButtons[2];
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        axis = (data & 0x80) ? 32767 : -32768;
+        SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
+    }
+
+    axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8);
+    axis = ApplyStickCalibration(ctx, 0, 0, axis);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
+
+    axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4);
+    axis = ApplyStickCalibration(ctx, 0, 1, axis);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis);
+
+    axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8);
+    axis = ApplyStickCalibration(ctx, 1, 0, axis);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
+
+    axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4);
+    axis = ApplyStickCalibration(ctx, 1, 1, axis);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis);
+
+    /* High nibble of battery/connection byte is battery level, low nibble is connection status
+     * LSB of connection nibble is USB/Switch connection status
+     */
+    if (packet->controllerState.ucBatteryAndConnection & 0x1) {
+        joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+    } else {
+        /* LSB of the battery nibble is used to report charging.
+         * The battery level is reported from 0(empty)-8(full)
+         */
+        int level = (packet->controllerState.ucBatteryAndConnection & 0xE0) >> 4;
+        if (level == 0) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_EMPTY;
+        } else if (level <= 2) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_LOW;
+        } else if (level <= 6) {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_MEDIUM;
+        } else {
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
+        }
+    }
+
+    ctx->m_lastFullState = *packet;
+}
+
+static void
+HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+
+    while (ReadInput(ctx) > 0) {
+        switch (ctx->m_rgucReadBuffer[0]) {
+        case k_eSwitchInputReportIDs_SimpleControllerState:
+            HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
+            break;
+        case k_eSwitchInputReportIDs_FullControllerState:
+            HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (ctx->m_nRumbleExpiration) {
+        Uint32 now = SDL_GetTicks();
+        if (SDL_TICKS_PASSED(now, ctx->m_nRumbleExpiration)) {
+            HIDAPI_DriverSwitch_Rumble(joystick, dev, context, 0, 0, 0);
+        }
+    }
+}
+
+static void
+HIDAPI_DriverSwitch_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+
+    /* Restore simple input mode for other applications */
+    SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState);
+
+    SDL_free(context);
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch =
+{
+    SDL_HINT_JOYSTICK_HIDAPI_SWITCH,
+    SDL_TRUE,
+    HIDAPI_DriverSwitch_IsSupportedDevice,
+    HIDAPI_DriverSwitch_GetDeviceName,
+    HIDAPI_DriverSwitch_Init,
+    HIDAPI_DriverSwitch_Rumble,
+    HIDAPI_DriverSwitch_Update,
+    HIDAPI_DriverSwitch_Quit
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_SWITCH */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 363 - 0
src/joystick/hidapi/SDL_hidapi_xbox360.c

@@ -0,0 +1,363 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_events.h"
+#include "SDL_timer.h"
+#include "SDL_joystick.h"
+#include "SDL_gamecontroller.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
+
+#define USB_PACKET_LENGTH   64
+
+typedef struct
+{
+    Uint16 vendor_id;
+    Uint16 product_id;
+    const char *name;
+} SDL_DriverXbox360_DeviceName;
+
+static const SDL_DriverXbox360_DeviceName xbox360_devicenames[] = {
+    { 0x0079, 0x18d4, "GPD Win 2 X-Box Controller" },
+    { 0x044f, 0xb326, "Thrustmaster Gamepad GP XID" },
+    { 0x045e, 0x028e, "Microsoft X-Box 360 pad" },
+    { 0x045e, 0x028f, "Microsoft X-Box 360 pad v2" },
+    { 0x045e, 0x0291, "Xbox 360 Wireless Receiver (XBOX)" },
+    { 0x045e, 0x0719, "Xbox 360 Wireless Receiver" },
+    { 0x046d, 0xc21d, "Logitech Gamepad F310" },
+    { 0x046d, 0xc21e, "Logitech Gamepad F510" },
+    { 0x046d, 0xc21f, "Logitech Gamepad F710" },
+    { 0x046d, 0xc242, "Logitech Chillstream Controller" },
+    { 0x046d, 0xcaa3, "Logitech DriveFx Racing Wheel" },
+    { 0x056e, 0x2004, "Elecom JC-U3613M" },
+    { 0x06a3, 0xf51a, "Saitek P3600" },
+    { 0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller" },
+    { 0x0738, 0x4718, "Mad Catz Street Fighter IV FightStick SE" },
+    { 0x0738, 0x4726, "Mad Catz Xbox 360 Controller" },
+    { 0x0738, 0x4728, "Mad Catz Street Fighter IV FightPad" },
+    { 0x0738, 0x4736, "Mad Catz MicroCon Gamepad" },
+    { 0x0738, 0x4738, "Mad Catz Wired Xbox 360 Controller (SFIV)" },
+    { 0x0738, 0x4740, "Mad Catz Beat Pad" },
+    { 0x0738, 0x4758, "Mad Catz Arcade Game Stick" },
+    { 0x0738, 0x9871, "Mad Catz Portable Drum" },
+    { 0x0738, 0xb726, "Mad Catz Xbox controller - MW2" },
+    { 0x0738, 0xb738, "Mad Catz MVC2TE Stick 2" },
+    { 0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad" },
+    { 0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360" },
+    { 0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360" },
+    { 0x0738, 0xcb29, "Saitek Aviator Stick AV8R02" },
+    { 0x0738, 0xf738, "Super SFIV FightStick TE S" },
+    { 0x07ff, 0xffff, "Mad Catz GamePad" },
+    { 0x0e6f, 0x0105, "HSM3 Xbox360 dancepad" },
+    { 0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360" },
+    { 0x0e6f, 0x011f, "Rock Candy Gamepad Wired Controller" },
+    { 0x0e6f, 0x0131, "PDP EA Sports Controller" },
+    { 0x0e6f, 0x0133, "Xbox 360 Wired Controller" },
+    { 0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller" },
+    { 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360" },
+    { 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360" },
+    { 0x0e6f, 0x0301, "Logic3 Controller" },
+    { 0x0e6f, 0x0401, "Logic3 Controller" },
+    { 0x0e6f, 0x0413, "Afterglow AX.1 Gamepad for Xbox 360" },
+    { 0x0e6f, 0x0501, "PDP Xbox 360 Controller" },
+    { 0x0e6f, 0xf900, "PDP Afterglow AX.1" },
+    { 0x0f0d, 0x000a, "Hori Co. DOA4 FightStick" },
+    { 0x0f0d, 0x000c, "Hori PadEX Turbo" },
+    { 0x0f0d, 0x000d, "Hori Fighting Stick EX2" },
+    { 0x0f0d, 0x0016, "Hori Real Arcade Pro.EX" },
+    { 0x0f0d, 0x001b, "Hori Real Arcade Pro VX" },
+    { 0x11c9, 0x55f0, "Nacon GC-100XF" },
+    { 0x12ab, 0x0004, "Honey Bee Xbox360 dancepad" },
+    { 0x12ab, 0x0301, "PDP AFTERGLOW AX.1" },
+    { 0x12ab, 0x0303, "Mortal Kombat Klassic FightStick" },
+    { 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer" },
+    { 0x1430, 0xf801, "RedOctane Controller" },
+    { 0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller" },
+    { 0x1532, 0x0037, "Razer Sabertooth" },
+    { 0x15e4, 0x3f00, "Power A Mini Pro Elite" },
+    { 0x15e4, 0x3f0a, "Xbox Airflo wired controller" },
+    { 0x15e4, 0x3f10, "Batarang Xbox 360 controller" },
+    { 0x162e, 0xbeef, "Joytech Neo-Se Take2" },
+    { 0x1689, 0xfd00, "Razer Onza Tournament Edition" },
+    { 0x1689, 0xfd01, "Razer Onza Classic Edition" },
+    { 0x1689, 0xfe00, "Razer Sabertooth" },
+    { 0x1bad, 0x0002, "Harmonix Rock Band Guitar" },
+    { 0x1bad, 0x0003, "Harmonix Rock Band Drumkit" },
+    { 0x1bad, 0x0130, "Ion Drum Rocker" },
+    { 0x1bad, 0xf016, "Mad Catz Xbox 360 Controller" },
+    { 0x1bad, 0xf018, "Mad Catz Street Fighter IV SE Fighting Stick" },
+    { 0x1bad, 0xf019, "Mad Catz Brawlstick for Xbox 360" },
+    { 0x1bad, 0xf021, "Mad Cats Ghost Recon FS GamePad" },
+    { 0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)" },
+    { 0x1bad, 0xf025, "Mad Catz Call Of Duty" },
+    { 0x1bad, 0xf027, "Mad Catz FPS Pro" },
+    { 0x1bad, 0xf028, "Street Fighter IV FightPad" },
+    { 0x1bad, 0xf02e, "Mad Catz Fightpad" },
+    { 0x1bad, 0xf030, "Mad Catz Xbox 360 MC2 MicroCon Racing Wheel" },
+    { 0x1bad, 0xf036, "Mad Catz MicroCon GamePad Pro" },
+    { 0x1bad, 0xf038, "Street Fighter IV FightStick TE" },
+    { 0x1bad, 0xf039, "Mad Catz MvC2 TE" },
+    { 0x1bad, 0xf03a, "Mad Catz SFxT Fightstick Pro" },
+    { 0x1bad, 0xf03d, "Street Fighter IV Arcade Stick TE - Chun Li" },
+    { 0x1bad, 0xf03e, "Mad Catz MLG FightStick TE" },
+    { 0x1bad, 0xf03f, "Mad Catz FightStick SoulCaliber" },
+    { 0x1bad, 0xf042, "Mad Catz FightStick TES+" },
+    { 0x1bad, 0xf080, "Mad Catz FightStick TE2" },
+    { 0x1bad, 0xf501, "HoriPad EX2 Turbo" },
+    { 0x1bad, 0xf502, "Hori Real Arcade Pro.VX SA" },
+    { 0x1bad, 0xf503, "Hori Fighting Stick VX" },
+    { 0x1bad, 0xf504, "Hori Real Arcade Pro. EX" },
+    { 0x1bad, 0xf505, "Hori Fighting Stick EX2B" },
+    { 0x1bad, 0xf506, "Hori Real Arcade Pro.EX Premium VLX" },
+    { 0x1bad, 0xf900, "Harmonix Xbox 360 Controller" },
+    { 0x1bad, 0xf901, "Gamestop Xbox 360 Controller" },
+    { 0x1bad, 0xf903, "Tron Xbox 360 controller" },
+    { 0x1bad, 0xf904, "PDP Versus Fighting Pad" },
+    { 0x1bad, 0xf906, "MortalKombat FightStick" },
+    { 0x1bad, 0xfa01, "MadCatz GamePad" },
+    { 0x1bad, 0xfd00, "Razer Onza TE" },
+    { 0x1bad, 0xfd01, "Razer Onza" },
+    { 0x24c6, 0x5000, "Razer Atrox Arcade Stick" },
+    { 0x24c6, 0x5300, "PowerA MINI PROEX Controller" },
+    { 0x24c6, 0x5303, "Xbox Airflo wired controller" },
+    { 0x24c6, 0x530a, "Xbox 360 Pro EX Controller" },
+    { 0x24c6, 0x531a, "PowerA Pro Ex" },
+    { 0x24c6, 0x5397, "FUS1ON Tournament Controller" },
+    { 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo" },
+    { 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA" },
+    { 0x24c6, 0x5502, "Hori Fighting Stick VX Alt" },
+    { 0x24c6, 0x5503, "Hori Fighting Edge" },
+    { 0x24c6, 0x5506, "Hori SOULCALIBUR V Stick" },
+    { 0x24c6, 0x550d, "Hori GEM Xbox controller" },
+    { 0x24c6, 0x550e, "Hori Real Arcade Pro V Kai 360" },
+    { 0x24c6, 0x5b00, "ThrustMaster Ferrari 458 Racing Wheel" },
+    { 0x24c6, 0x5b02, "Thrustmaster, Inc. GPX Controller" },
+    { 0x24c6, 0x5b03, "Thrustmaster Ferrari 458 Racing Wheel" },
+    { 0x24c6, 0x5d04, "Razer Sabertooth" },
+    { 0x24c6, 0xfafe, "Rock Candy Gamepad for Xbox 360" },
+};
+
+typedef struct {
+    Uint8 last_state[USB_PACKET_LENGTH];
+    Uint32 rumble_expiration;
+} SDL_DriverXbox360_Context;
+
+
+static SDL_bool
+HIDAPI_DriverXbox360_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, int interface_number, Uint16 usage_page, Uint16 usage)
+{
+#ifdef __MACOSX__
+    return SDL_IsJoystickXbox360(vendor_id, product_id) || SDL_IsJoystickXboxOne(vendor_id, product_id);
+#else
+    return SDL_IsJoystickXbox360(vendor_id, product_id);
+#endif
+}
+
+static const char *
+HIDAPI_DriverXbox360_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+{
+    int i;
+
+    for (i = 0; i < SDL_arraysize(xbox360_devicenames); ++i) {
+        const SDL_DriverXbox360_DeviceName *entry = &xbox360_devicenames[i];
+        if (vendor_id == entry->vendor_id && product_id == entry->product_id) {
+            return entry->name;
+        }
+    }
+    return NULL;
+}
+
+static SDL_bool SetSlotLED(hid_device *dev, Uint8 slot)
+{
+    const Uint8 led_packet[] = { 0x01, 0x03, (2 + slot) };
+
+    if (hid_write(dev, led_packet, sizeof(led_packet)) != sizeof(led_packet)) {
+		return SDL_FALSE;
+	}
+	return SDL_TRUE;
+}
+
+static SDL_bool
+HIDAPI_DriverXbox360_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+{
+    SDL_DriverXbox360_Context *ctx;
+
+    ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    *context = ctx;
+
+    /* Set the controller LED */
+	SetSlotLED(dev, (joystick->instance_id % 4));
+
+    /* Initialize the joystick capabilities */
+    joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
+    joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+
+    return SDL_TRUE;
+}
+
+static int
+HIDAPI_DriverXbox360_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context;
+#ifdef __MACOSX__
+    /* On Mac OS X the 360Controller driver uses this short report,
+       and we need to prefix it with a magic token so hidapi passes it through untouched
+     */
+    Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 };
+
+    rumble_packet[6+2] = (low_frequency_rumble >> 8);
+    rumble_packet[6+3] = (high_frequency_rumble >> 8);
+#else
+    Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+    rumble_packet[3] = (low_frequency_rumble >> 8);
+    rumble_packet[4] = (high_frequency_rumble >> 8);
+#endif
+
+    if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
+        return SDL_SetError("Couldn't send rumble packet");
+    }
+
+    if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
+        ctx->rumble_expiration = SDL_GetTicks() + duration_ms;
+    } else {
+        ctx->rumble_expiration = 0;
+    }
+    return 0;
+}
+
+static void
+HIDAPI_DriverXbox360_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXbox360_Context *ctx, Uint8 *data, int size)
+{
+    Sint16 axis;
+#ifdef __MACOSX__
+	const SDL_bool invert_y_axes = SDL_FALSE;
+#else
+	const SDL_bool invert_y_axes = SDL_TRUE;
+#endif
+
+    if (ctx->last_state[2] != data[2]) {
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data[2] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data[2] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data[2] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data[2] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[2] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[2] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[2] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[2] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    if (ctx->last_state[3] != data[3]) {
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[3] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[3] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[3] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[3] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[3] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[3] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[3] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    axis = ((int)data[4] * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
+    axis = ((int)data[5] * 257) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
+    axis = *(Sint16*)(&data[6]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
+    axis = *(Sint16*)(&data[8]);
+	if (invert_y_axes) {
+		axis = ~axis;
+	}
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis);
+    axis = *(Sint16*)(&data[10]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
+    axis = *(Sint16*)(&data[12]);
+	if (invert_y_axes) {
+		axis = ~axis;
+	}
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
+
+    SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
+}
+
+static void
+HIDAPI_DriverXbox360_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context;
+    Uint8 data[USB_PACKET_LENGTH];
+    int size;
+
+    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+        switch (data[0]) {
+        case 0x00:
+            HIDAPI_DriverXbox360_HandleStatePacket(joystick, dev, ctx, data, size);
+            break;
+        default:
+#ifdef DEBUG_JOYSTICK
+            SDL_Log("Unknown Xbox 360 packet: 0x%.2x\n", data[0]);
+#endif
+            break;
+        }
+    }
+
+    if (ctx->rumble_expiration) {
+        Uint32 now = SDL_GetTicks();
+        if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
+            HIDAPI_DriverXbox360_Rumble(joystick, dev, context, 0, 0, 0);
+        }
+    }
+}
+
+static void
+HIDAPI_DriverXbox360_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_free(context);
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 =
+{
+    SDL_HINT_JOYSTICK_HIDAPI_XBOX360,
+    SDL_TRUE,
+    HIDAPI_DriverXbox360_IsSupportedDevice,
+    HIDAPI_DriverXbox360_GetDeviceName,
+    HIDAPI_DriverXbox360_Init,
+    HIDAPI_DriverXbox360_Rumble,
+    HIDAPI_DriverXbox360_Update,
+    HIDAPI_DriverXbox360_Quit
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 364 - 0
src/joystick/hidapi/SDL_hidapi_xboxone.c

@@ -0,0 +1,364 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_events.h"
+#include "SDL_timer.h"
+#include "SDL_joystick.h"
+#include "SDL_gamecontroller.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
+
+#define USB_PACKET_LENGTH   64
+
+typedef struct
+{
+    Uint16 vendor_id;
+    Uint16 product_id;
+    const char *name;
+} SDL_DriverXboxOne_DeviceName;
+
+static const SDL_DriverXboxOne_DeviceName xboxone_devicenames[] = {
+    { 0x045e, 0x02d1, "Microsoft X-Box One pad" },
+    { 0x045e, 0x02dd, "Microsoft X-Box One pad (Firmware 2015)" },
+    { 0x045e, 0x02e3, "Microsoft X-Box One Elite pad" },
+    { 0x045e, 0x02ea, "Microsoft X-Box One S pad" },
+    { 0x045e, 0x02ff, "Microsoft X-Box One pad" },
+    { 0x0738, 0x4a01, "Mad Catz FightStick TE 2" },
+    { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller" },
+    { 0x0e6f, 0x013a, "PDP Xbox One Controller" },
+    { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One" },
+    { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller" },
+    { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick" },
+    { 0x0e6f, 0x0161, "PDP Xbox One Controller" },
+    { 0x0e6f, 0x0162, "PDP Xbox One Controller" },
+    { 0x0e6f, 0x0163, "PDP Xbox One Controller" },
+    { 0x0e6f, 0x0164, "PDP Battlefield One" },
+    { 0x0e6f, 0x0165, "PDP Titanfall 2" },
+    { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015" },
+    { 0x0e6f, 0x02ab, "PDP Controller for Xbox One" },
+    { 0x0e6f, 0x02a4, "PDP Wired Controller for Xbox One - Stealth Series" },
+    { 0x0e6f, 0x0346, "Rock Candy Gamepad for Xbox One 2016" },
+    { 0x0f0d, 0x0063, "Hori Real Arcade Pro Hayabusa (USA) Xbox One" },
+    { 0x0f0d, 0x0067, "HORIPAD ONE" },
+    { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One" },
+    { 0x1532, 0x0a00, "Razer Atrox Arcade Stick" },
+    { 0x1532, 0x0a03, "Razer Wildcat" },
+    { 0x24c6, 0x541a, "PowerA Xbox One Mini Wired Controller" },
+    { 0x24c6, 0x542a, "Xbox ONE spectra" },
+    { 0x24c6, 0x543a, "PowerA Xbox One wired controller" },
+    { 0x24c6, 0x551a, "PowerA FUSION Pro Controller" },
+    { 0x24c6, 0x561a, "PowerA FUSION Controller" },
+};
+
+/*
+ * This packet is required for all Xbox One pads with 2015
+ * or later firmware installed (or present from the factory).
+ */
+static const Uint8 xboxone_fw2015_init[] = {
+    0x05, 0x20, 0x00, 0x01, 0x00
+};
+
+/*
+ * This packet is required for the Titanfall 2 Xbox One pads
+ * (0x0e6f:0x0165) to finish initialization and for Hori pads
+ * (0x0f0d:0x0067) to make the analog sticks work.
+ */
+static const Uint8 xboxone_hori_init[] = {
+    0x01, 0x20, 0x00, 0x09, 0x00, 0x04, 0x20, 0x3a,
+    0x00, 0x00, 0x00, 0x80, 0x00
+};
+
+/*
+ * This packet is required for some of the PDP pads to start
+ * sending input reports. These pads include: (0x0e6f:0x02ab),
+ * (0x0e6f:0x02a4).
+ */
+static const Uint8 xboxone_pdp_init1[] = {
+    0x0a, 0x20, 0x00, 0x03, 0x00, 0x01, 0x14
+};
+
+/*
+ * This packet is required for some of the PDP pads to start
+ * sending input reports. These pads include: (0x0e6f:0x02ab),
+ * (0x0e6f:0x02a4).
+ */
+static const Uint8 xboxone_pdp_init2[] = {
+    0x06, 0x20, 0x00, 0x02, 0x01, 0x00
+};
+
+/*
+ * A specific rumble packet is required for some PowerA pads to start
+ * sending input reports. One of those pads is (0x24c6:0x543a).
+ */
+static const Uint8 xboxone_rumblebegin_init[] = {
+    0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
+    0x1D, 0x1D, 0xFF, 0x00, 0x00
+};
+
+/*
+ * A rumble packet with zero FF intensity will immediately
+ * terminate the rumbling required to init PowerA pads.
+ * This should happen fast enough that the motors don't
+ * spin up to enough speed to actually vibrate the gamepad.
+ */
+static const Uint8 xboxone_rumbleend_init[] = {
+    0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/*
+ * This specifies the selection of init packets that a gamepad
+ * will be sent on init *and* the order in which they will be
+ * sent. The correct sequence number will be added when the
+ * packet is going to be sent.
+ */
+typedef struct {
+    Uint16 vendor_id;
+    Uint16 product_id;
+    const Uint8 *data;
+    int size;
+} SDL_DriverXboxOne_InitPacket;
+
+static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {
+    { 0x0e6f, 0x0165, xboxone_hori_init, sizeof(xboxone_hori_init) },
+    { 0x0f0d, 0x0067, xboxone_hori_init, sizeof(xboxone_hori_init) },
+    { 0x0000, 0x0000, xboxone_fw2015_init, sizeof(xboxone_fw2015_init) },
+    { 0x0e6f, 0x0246, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) },
+    { 0x0e6f, 0x0246, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) },
+    { 0x0e6f, 0x02ab, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) },
+    { 0x0e6f, 0x02ab, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) },
+    { 0x0e6f, 0x02a4, xboxone_pdp_init1, sizeof(xboxone_pdp_init1) },
+    { 0x0e6f, 0x02a4, xboxone_pdp_init2, sizeof(xboxone_pdp_init2) },
+    { 0x24c6, 0x541a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) },
+    { 0x24c6, 0x542a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) },
+    { 0x24c6, 0x543a, xboxone_rumblebegin_init, sizeof(xboxone_rumblebegin_init) },
+    { 0x24c6, 0x541a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) },
+    { 0x24c6, 0x542a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) },
+    { 0x24c6, 0x543a, xboxone_rumbleend_init, sizeof(xboxone_rumbleend_init) },
+};
+
+typedef struct {
+    Uint8 sequence;
+    Uint8 last_state[USB_PACKET_LENGTH];
+    Uint32 rumble_expiration;
+} SDL_DriverXboxOne_Context;
+
+
+static SDL_bool
+HIDAPI_DriverXboxOne_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, int interface_number, Uint16 usage_page, Uint16 usage)
+{
+    return SDL_IsJoystickXboxOne(vendor_id, product_id);
+}
+
+static const char *
+HIDAPI_DriverXboxOne_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+{
+    int i;
+
+    for (i = 0; i < SDL_arraysize(xboxone_devicenames); ++i) {
+        const SDL_DriverXboxOne_DeviceName *entry = &xboxone_devicenames[i];
+        if (vendor_id == entry->vendor_id && product_id == entry->product_id) {
+            return entry->name;
+        }
+    }
+    return NULL;
+}
+
+static SDL_bool
+HIDAPI_DriverXboxOne_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+{
+    SDL_DriverXboxOne_Context *ctx;
+    int i;
+    Uint8 init_packet[USB_PACKET_LENGTH];
+
+    ctx = (SDL_DriverXboxOne_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    *context = ctx;
+
+    /* Send the controller init data */
+    for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) {
+        const SDL_DriverXboxOne_InitPacket *packet = &xboxone_init_packets[i];
+        if (!packet->vendor_id || (vendor_id == packet->vendor_id && product_id == packet->product_id)) {
+            SDL_memcpy(init_packet, packet->data, packet->size);
+            init_packet[2] = ctx->sequence++;
+            if (hid_write(dev, init_packet, packet->size) != packet->size) {
+                SDL_SetError("Couldn't write Xbox One initialization packet");
+                SDL_free(ctx);
+                return SDL_FALSE;
+            }
+        }
+    }
+
+    /* Initialize the joystick capabilities */
+    joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
+    joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+
+    return SDL_TRUE;
+}
+
+static int
+HIDAPI_DriverXboxOne_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context;
+    Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF };
+
+    /* The Rock Candy Xbox One Controller limits the range of
+         low frequency rumble strength in the range of [0 - 0x99]
+         high frequency rumble strength in the range of [0 - 0x82]
+
+       I think the valid range of rumble at the firmware level is [0 - 0x7F]
+    */
+    rumble_packet[2] = ctx->sequence++;
+    rumble_packet[8] = (low_frequency_rumble >> 9);
+    rumble_packet[9] = (high_frequency_rumble >> 9);
+
+    if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
+        return SDL_SetError("Couldn't send rumble packet");
+    }
+
+    if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
+        ctx->rumble_expiration = SDL_GetTicks() + duration_ms;
+    } else {
+        ctx->rumble_expiration = 0;
+    }
+    return 0;
+}
+
+static void
+HIDAPI_DriverXboxOne_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
+{
+    Sint16 axis;
+
+    if (ctx->last_state[4] != data[4]) {
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data[4] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data[4] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data[4] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data[4] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data[4] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data[4] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    if (ctx->last_state[5] != data[5]) {
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data[5] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data[5] & 0x02) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data[5] & 0x04) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data[5] & 0x08) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data[5] & 0x10) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data[5] & 0x20) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data[5] & 0x40) ? SDL_PRESSED : SDL_RELEASED);
+        SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data[5] & 0x80) ? SDL_PRESSED : SDL_RELEASED);
+    }
+
+    axis = ((int)*(Sint16*)(&data[6]) * 64) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
+    axis = ((int)*(Sint16*)(&data[8]) * 64) - 32768;
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
+    axis = *(Sint16*)(&data[10]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
+    axis = *(Sint16*)(&data[12]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis);
+    axis = *(Sint16*)(&data[14]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
+    axis = *(Sint16*)(&data[16]);
+    SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis);
+
+    SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
+}
+
+static void
+HIDAPI_DriverXboxOne_HandleModePacket(SDL_Joystick *joystick, hid_device *dev, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
+{
+    if (data[1] == 0x30) {
+        /* The Xbox One S controller needs acks for mode reports */
+        const Uint8 seqnum = data[2];
+        const Uint8 ack[] = { 0x01, 0x20, seqnum, 0x09, 0x00, 0x07, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
+        hid_write(dev, ack, sizeof(ack));
+    }
+
+    SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data[4] & 0x01) ? SDL_PRESSED : SDL_RELEASED);
+}
+
+static void
+HIDAPI_DriverXboxOne_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context;
+    Uint8 data[USB_PACKET_LENGTH];
+    int size;
+
+    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+        switch (data[0]) {
+        case 0x20:
+            HIDAPI_DriverXboxOne_HandleStatePacket(joystick, dev, ctx, data, size);
+            break;
+        case 0x07:
+            HIDAPI_DriverXboxOne_HandleModePacket(joystick, dev, ctx, data, size);
+            break;
+        default:
+#ifdef DEBUG_JOYSTICK
+            SDL_Log("Unknown Xbox One packet: 0x%.2x\n", data[0]);
+#endif
+            break;
+        }
+    }
+
+    if (ctx->rumble_expiration) {
+        Uint32 now = SDL_GetTicks();
+        if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
+            HIDAPI_DriverXboxOne_Rumble(joystick, dev, context, 0, 0, 0);
+        }
+    }
+}
+
+static void
+HIDAPI_DriverXboxOne_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+{
+    SDL_free(context);
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne =
+{
+    SDL_HINT_JOYSTICK_HIDAPI_XBOXONE,
+    SDL_TRUE,
+    HIDAPI_DriverXboxOne_IsSupportedDevice,
+    HIDAPI_DriverXboxOne_GetDeviceName,
+    HIDAPI_DriverXboxOne_Init,
+    HIDAPI_DriverXboxOne_Rumble,
+    HIDAPI_DriverXboxOne_Update,
+    HIDAPI_DriverXboxOne_Quit
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_XBOXONE */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
+
+/* vi: set ts=4 sw=4 expandtab: */

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

@@ -0,0 +1,541 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_endian.h"
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_timer.h"
+#include "SDL_joystick.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+struct joystick_hwdata
+{
+    SDL_HIDAPI_DeviceDriver *driver;
+    void *context;
+
+    hid_device *dev;
+};
+
+typedef struct _SDL_HIDAPI_Device
+{
+    SDL_JoystickID instance_id;
+    char *name;
+    char *path;
+    Uint16 vendor_id;
+    Uint16 product_id;
+    SDL_JoystickGUID guid;
+    int interface_number;   /* Available on Windows and Linux */
+    Uint16 usage_page;      /* Available on Windows and Mac OS X */
+    Uint16 usage;           /* Available on Windows and Mac OS X */
+    SDL_HIDAPI_DeviceDriver *driver;
+
+    /* Used during scanning for device changes */
+    SDL_bool seen;
+
+    struct _SDL_HIDAPI_Device *next;
+} SDL_HIDAPI_Device;
+
+static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
+#ifdef SDL_JOYSTICK_HIDAPI_PS4
+    &SDL_HIDAPI_DriverPS4,
+#endif
+#ifdef SDL_JOYSTICK_HIDAPI_STEAM
+    &SDL_HIDAPI_DriverSteam,
+#endif
+#ifdef SDL_JOYSTICK_HIDAPI_SWITCH
+    &SDL_HIDAPI_DriverSwitch,
+#endif
+#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
+    &SDL_HIDAPI_DriverXbox360,
+#endif
+#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
+    &SDL_HIDAPI_DriverXboxOne,
+#endif
+};
+static SDL_HIDAPI_Device *SDL_HIDAPI_devices;
+static int SDL_HIDAPI_numjoysticks = 0;
+static Uint32 SDL_HIDAPI_last_detect = 0;
+
+static SDL_HIDAPI_DeviceDriver *
+HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device)
+{
+    int i;
+
+    if (SDL_ShouldIgnoreJoystick(device->name, device->guid)) {
+        return NULL;
+    }
+
+    for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
+        SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
+        if (driver->enabled && driver->IsSupportedDevice(device->vendor_id, device->product_id, device->interface_number, device->usage_page, device->usage)) {
+            return driver;
+        }
+    }
+    return NULL;
+}
+
+static SDL_HIDAPI_Device *
+HIDAPI_GetJoystickByIndex(int device_index)
+{
+    SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    while (device) {
+        if (device->driver) {
+            if (device_index == 0) {
+                break;
+            }
+            --device_index;
+        }
+        device = device->next;
+    }
+    return device;
+}
+
+static SDL_HIDAPI_Device *
+HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id)
+{
+    SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    while (device) {
+        if (device->vendor_id == vendor_id && device->product_id == product_id &&
+            SDL_strcmp(device->path, path) == 0) {
+            break;
+        }
+        device = device->next;
+    }
+    return device;
+}
+
+static void SDLCALL
+SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    int i;
+    SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    SDL_bool enabled = (!hint || !*hint || ((*hint != '0') && (SDL_strcasecmp(hint, "false") != 0)));
+
+    if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI) == 0) {
+        for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
+            SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
+            driver->enabled = SDL_GetHintBoolean(driver->hint, enabled);
+        }
+    } else {
+        for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
+            SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
+            if (SDL_strcmp(name, driver->hint) == 0) {
+                driver->enabled = enabled;
+                break;
+            }
+        }
+    }
+
+    /* Update device list if driver availability changes */
+    while (device) {
+        if (device->driver) {
+            if (!device->driver->enabled) {
+                device->driver = NULL;
+
+                --SDL_HIDAPI_numjoysticks;
+
+                SDL_PrivateJoystickRemoved(device->instance_id);
+            }
+        } else {
+            device->driver = HIDAPI_GetDeviceDriver(device);
+            if (device->driver) {
+                device->instance_id = SDL_GetNextJoystickInstanceID();
+
+                ++SDL_HIDAPI_numjoysticks;
+
+                SDL_PrivateJoystickAdded(device->instance_id);
+            }
+        }
+        device = device->next;
+    }
+}
+
+static void HIDAPI_JoystickDetect(void);
+
+static int
+HIDAPI_JoystickInit(void)
+{
+    int i;
+
+    if (hid_init() < 0) {
+        SDL_SetError("Couldn't initialize hidapi");
+        return -1;
+    }
+
+    for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
+        SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
+        SDL_AddHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL);
+    }
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI,
+                        SDL_HIDAPIDriverHintChanged, NULL);
+    SDL_HIDAPI_last_detect = 0;
+    HIDAPI_JoystickDetect();
+    return 0;
+}
+
+static int
+HIDAPI_JoystickGetCount(void)
+{
+    return SDL_HIDAPI_numjoysticks;
+}
+
+static void
+HIDAPI_AddDevice(struct hid_device_info *info)
+{
+    SDL_HIDAPI_Device *device;
+    SDL_HIDAPI_Device *curr, *last = NULL;
+
+    for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) {
+        continue;
+    }
+
+    device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device));
+    if (!device) {
+        return;
+    }
+    device->instance_id = -1;
+    device->seen = SDL_TRUE;
+    device->vendor_id = info->vendor_id;
+    device->product_id = info->product_id;
+    device->interface_number = info->interface_number;
+    device->usage_page = info->usage_page;
+    device->usage = info->usage;
+    {
+        /* FIXME: Is there any way to tell whether this is a Bluetooth device? */
+        const Uint16 vendor = device->vendor_id;
+        const Uint16 product = device->product_id;
+        const Uint16 version = 0;
+        Uint16 *guid16 = (Uint16 *)device->guid.data;
+
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
+        *guid16++ = 0;
+        *guid16++ = SDL_SwapLE16(vendor);
+        *guid16++ = 0;
+        *guid16++ = SDL_SwapLE16(product);
+        *guid16++ = 0;
+        *guid16++ = SDL_SwapLE16(version);
+        *guid16++ = 0;
+
+        /* Note that this is a HIDAPI device for special handling elsewhere */
+        device->guid.data[14] = 'h';
+        device->guid.data[15] = 0;
+    }
+    device->driver = HIDAPI_GetDeviceDriver(device);
+
+    if (device->driver) {
+        const char *name = device->driver->GetDeviceName(device->vendor_id, device->product_id);
+        if (name) {
+            device->name = SDL_strdup(name);
+        }
+    }
+
+    if (!device->name && info->manufacturer_string && info->product_string) {
+        char *manufacturer_string = SDL_iconv_string("UTF-8", "WCHAR_T", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t));
+        char *product_string = SDL_iconv_string("UTF-8", "WCHAR_T", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t));
+        if (!manufacturer_string && !product_string) {
+            if (sizeof(wchar_t) == sizeof(Uint16)) {
+                manufacturer_string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t));
+                product_string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t));
+            } else if (sizeof(wchar_t) == sizeof(Uint32)) {
+                manufacturer_string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char*)info->manufacturer_string, (SDL_wcslen(info->manufacturer_string)+1)*sizeof(wchar_t));
+                product_string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char*)info->product_string, (SDL_wcslen(info->product_string)+1)*sizeof(wchar_t));
+            }
+        }
+        if (manufacturer_string && product_string) {
+            size_t name_size = (SDL_strlen(manufacturer_string) + 1 + SDL_strlen(product_string) + 1);
+            device->name = (char *)SDL_malloc(name_size);
+            if (device->name) {
+                SDL_snprintf(device->name, name_size, "%s %s", manufacturer_string, product_string);
+            }
+        }
+        if (manufacturer_string) {
+            SDL_free(manufacturer_string);
+        }
+        if (product_string) {
+            SDL_free(product_string);
+        }
+    }
+    if (!device->name) {
+        size_t name_size = (6 + 1 + 6 + 1);
+        device->name = (char *)SDL_malloc(name_size);
+        if (!device->name) {
+            SDL_free(device);
+            return;
+        }
+        SDL_snprintf(device->name, name_size, "0x%.4x/0x%.4x", info->vendor_id, info->product_id);
+    }
+
+    device->path = SDL_strdup(info->path);
+    if (!device->path) {
+        SDL_free(device->name);
+        SDL_free(device);
+        return;
+    }
+
+#ifdef DEBUG_HIDAPI
+    SDL_Log("Adding HIDAPI device '%s' interface %d, usage page 0x%.4x, usage 0x%.4x\n", device->name, device->interface_number, device->usage_page, device->usage);
+#endif
+
+    /* Add it to the list */
+    if (last) {
+        last->next = device;
+    } else {
+        SDL_HIDAPI_devices = device;
+    }
+
+    if (device->driver) {
+        /* It's a joystick! */
+        device->instance_id = SDL_GetNextJoystickInstanceID();
+
+        ++SDL_HIDAPI_numjoysticks;
+
+        SDL_PrivateJoystickAdded(device->instance_id);
+    }
+}
+
+
+static void
+HIDAPI_DelDevice(SDL_HIDAPI_Device *device, SDL_bool send_event)
+{
+    SDL_HIDAPI_Device *curr, *last;
+    for (curr = SDL_HIDAPI_devices, last = NULL; curr; last = curr, curr = curr->next) {
+        if (curr == device) {
+            if (last) {
+                last->next = curr->next;
+            } else {
+                SDL_HIDAPI_devices = curr->next;
+            }
+
+            if (device->driver && send_event) {
+                /* Need to decrement the joystick count before we post the event */
+                --SDL_HIDAPI_numjoysticks;
+
+                SDL_PrivateJoystickRemoved(device->instance_id);
+            }
+
+            SDL_free(device->name);
+            SDL_free(device->path);
+            SDL_free(device);
+            return;
+        }
+    }
+}
+
+static void
+HIDAPI_UpdateDeviceList(void)
+{
+    SDL_HIDAPI_Device *device;
+    struct hid_device_info *devs, *info;
+
+    /* Prepare the existing device list */
+    device = SDL_HIDAPI_devices;
+    while (device) {
+        device->seen = SDL_FALSE;
+        device = device->next;
+    }
+
+    /* Enumerate the devices */
+    devs = hid_enumerate(0, 0);
+    if (devs) {
+        for (info = devs; info; info = info->next) {
+            device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id);
+            if (device) {
+                device->seen = SDL_TRUE;
+            } else {
+                HIDAPI_AddDevice(info);
+            }
+        }
+        hid_free_enumeration(devs);
+    }
+
+    /* Remove any devices that weren't seen */
+    device = SDL_HIDAPI_devices;
+    while (device) {
+        SDL_HIDAPI_Device *next = device->next;
+
+        if (!device->seen) {
+            HIDAPI_DelDevice(device, SDL_TRUE);
+        }
+        device = next;
+    }
+}
+
+SDL_bool
+HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id)
+{
+    SDL_HIDAPI_Device *device;
+
+    /* Make sure the device list is completely up to date when we check for device presence */
+    HIDAPI_UpdateDeviceList();
+
+    device = SDL_HIDAPI_devices;
+    while (device) {
+        if (device->vendor_id == vendor_id && device->product_id == product_id && device->driver) {
+            return SDL_TRUE;
+        }
+        device = device->next;
+    }
+    return SDL_FALSE;
+}
+
+static void
+HIDAPI_JoystickDetect(void)
+{
+    const Uint32 SDL_HIDAPI_DETECT_INTERVAL_MS = 3000;  /* Update every 3 seconds */
+    Uint32 now = SDL_GetTicks();
+    if (!SDL_HIDAPI_last_detect || SDL_TICKS_PASSED(now, SDL_HIDAPI_last_detect + SDL_HIDAPI_DETECT_INTERVAL_MS)) {
+        HIDAPI_UpdateDeviceList();
+        SDL_HIDAPI_last_detect = now;
+    }
+}
+
+static const char *
+HIDAPI_JoystickGetDeviceName(int device_index)
+{
+    return HIDAPI_GetJoystickByIndex(device_index)->name;
+}
+
+static SDL_JoystickGUID
+HIDAPI_JoystickGetDeviceGUID(int device_index)
+{
+    return HIDAPI_GetJoystickByIndex(device_index)->guid;
+}
+
+static SDL_JoystickID
+HIDAPI_JoystickGetDeviceInstanceID(int device_index)
+{
+    return HIDAPI_GetJoystickByIndex(device_index)->instance_id;
+}
+
+static int
+HIDAPI_JoystickOpen(SDL_Joystick * joystick, int device_index)
+{
+    SDL_HIDAPI_Device *device = HIDAPI_GetJoystickByIndex(device_index);
+    struct joystick_hwdata *hwdata;
+
+    hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
+    if (!hwdata) {
+        return SDL_OutOfMemory();
+    }
+
+    hwdata->driver = device->driver;
+    hwdata->dev = hid_open_path(device->path, 0);
+    if (!hwdata->dev) {
+        SDL_free(hwdata);
+        return SDL_SetError("Couldn't open HID device %s", device->path);
+    }
+
+    if (!device->driver->Init(joystick, hwdata->dev, device->vendor_id, device->product_id, &hwdata->context)) {
+        hid_close(hwdata->dev);
+        SDL_free(hwdata);
+        return -1;
+    }
+
+    joystick->hwdata = hwdata;
+    return 0;
+}
+
+static SDL_bool
+HIDAPI_JoystickIsAttached(SDL_Joystick *joystick)
+{
+    SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    while (device) {
+        if (device->driver) {
+            if (joystick->instance_id == device->instance_id) {
+                return SDL_TRUE;
+            }
+        }
+        device = device->next;
+    }
+    return SDL_FALSE;
+}
+
+static int
+HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    struct joystick_hwdata *hwdata = joystick->hwdata;
+    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
+    return driver->Rumble(joystick, hwdata->dev, hwdata->context, low_frequency_rumble, high_frequency_rumble, duration_ms);
+}
+
+static void
+HIDAPI_JoystickUpdate(SDL_Joystick * joystick)
+{
+    struct joystick_hwdata *hwdata = joystick->hwdata;
+    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
+    driver->Update(joystick, hwdata->dev, hwdata->context);
+}
+
+static void
+HIDAPI_JoystickClose(SDL_Joystick * joystick)
+{
+    struct joystick_hwdata *hwdata = joystick->hwdata;
+    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
+    driver->Quit(joystick, hwdata->dev, hwdata->context);
+
+    hid_close(hwdata->dev);
+    SDL_free(hwdata);
+    joystick->hwdata = NULL;
+}
+
+static void
+HIDAPI_JoystickQuit(void)
+{
+    int i;
+
+    while (SDL_HIDAPI_devices) {
+        HIDAPI_DelDevice(SDL_HIDAPI_devices, SDL_FALSE);
+    }
+    for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) {
+        SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i];
+        SDL_DelHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL);
+    }
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI,
+                        SDL_HIDAPIDriverHintChanged, NULL);
+    SDL_HIDAPI_numjoysticks = 0;
+
+    hid_exit();
+}
+
+SDL_JoystickDriver SDL_HIDAPI_JoystickDriver =
+{
+    HIDAPI_JoystickInit,
+    HIDAPI_JoystickGetCount,
+    HIDAPI_JoystickDetect,
+    HIDAPI_JoystickGetDeviceName,
+    HIDAPI_JoystickGetDeviceGUID,
+    HIDAPI_JoystickGetDeviceInstanceID,
+    HIDAPI_JoystickOpen,
+    HIDAPI_JoystickIsAttached,
+    HIDAPI_JoystickRumble,
+    HIDAPI_JoystickUpdate,
+    HIDAPI_JoystickClose,
+    HIDAPI_JoystickQuit,
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 70 - 0
src/joystick/hidapi/SDL_hidapijoystick_c.h

@@ -0,0 +1,70 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2018 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"
+
+#ifndef SDL_JOYSTICK_HIDAPI_H
+#define SDL_JOYSTICK_HIDAPI_H
+
+#include "../../hidapi/hidapi/hidapi.h"
+
+/* This is the full set of HIDAPI drivers available */
+#define SDL_JOYSTICK_HIDAPI_PS4
+#define SDL_JOYSTICK_HIDAPI_SWITCH
+#define SDL_JOYSTICK_HIDAPI_XBOX360
+#define SDL_JOYSTICK_HIDAPI_XBOXONE
+
+#ifdef __WINDOWS__
+/* On Windows, Xbox controllers are handled by the XInput driver */
+#undef SDL_JOYSTICK_HIDAPI_XBOX360
+#undef SDL_JOYSTICK_HIDAPI_XBOXONE
+#endif
+
+#ifdef __MACOSX__
+/* On Mac OS X, Xbox One controllers are handled by the Xbox 360 driver */
+#undef SDL_JOYSTICK_HIDAPI_XBOXONE
+#endif
+
+typedef struct _SDL_HIDAPI_DeviceDriver
+{
+    const char *hint;
+    SDL_bool enabled;
+    SDL_bool (*IsSupportedDevice)(Uint16 vendor_id, Uint16 product_id, int interface_number, Uint16 usage_page, Uint16 usage);
+    const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id);
+    SDL_bool (*Init)(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context);
+    int (*Rumble)(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+    void (*Update)(SDL_Joystick *joystick, hid_device *dev, void *context);
+    void (*Quit)(SDL_Joystick *joystick, hid_device *dev, void *context);
+
+} SDL_HIDAPI_DeviceDriver;
+
+/* HIDAPI device support */
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
+
+/* Return true if a HID device is present and supported as a joystick */
+extern SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id);
+
+#endif /* SDL_JOYSTICK_HIDAPI_H */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 75 - 141
src/joystick/iphoneos/SDL_sysjoystick.m

@@ -33,7 +33,6 @@
 #include "SDL_stdinc.h"
 #include "../SDL_sysjoystick.h"
 #include "../SDL_joystick_c.h"
-#include "../steam/SDL_steamcontroller.h"
 
 
 #if !SDL_EVENTS_DISABLED
@@ -59,7 +58,6 @@ static CMMotionManager *motionManager = nil;
 static SDL_JoystickDeviceItem *deviceList = NULL;
 
 static int numjoysticks = 0;
-static SDL_JoystickID instancecounter = 0;
 int SDL_AppleTVRemoteOpenedAsJoystick = 0;
 
 static SDL_JoystickDeviceItem *
@@ -80,10 +78,9 @@ GetDeviceForIndex(int device_index)
 }
 
 static void
-SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
+IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
 {
 #ifdef SDL_JOYSTICK_MFI
-    const Uint16 BUS_BLUETOOTH = 0x05;
     const Uint16 VENDOR_APPLE = 0x05AC;
     Uint16 *guid16 = (Uint16 *)device->guid.data;
     Uint16 vendor = 0;
@@ -136,7 +133,7 @@ SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *contr
 
     /* We only need 16 bits for each of these; space them out to fill 128. */
     /* Byteswap so devices get same GUID on little/big endian platforms. */
-    *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
+    *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
     *guid16++ = 0;
     *guid16++ = SDL_SwapLE16(vendor);
     *guid16++ = 0;
@@ -157,7 +154,7 @@ SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *contr
 }
 
 static void
-SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
+IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
 {
     SDL_JoystickDeviceItem *device = deviceList;
 
@@ -183,7 +180,7 @@ SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
     }
 
     device->accelerometer = accelerometer;
-    device->instance_id = instancecounter++;
+    device->instance_id = SDL_GetNextJoystickInstanceID();
 
     if (accelerometer) {
 #if TARGET_OS_TV
@@ -199,7 +196,7 @@ SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
         SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
 #endif /* TARGET_OS_TV */
     } else if (controller) {
-        SDL_SYS_AddMFIJoystickDevice(device, controller);
+        IOS_AddMFIJoystickDevice(device, controller);
     }
 
     if (deviceList == NULL) {
@@ -214,11 +211,11 @@ SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
 
     ++numjoysticks;
 
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
+    SDL_PrivateJoystickAdded(device->instance_id);
 }
 
 static SDL_JoystickDeviceItem *
-SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
+IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
 {
     SDL_JoystickDeviceItem *prev = NULL;
     SDL_JoystickDeviceItem *next = NULL;
@@ -287,78 +284,27 @@ SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *
 }
 #endif /* TARGET_OS_TV */
 
-static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickGUID guid, int *device_instance)
-{
-    SDL_JoystickDeviceItem *device = (SDL_JoystickDeviceItem *)SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
-    if (device == NULL) {
-        return SDL_FALSE;
-    }
-
-    *device_instance = device->instance_id = instancecounter++;
-    device->name = SDL_strdup(name);
-    device->guid = guid;
-    SDL_GetSteamControllerInputs(&device->nbuttons,
-                                 &device->naxes,
-                                 &device->nhats);
-    device->m_bSteamController = SDL_TRUE;
-
-    if (deviceList == NULL) {
-        deviceList = device;
-    } else {
-        SDL_JoystickDeviceItem *lastdevice = deviceList;
-        while (lastdevice->next != NULL) {
-            lastdevice = lastdevice->next;
-        }
-        lastdevice->next = device;
-    }
-
-    ++numjoysticks;
-
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
-
-    return SDL_TRUE;
-}
-
-static void SteamControllerDisconnectedCallback(int device_instance)
-{
-    SDL_JoystickDeviceItem *item;
-
-    for (item = deviceList; item; item = item->next) {
-        if (item->instance_id == device_instance) {
-            SDL_SYS_RemoveJoystickDevice(item);
-            break;
-        }
-    }
-}
-
-/* Function to scan the system for joysticks.
- * Joystick 0 should be the system default joystick.
- * It should return 0, or -1 on an unrecoverable fatal error.
- */
-int
-SDL_SYS_JoystickInit(void)
+static int
+IOS_JoystickInit(void)
 {
     @autoreleasepool {
         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
 
-        SDL_InitSteamControllers(SteamControllerConnectedCallback,
-                                 SteamControllerDisconnectedCallback);
-
 #if !TARGET_OS_TV
         if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
             /* Default behavior, accelerometer as joystick */
-            SDL_SYS_AddJoystickDevice(nil, SDL_TRUE);
+            IOS_AddJoystickDevice(nil, SDL_TRUE);
         }
 #endif /* !TARGET_OS_TV */
 
 #ifdef SDL_JOYSTICK_MFI
         /* GameController.framework was added in iOS 7. */
         if (![GCController class]) {
-            return numjoysticks;
+            return 0;
         }
 
         for (GCController *controller in [GCController controllers]) {
-            SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
+            IOS_AddJoystickDevice(controller, SDL_FALSE);
         }
 
 #if TARGET_OS_TV
@@ -371,7 +317,7 @@ SDL_SYS_JoystickInit(void)
                                                queue:nil
                                           usingBlock:^(NSNotification *note) {
                                               GCController *controller = note.object;
-                                              SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
+                                              IOS_AddJoystickDevice(controller, SDL_FALSE);
                                           }];
 
         disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
@@ -382,7 +328,7 @@ SDL_SYS_JoystickInit(void)
                                                  SDL_JoystickDeviceItem *device = deviceList;
                                                  while (device != NULL) {
                                                      if (device->controller == controller) {
-                                                         SDL_SYS_RemoveJoystickDevice(device);
+                                                         IOS_RemoveJoystickDevice(device);
                                                          break;
                                                      }
                                                      device = device->next;
@@ -391,43 +337,49 @@ SDL_SYS_JoystickInit(void)
 #endif /* SDL_JOYSTICK_MFI */
     }
 
-    return numjoysticks;
+    return 0;
 }
 
-int
-SDL_SYS_NumJoysticks(void)
+static int
+IOS_JoystickGetCount(void)
 {
     return numjoysticks;
 }
 
-void
-SDL_SYS_JoystickDetect(void)
+static void
+IOS_JoystickDetect(void)
 {
-    SDL_UpdateSteamControllers();
 }
 
-/* Function to get the device-dependent name of a joystick */
-const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+static const char *
+IOS_JoystickGetDeviceName(int device_index)
 {
     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
     return device ? device->name : "Unknown";
 }
 
-/* Function to perform the mapping from device index to the instance id for this index */
-SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickGUID
+IOS_JoystickGetDeviceGUID( int device_index )
+{
+    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
+    SDL_JoystickGUID guid;
+    if (device) {
+        guid = device->guid;
+    } else {
+        SDL_zero(guid);
+    }
+    return guid;
+}
+
+static SDL_JoystickID
+IOS_JoystickGetDeviceInstanceID(int device_index)
 {
     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
-    return device ? device->instance_id : 0;
+    return device ? device->instance_id : -1;
 }
 
-/* Function to open a joystick for use.
-   The joystick to open is specified by the device index.
-   This should fill the nbuttons and naxes fields of the joystick structure.
-   It returns 0, or -1 if there is an error.
- */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static int
+IOS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
     if (device == NULL) {
@@ -473,15 +425,14 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
     return 0;
 }
 
-/* Function to determine if this joystick is attached to the system right now */
-SDL_bool
-SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
+static SDL_bool
+IOS_JoystickIsAttached(SDL_Joystick *joystick)
 {
     return joystick->hwdata != NULL;
 }
 
 static void
-SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
+IOS_AccelerometerUpdate(SDL_Joystick * joystick)
 {
 #if !TARGET_OS_TV
     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
@@ -526,7 +477,7 @@ SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
 
 #ifdef SDL_JOYSTICK_MFI
 static Uint8
-SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
+IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
 {
     Uint8 hat = 0;
 
@@ -551,7 +502,7 @@ SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
 #endif
 
 static void
-SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
+IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
 {
 #if SDL_JOYSTICK_MFI
     @autoreleasepool {
@@ -581,7 +532,7 @@ SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
                 gamepad.rightShoulder.isPressed,
             };
 
-            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
+            hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
 
             for (i = 0; i < SDL_arraysize(axes); i++) {
                 /* The triggers (axes 2 and 5) are resting at -32768 but SDL
@@ -608,7 +559,7 @@ SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
                 gamepad.rightShoulder.isPressed,
             };
 
-            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
+            hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
 
             for (i = 0; i < SDL_arraysize(buttons); i++) {
                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
@@ -678,13 +629,14 @@ SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
 #endif /* SDL_JOYSTICK_MFI */
 }
 
-/* Function to update the state of a joystick - called as a device poll.
- * This function shouldn't update the joystick structure directly,
- * but instead should call SDL_PrivateJoystick*() to deliver events
- * and update joystick device state.
- */
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static int
+IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    return SDL_Unsupported();
+}
+
+static void
+IOS_JoystickUpdate(SDL_Joystick * joystick)
 {
     SDL_JoystickDeviceItem *device = joystick->hwdata;
 
@@ -692,21 +644,15 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
         return;
     }
     
-    if (device->m_bSteamController) {
-        SDL_UpdateSteamController(joystick);
-        return;
-    }
-
     if (device->accelerometer) {
-        SDL_SYS_AccelerometerUpdate(joystick);
+        IOS_AccelerometerUpdate(joystick);
     } else if (device->controller) {
-        SDL_SYS_MFIJoystickUpdate(joystick);
+        IOS_MFIJoystickUpdate(joystick);
     }
 }
 
-/* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static void
+IOS_JoystickClose(SDL_Joystick * joystick)
 {
     SDL_JoystickDeviceItem *device = joystick->hwdata;
 
@@ -734,9 +680,8 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick)
     }
 }
 
-/* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+IOS_JoystickQuit(void)
 {
     @autoreleasepool {
 #ifdef SDL_JOYSTICK_MFI
@@ -759,7 +704,7 @@ SDL_SYS_JoystickQuit(void)
 #endif /* SDL_JOYSTICK_MFI */
 
         while (deviceList != NULL) {
-            SDL_SYS_RemoveJoystickDevice(deviceList);
+            IOS_RemoveJoystickDevice(deviceList);
         }
 
 #if !TARGET_OS_TV
@@ -767,34 +712,23 @@ SDL_SYS_JoystickQuit(void)
 #endif /* !TARGET_OS_TV */
     }
 
-    SDL_QuitSteamControllers();
-
     numjoysticks = 0;
 }
 
-SDL_JoystickGUID
-SDL_SYS_JoystickGetDeviceGUID( int device_index )
+SDL_JoystickDriver SDL_IOS_JoystickDriver =
 {
-    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
-    SDL_JoystickGUID guid;
-    if (device) {
-        guid = device->guid;
-    } else {
-        SDL_zero(guid);
-    }
-    return guid;
-}
-
-SDL_JoystickGUID
-SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
-{
-    SDL_JoystickGUID guid;
-    if (joystick->hwdata) {
-        guid = joystick->hwdata->guid;
-    } else {
-        SDL_zero(guid);
-    }
-    return guid;
-}
+    IOS_JoystickInit,
+    IOS_JoystickGetCount,
+    IOS_JoystickDetect,
+    IOS_JoystickGetDeviceName,
+    IOS_JoystickGetDeviceGUID,
+    IOS_JoystickGetDeviceInstanceID,
+    IOS_JoystickOpen,
+    IOS_JoystickIsAttached,
+    IOS_JoystickRumble,
+    IOS_JoystickUpdate,
+    IOS_JoystickClose,
+    IOS_JoystickQuit,
+};
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 0 - 3
src/joystick/iphoneos/SDL_sysjoystick_c.h

@@ -46,9 +46,6 @@ typedef struct joystick_hwdata
     int nbuttons;
     int nhats;
 
-    /* Steam Controller support */
-    SDL_bool m_bSteamController;
-
     struct joystick_hwdata *next;
 } joystick_hwdata;
 

+ 115 - 40
src/joystick/linux/SDL_sysjoystick.c

@@ -29,10 +29,11 @@
 /* This is the Linux implementation of the SDL joystick API */
 
 #include <sys/stat.h>
-#include <unistd.h>
+#include <errno.h>              /* errno, strerror */
 #include <fcntl.h>
-#include <sys/ioctl.h>
 #include <limits.h>             /* For the definition of PATH_MAX */
+#include <sys/ioctl.h>
+#include <unistd.h>
 #include <linux/joystick.h>
 
 #include "SDL_assert.h"
@@ -43,6 +44,7 @@
 #include "../SDL_joystick_c.h"
 #include "../steam/SDL_steamcontroller.h"
 #include "SDL_sysjoystick_c.h"
+#include "../hidapi/SDL_hidapijoystick_c.h"
 
 /* This isn't defined in older Linux kernel headers */
 #ifndef SYN_DROPPED
@@ -76,7 +78,6 @@ typedef struct SDL_joylist_item
 static SDL_joylist_item *SDL_joylist = NULL;
 static SDL_joylist_item *SDL_joylist_tail = NULL;
 static int numjoysticks = 0;
-static int instance_counter = 0;
 
 
 #define test_bit(nr, addr) \
@@ -209,6 +210,13 @@ IsJoystick(int fd, char *namebuf, const size_t namebuflen, SDL_JoystickGUID *gui
         return 0;
     }
 
+#ifdef SDL_JOYSTICK_HIDAPI
+    if (HIDAPI_IsDevicePresent(inpid.vendor, inpid.product)) {
+        /* The HIDAPI driver is taking care of this device */
+        return 0;
+    }
+#endif
+
     /* Check the joystick blacklist */
     id = MAKE_VIDPID(inpid.vendor, inpid.product);
     for (i = 0; i < SDL_arraysize(joystick_blacklist); ++i) {
@@ -239,8 +247,7 @@ IsJoystick(int fd, char *namebuf, const size_t namebuflen, SDL_JoystickGUID *gui
         SDL_strlcpy((char*)guid16, namebuf, sizeof(guid->data) - 4);
     }
 
-    if (SDL_IsGameControllerNameAndGUID(namebuf, *guid) &&
-        SDL_ShouldIgnoreGameController(namebuf, *guid)) {
+    if (SDL_ShouldIgnoreJoystick(namebuf, *guid)) {
         return 0;
     }
     return 1;
@@ -325,14 +332,14 @@ MaybeAddDevice(const char *path)
     item->name = SDL_strdup(namebuf);
     item->guid = guid;
 
-    if ( (item->path == NULL) || (item->name == NULL) ) {
+    if ((item->path == NULL) || (item->name == NULL)) {
          SDL_free(item->path);
          SDL_free(item->name);
          SDL_free(item);
          return -1;
     }
 
-    item->device_instance = instance_counter++;
+    item->device_instance = SDL_GetNextJoystickInstanceID();
     if (SDL_joylist_tail == NULL) {
         SDL_joylist = SDL_joylist_tail = item;
     } else {
@@ -343,7 +350,7 @@ MaybeAddDevice(const char *path)
     /* Need to increment the joystick count before we post the event */
     ++numjoysticks;
 
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
+    SDL_PrivateJoystickAdded(item->device_instance);
 
     return numjoysticks;
 }
@@ -409,7 +416,7 @@ JoystickInitWithoutUdev(void)
         MaybeAddDevice(path);
     }
 
-    return numjoysticks;
+    return 0;
 }
 #endif
 
@@ -430,7 +437,7 @@ JoystickInitWithUdev(void)
     /* Force a scan to build the initial device list */
     SDL_UDEV_Scan();
 
-    return numjoysticks;
+    return 0;
 }
 #endif
 
@@ -455,7 +462,7 @@ static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickG
          return SDL_FALSE;
     }
 
-    *device_instance = item->device_instance = instance_counter++;
+    *device_instance = item->device_instance = SDL_GetNextJoystickInstanceID();
     if (SDL_joylist_tail == NULL) {
         SDL_joylist = SDL_joylist_tail = item;
     } else {
@@ -466,7 +473,7 @@ static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickG
     /* Need to increment the joystick count before we post the event */
     ++numjoysticks;
 
-    SDL_PrivateJoystickAdded(numjoysticks - 1);
+    SDL_PrivateJoystickAdded(item->device_instance);
 
     return SDL_TRUE;
 }
@@ -505,8 +512,8 @@ static void SteamControllerDisconnectedCallback(int device_instance)
     }
 }
 
-int
-SDL_SYS_JoystickInit(void)
+static int
+LINUX_JoystickInit(void)
 {
     /* First see if the user specified one or more joysticks to use */
     if (SDL_getenv("SDL_JOYSTICK_DEVICE") != NULL) {
@@ -534,14 +541,14 @@ SDL_SYS_JoystickInit(void)
 #endif
 }
 
-int
-SDL_SYS_NumJoysticks(void)
+static int
+LINUX_JoystickGetCount(void)
 {
     return numjoysticks;
 }
 
-void
-SDL_SYS_JoystickDetect(void)
+static void
+LINUX_JoystickDetect(void)
 {
 #if SDL_USE_LIBUDEV
     SDL_UDEV_Poll();
@@ -569,14 +576,21 @@ JoystickByDevIndex(int device_index)
 }
 
 /* Function to get the device-dependent name of a joystick */
-const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+static const char *
+LINUX_JoystickGetDeviceName(int device_index)
 {
     return JoystickByDevIndex(device_index)->name;
 }
 
+static SDL_JoystickGUID
+LINUX_JoystickGetDeviceGUID( int device_index )
+{
+    return JoystickByDevIndex(device_index)->guid;
+}
+
 /* Function to perform the mapping from device index to the instance id for this index */
-SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickID
+LINUX_JoystickGetDeviceInstanceID(int device_index)
 {
     return JoystickByDevIndex(device_index)->device_instance;
 }
@@ -624,6 +638,7 @@ ConfigJoystick(SDL_Joystick * joystick, int fd)
     unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
     unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
     unsigned long relbit[NBITS(REL_MAX)] = { 0 };
+    unsigned long ffbit[NBITS(FF_MAX)] = { 0 };
 
     /* See if this device uses the new unified event API */
     if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
@@ -719,6 +734,15 @@ ConfigJoystick(SDL_Joystick * joystick, int fd)
             }
         }
     }
+
+    if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) >= 0) {
+        if (test_bit(FF_RUMBLE, ffbit)) {
+            joystick->hwdata->ff_rumble = SDL_TRUE;
+        }
+        if (test_bit(FF_SINE, ffbit)) {
+            joystick->hwdata->ff_sine = SDL_TRUE;
+        }
+    }
 }
 
 
@@ -727,8 +751,8 @@ ConfigJoystick(SDL_Joystick * joystick, int fd)
    This should fill the nbuttons and naxes fields of the joystick structure.
    It returns 0, or -1 if there is an error.
  */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static int
+LINUX_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
     SDL_joylist_item *item = JoystickByDevIndex(device_index);
 
@@ -744,6 +768,7 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
     }
     joystick->hwdata->item = item;
     joystick->hwdata->guid = item->guid;
+    joystick->hwdata->effect.id = -1;
     joystick->hwdata->m_bSteamController = item->m_bSteamController;
 
     if (item->m_bSteamController) {
@@ -752,7 +777,7 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
                                      &joystick->naxes,
                                      &joystick->nhats);
     } else {
-        int fd = open(item->path, O_RDONLY, 0);
+        int fd = open(item->path, O_RDWR, 0);
         if (fd < 0) {
             SDL_free(joystick->hwdata);
             joystick->hwdata = NULL;
@@ -785,11 +810,52 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 /* Function to determine if this joystick is attached to the system right now */
-SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
+static SDL_bool
+LINUX_JoystickIsAttached(SDL_Joystick *joystick)
 {
     return joystick->hwdata->item != NULL;
 }
 
+static int
+LINUX_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    struct input_event event;
+
+    if (joystick->hwdata->effect.id < 0) {
+        if (joystick->hwdata->ff_rumble) {
+            struct ff_effect *effect = &joystick->hwdata->effect;
+
+            effect->type = FF_RUMBLE;
+            effect->replay.length = SDL_min(duration_ms, 32767);
+            effect->u.rumble.strong_magnitude = low_frequency_rumble;
+            effect->u.rumble.weak_magnitude = high_frequency_rumble;
+        } else if (joystick->hwdata->ff_sine) {
+            /* Scale and average the two rumble strengths */
+            Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
+            struct ff_effect *effect = &joystick->hwdata->effect;
+
+            effect->type = FF_PERIODIC;
+            effect->replay.length = SDL_min(duration_ms, 32767);
+            effect->u.periodic.waveform = FF_SINE;
+            effect->u.periodic.magnitude = magnitude;
+        } else {
+            return SDL_Unsupported();
+        }
+    }
+
+    if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) {
+        return SDL_SetError("Couldn't update rumble effect: %s", strerror(errno));
+    }
+
+    event.type = EV_FF;
+    event.code = joystick->hwdata->effect.id;
+    event.value = 1;
+    if (write(joystick->hwdata->fd, &event, sizeof(event)) < 0) {
+        return SDL_SetError("Couldn't start rumble effect: %s", strerror(errno));
+    }
+    return 0;
+}
+
 static SDL_INLINE void
 HandleHat(SDL_Joystick * stick, Uint8 hat, int axis, int value)
 {
@@ -963,8 +1029,8 @@ HandleInputEvents(SDL_Joystick * joystick)
     }
 }
 
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static void
+LINUX_JoystickUpdate(SDL_Joystick * joystick)
 {
     int i;
 
@@ -990,10 +1056,14 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
 }
 
 /* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static void
+LINUX_JoystickClose(SDL_Joystick * joystick)
 {
     if (joystick->hwdata) {
+        if (joystick->hwdata->effect.id >= 0) {
+            ioctl(joystick->hwdata->fd, EVIOCRMFF, joystick->hwdata->effect.id);
+            joystick->hwdata->effect.id = -1;
+        }
         if (joystick->hwdata->fd >= 0) {
             close(joystick->hwdata->fd);
         }
@@ -1008,8 +1078,8 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick)
 }
 
 /* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+LINUX_JoystickQuit(void)
 {
     SDL_joylist_item *item = NULL;
     SDL_joylist_item *next = NULL;
@@ -1024,7 +1094,6 @@ SDL_SYS_JoystickQuit(void)
     SDL_joylist = SDL_joylist_tail = NULL;
 
     numjoysticks = 0;
-    instance_counter = 0;
 
 #if SDL_USE_LIBUDEV
     SDL_UDEV_DelCallback(joystick_udev_callback);
@@ -1034,15 +1103,21 @@ SDL_SYS_JoystickQuit(void)
     SDL_QuitSteamControllers();
 }
 
-SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
+SDL_JoystickDriver SDL_LINUX_JoystickDriver =
 {
-    return JoystickByDevIndex(device_index)->guid;
-}
-
-SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
-{
-    return joystick->hwdata->guid;
-}
+    LINUX_JoystickInit,
+    LINUX_JoystickGetCount,
+    LINUX_JoystickDetect,
+    LINUX_JoystickGetDeviceName,
+    LINUX_JoystickGetDeviceGUID,
+    LINUX_JoystickGetDeviceInstanceID,
+    LINUX_JoystickOpen,
+    LINUX_JoystickIsAttached,
+    LINUX_JoystickRumble,
+    LINUX_JoystickUpdate,
+    LINUX_JoystickClose,
+    LINUX_JoystickQuit,
+};
 
 #endif /* SDL_JOYSTICK_LINUX */
 

+ 4 - 0
src/joystick/linux/SDL_sysjoystick_c.h

@@ -31,6 +31,10 @@ struct joystick_hwdata
     SDL_JoystickGUID guid;
     char *fname;                /* Used in haptic subsystem */
 
+    SDL_bool ff_rumble;
+    SDL_bool ff_sine;
+    struct ff_effect effect;
+
     /* The current Linux joystick driver maps hats to two axes */
     struct hwdata_hat
     {

+ 352 - 185
src/joystick/windows/SDL_dinputjoystick.c

@@ -27,6 +27,7 @@
 #include "SDL_windowsjoystick_c.h"
 #include "SDL_dinputjoystick_c.h"
 #include "SDL_xinputjoystick_c.h"
+#include "../hidapi/SDL_hidapijoystick_c.h"
 
 #ifndef DIDFT_OPTIONAL
 #define DIDFT_OPTIONAL      0x80000000
@@ -35,6 +36,8 @@
 #define INPUT_QSIZE 32      /* Buffer up to 32 input messages */
 #define JOY_AXIS_THRESHOLD  (((SDL_JOYSTICK_AXIS_MAX)-(SDL_JOYSTICK_AXIS_MIN))/100)   /* 1% motion */
 
+#define CONVERT_MAGNITUDE(x)    (((x)*10000) / 0x7FFF)
+
 /* external variables referenced. */
 extern HWND SDL_HelperWindow;
 
@@ -46,170 +49,170 @@ static UINT SDL_RawDevListCount = 0;
 
 /* Taken from Wine - Thanks! */
 static DIOBJECTDATAFORMAT dfDIJoystick2[] = {
-        { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglVSlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglVSlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglASlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglASlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglFSlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
-        { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglFSlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_XAxis, DIJOFS_X, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_YAxis, DIJOFS_Y, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_ZAxis, DIJOFS_Z, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RxAxis, DIJOFS_RX, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RyAxis, DIJOFS_RY, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RzAxis, DIJOFS_RZ, DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, DIJOFS_SLIDER(0), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, DIJOFS_SLIDER(1), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_POV, DIJOFS_POV(0), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_POV, DIJOFS_POV(1), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_POV, DIJOFS_POV(2), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_POV, DIJOFS_POV(3), DIDFT_OPTIONAL | DIDFT_POV | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(0), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(1), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(2), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(3), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(4), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(5), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(6), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(7), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(8), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(9), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(10), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(11), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(12), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(13), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(14), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(15), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(16), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(17), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(18), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(19), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(20), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(21), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(22), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(23), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(24), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(25), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(26), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(27), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(28), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(29), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(30), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(31), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(32), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(33), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(34), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(35), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(36), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(37), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(38), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(39), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(40), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(41), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(42), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(43), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(44), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(45), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(46), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(47), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(48), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(49), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(50), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(51), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(52), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(53), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(54), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(55), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(56), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(57), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(58), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(59), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(60), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(61), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(62), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(63), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(64), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(65), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(66), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(67), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(68), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(69), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(70), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(71), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(72), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(73), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(74), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(75), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(76), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(77), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(78), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(79), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(80), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(81), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(82), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(83), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(84), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(85), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(86), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(87), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(88), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(89), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(90), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(91), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(92), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(93), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(94), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(95), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(96), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(97), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(98), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(99), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(100), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(101), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(102), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(103), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(104), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(105), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(106), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(107), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(108), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(109), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(110), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(111), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(112), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(113), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(114), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(115), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(116), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(117), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(118), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(119), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(120), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(121), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(122), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(123), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(124), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(125), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(126), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { NULL, DIJOFS_BUTTON(127), DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lVX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lVY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lVZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lVRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lVRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lVRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglVSlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglVSlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lAX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lAY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lAZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lARx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lARy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lARz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglASlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglASlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_XAxis, FIELD_OFFSET(DIJOYSTATE2, lFX), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_YAxis, FIELD_OFFSET(DIJOYSTATE2, lFY), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_ZAxis, FIELD_OFFSET(DIJOYSTATE2, lFZ), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RxAxis, FIELD_OFFSET(DIJOYSTATE2, lFRx), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RyAxis, FIELD_OFFSET(DIJOYSTATE2, lFRy), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_RzAxis, FIELD_OFFSET(DIJOYSTATE2, lFRz), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglFSlider[0]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
+    { &GUID_Slider, FIELD_OFFSET(DIJOYSTATE2, rglFSlider[1]), DIDFT_OPTIONAL | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0 },
 };
 
 const DIDATAFORMAT SDL_c_dfDIJoystick2 = {
@@ -311,6 +314,61 @@ SDL_IsXInputDevice(const GUID* pGuidProductFromDirectInput)
     return SDL_FALSE;
 }
 
+void FreeRumbleEffectData(DIEFFECT *effect)
+{
+    if (!effect) {
+        return;
+    }
+    SDL_free(effect->rgdwAxes);
+    SDL_free(effect->rglDirection);
+    SDL_free(effect->lpvTypeSpecificParams);
+    SDL_free(effect);
+}
+
+DIEFFECT *CreateRumbleEffectData(Sint16 magnitude, Uint32 duration_ms)
+{
+    DIEFFECT *effect;
+    DIPERIODIC *periodic;
+
+    /* Create the effect */
+    effect = (DIEFFECT *)SDL_calloc(1, sizeof(*effect));
+    if (!effect) {
+        return NULL;
+    }
+    effect->dwSize = sizeof(*effect);
+    effect->dwGain = 10000;
+    effect->dwFlags = DIEFF_OBJECTOFFSETS;
+    effect->dwDuration = duration_ms * 1000; /* In microseconds. */
+    effect->dwTriggerButton = DIEB_NOTRIGGER;
+
+    effect->cAxes = 2;
+    effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
+    if (!effect->rgdwAxes) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+
+    effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
+    if (!effect->rglDirection) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+    effect->dwFlags |= DIEFF_CARTESIAN;
+
+    periodic = (DIPERIODIC *)SDL_calloc(1, sizeof(*periodic));
+    if (!periodic) {
+        FreeRumbleEffectData(effect);
+        return NULL;
+    }
+    periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
+    periodic->dwPeriod = 1000000;
+
+    effect->cbTypeSpecificParams = sizeof(*periodic);
+    effect->lpvTypeSpecificParams = periodic;
+
+    return effect;
+}
+
 int
 SDL_DINPUT_JoystickInit(void)
 {
@@ -348,28 +406,29 @@ SDL_DINPUT_JoystickInit(void)
 static BOOL CALLBACK
 EnumJoysticksCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext)
 {
-    const Uint16 BUS_USB = 0x03;
-    const Uint16 BUS_BLUETOOTH = 0x05;
     JoyStick_DeviceData *pNewJoystick;
     JoyStick_DeviceData *pPrevJoystick = NULL;
     const DWORD devtype = (pdidInstance->dwDevType & 0xFF);
     Uint16 *guid16;
+    Uint16 vendor = 0;
+    Uint16 product = 0;
+    Uint16 version = 0;
     WCHAR hidPath[MAX_PATH];
 
     if (devtype == DI8DEVTYPE_SUPPLEMENTAL) {
         /* Add any supplemental devices that should be ignored here */
-#define MAKE_TABLE_ENTRY(VID, PID)	((((DWORD)PID)<<16)|VID)
-		static DWORD ignored_devices[] = {
-			MAKE_TABLE_ENTRY(0, 0)
-		};
+#define MAKE_TABLE_ENTRY(VID, PID)    ((((DWORD)PID)<<16)|VID)
+        static DWORD ignored_devices[] = {
+            MAKE_TABLE_ENTRY(0, 0)
+        };
 #undef MAKE_TABLE_ENTRY
-		unsigned int i;
+        unsigned int i;
 
-		for (i = 0; i < SDL_arraysize(ignored_devices); ++i) {
-			if (pdidInstance->guidProduct.Data1 == ignored_devices[i]) {
-				return DIENUM_CONTINUE;
-			}
-		}
+        for (i = 0; i < SDL_arraysize(ignored_devices); ++i) {
+            if (pdidInstance->guidProduct.Data1 == ignored_devices[i]) {
+                return DIENUM_CONTINUE;
+            }
+        }
     }
 
     if (SDL_IsXInputDevice(&pdidInstance->guidProduct)) {
@@ -452,27 +511,38 @@ EnumJoysticksCallback(const DIDEVICEINSTANCE * pdidInstance, VOID * pContext)
 
     guid16 = (Uint16 *)pNewJoystick->guid.data;
     if (SDL_memcmp(&pdidInstance->guidProduct.Data4[2], "PIDVID", 6) == 0) {
-        *guid16++ = SDL_SwapLE16(BUS_USB);
+        vendor = (Uint16)LOWORD(pdidInstance->guidProduct.Data1);
+        product = (Uint16)HIWORD(pdidInstance->guidProduct.Data1);
+        version = 0;
+
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
         *guid16++ = 0;
-        *guid16++ = SDL_SwapLE16((Uint16)LOWORD(pdidInstance->guidProduct.Data1)); /* vendor */
+        *guid16++ = SDL_SwapLE16(vendor);
         *guid16++ = 0;
-        *guid16++ = SDL_SwapLE16((Uint16)HIWORD(pdidInstance->guidProduct.Data1)); /* product */
+        *guid16++ = SDL_SwapLE16(product);
         *guid16++ = 0;
-        *guid16++ = 0; /* version */
+        *guid16++ = SDL_SwapLE16(version);
         *guid16++ = 0;
     } else {
-        *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
         *guid16++ = 0;
         SDL_strlcpy((char*)guid16, pNewJoystick->joystickname, sizeof(pNewJoystick->guid.data) - 4);
     }
 
-    if (SDL_IsGameControllerNameAndGUID(pNewJoystick->joystickname, pNewJoystick->guid) &&
-        SDL_ShouldIgnoreGameController(pNewJoystick->joystickname, pNewJoystick->guid)) {
+    if (SDL_ShouldIgnoreJoystick(pNewJoystick->joystickname, pNewJoystick->guid)) {
+        SDL_free(pNewJoystick);
+        return DIENUM_CONTINUE;
+    }
+
+#ifdef SDL_JOYSTICK_HIDAPI
+    if (HIDAPI_IsDevicePresent(vendor, product)) {
+        /* The HIDAPI driver is taking care of this device */
         SDL_free(pNewJoystick);
         return DIENUM_CONTINUE;
     }
+#endif
 
-    SDL_SYS_AddJoystickDevice(pNewJoystick);
+    WINDOWS_AddJoystickDevice(pNewJoystick);
 
     return DIENUM_CONTINUE; /* get next device, please */
 }
@@ -683,7 +753,6 @@ SDL_DINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickde
 
     /* Force capable? */
     if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
-
         result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
         if (FAILED(result)) {
             return SetDIerror("IDirectInputDevice8::Acquire", result);
@@ -752,6 +821,89 @@ SDL_DINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickde
     return 0;
 }
 
+static int
+SDL_DINPUT_JoystickInitRumble(SDL_Joystick * joystick, Sint16 magnitude, Uint32 duration_ms)
+{
+    HRESULT result;
+
+    /* Reset and then enable actuators */
+    result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
+    if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
+        result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
+        if (SUCCEEDED(result)) {
+            result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_RESET);
+        }
+    }
+    if (FAILED(result)) {
+        return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_RESET)", result);
+    }
+
+    result = IDirectInputDevice8_SendForceFeedbackCommand(joystick->hwdata->InputDevice, DISFFC_SETACTUATORSON);
+    if (FAILED(result)) {
+        return SetDIerror("IDirectInputDevice8::SendForceFeedbackCommand(DISFFC_SETACTUATORSON)", result);
+    }
+
+    /* Create the effect */
+    joystick->hwdata->ffeffect = CreateRumbleEffectData(magnitude, duration_ms);
+    if (!joystick->hwdata->ffeffect) {
+        return SDL_OutOfMemory();
+    }
+
+    result = IDirectInputDevice8_CreateEffect(joystick->hwdata->InputDevice, &GUID_Sine,
+                                              joystick->hwdata->ffeffect, &joystick->hwdata->ffeffect_ref, NULL);
+    if (FAILED(result)) {
+        return SetDIerror("IDirectInputDevice8::CreateEffect", result);
+    }
+    return 0;
+}
+
+int
+SDL_DINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    HRESULT result;
+
+    /* Scale and average the two rumble strengths */
+    Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
+
+    if (!(joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK)) {
+        return SDL_Unsupported();
+    }
+
+    if (joystick->hwdata->ff_initialized) {
+        DIPERIODIC *periodic = ((DIPERIODIC *)joystick->hwdata->ffeffect->lpvTypeSpecificParams);
+        joystick->hwdata->ffeffect->dwDuration = duration_ms * 1000; /* In microseconds. */
+        periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
+
+        result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
+        if (result == DIERR_INPUTLOST) {
+            result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
+            if (SUCCEEDED(result)) {
+                result = IDirectInputEffect_SetParameters(joystick->hwdata->ffeffect_ref, joystick->hwdata->ffeffect, (DIEP_DURATION | DIEP_TYPESPECIFICPARAMS));
+            }
+        }
+        if (FAILED(result)) {
+            return SetDIerror("IDirectInputDevice8::SetParameters", result);
+        }
+    } else {
+        if (SDL_DINPUT_JoystickInitRumble(joystick, magnitude, duration_ms) < 0) {
+            return -1;
+        }
+        joystick->hwdata->ff_initialized = SDL_TRUE;
+    }
+
+    result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
+    if (result == DIERR_INPUTLOST || result == DIERR_NOTEXCLUSIVEACQUIRED) {
+        result = IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
+        if (SUCCEEDED(result)) {
+            result = IDirectInputEffect_Start(joystick->hwdata->ffeffect_ref, 1, 0);
+        }
+    }
+    if (FAILED(result)) {
+        return SetDIerror("IDirectInputDevice8::Start", result);
+    }
+    return 0;
+}
+
 static Uint8
 TranslatePOV(DWORD value)
 {
@@ -933,8 +1085,17 @@ SDL_DINPUT_JoystickUpdate(SDL_Joystick * joystick)
 void
 SDL_DINPUT_JoystickClose(SDL_Joystick * joystick)
 {
+    if (joystick->hwdata->ffeffect_ref) {
+        IDirectInputEffect_Unload(joystick->hwdata->ffeffect_ref);
+        joystick->hwdata->ffeffect_ref = NULL;
+    }
+    if (joystick->hwdata->ffeffect) {
+        FreeRumbleEffectData(joystick->hwdata->ffeffect);
+        joystick->hwdata->ffeffect = NULL;
+    }
     IDirectInputDevice8_Unacquire(joystick->hwdata->InputDevice);
     IDirectInputDevice8_Release(joystick->hwdata->InputDevice);
+    joystick->hwdata->ff_initialized = SDL_FALSE;
 }
 
 void
@@ -972,6 +1133,12 @@ SDL_DINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickde
     return SDL_Unsupported();
 }
 
+int
+SDL_DINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    return SDL_Unsupported();
+}
+
 void
 SDL_DINPUT_JoystickUpdate(SDL_Joystick * joystick)
 {

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

@@ -23,6 +23,7 @@
 extern int SDL_DINPUT_JoystickInit(void);
 extern void SDL_DINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
 extern int SDL_DINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice);
+extern int SDL_DINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
 extern void SDL_DINPUT_JoystickUpdate(SDL_Joystick * joystick);
 extern void SDL_DINPUT_JoystickClose(SDL_Joystick * joystick);
 extern void SDL_DINPUT_JoystickQuit(void);

+ 67 - 45
src/joystick/windows/SDL_windowsjoystick.c

@@ -61,7 +61,6 @@
 /* local variables */
 static SDL_bool s_bDeviceAdded = SDL_FALSE;
 static SDL_bool s_bDeviceRemoved = SDL_FALSE;
-static SDL_JoystickID s_nInstanceID = -1;
 static SDL_cond *s_condJoystickThread = NULL;
 static SDL_mutex *s_mutexJoyStickEnum = NULL;
 static SDL_Thread *s_threadJoystick = NULL;
@@ -271,30 +270,33 @@ SDL_JoystickThread(void *_data)
     return 1;
 }
 
-void SDL_SYS_AddJoystickDevice(JoyStick_DeviceData *device)
+void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device)
 {
     device->send_add_event = SDL_TRUE;
-    device->nInstanceID = ++s_nInstanceID;
+    device->nInstanceID = SDL_GetNextJoystickInstanceID();
     device->pNext = SYS_Joystick;
     SYS_Joystick = device;
 
     s_bDeviceAdded = SDL_TRUE;
 }
 
+static void WINDOWS_JoystickDetect(void);
+static void WINDOWS_JoystickQuit(void);
+
 /* Function to scan the system for joysticks.
  * Joystick 0 should be the system default joystick.
  * It should return 0, or -1 on an unrecoverable fatal error.
  */
-int
-SDL_SYS_JoystickInit(void)
+static int
+WINDOWS_JoystickInit(void)
 {
     if (SDL_DINPUT_JoystickInit() < 0) {
-        SDL_SYS_JoystickQuit();
+        WINDOWS_JoystickQuit();
         return -1;
     }
 
     if (SDL_XINPUT_JoystickInit() < 0) {
-        SDL_SYS_JoystickQuit();
+        WINDOWS_JoystickQuit();
         return -1;
     }
 
@@ -302,19 +304,19 @@ SDL_SYS_JoystickInit(void)
     s_condJoystickThread = SDL_CreateCond();
     s_bDeviceAdded = SDL_TRUE; /* force a scan of the system for joysticks this first time */
 
-    SDL_SYS_JoystickDetect();
+    WINDOWS_JoystickDetect();
 
     if (!s_threadJoystick) {
         /* spin up the thread to detect hotplug of devices */
         s_bJoystickThreadQuit = SDL_FALSE;
         s_threadJoystick = SDL_CreateThreadInternal(SDL_JoystickThread, "SDL_joystick", 64 * 1024, NULL);
     }
-    return SDL_SYS_NumJoysticks();
+    return 0;
 }
 
 /* return the number of joysticks that are connected right now */
-int
-SDL_SYS_NumJoysticks(void)
+static int
+WINDOWS_JoystickGetCount(void)
 {
     int nJoysticks = 0;
     JoyStick_DeviceData *device = SYS_Joystick;
@@ -327,8 +329,8 @@ SDL_SYS_NumJoysticks(void)
 }
 
 /* detect any new joysticks being inserted into the system */
-void
-SDL_SYS_JoystickDetect(void)
+static void
+WINDOWS_JoystickDetect(void)
 {
     JoyStick_DeviceData *pCurList = NULL;
 
@@ -383,7 +385,7 @@ SDL_SYS_JoystickDetect(void)
                     SDL_DINPUT_MaybeAddDevice(&pNewJoystick->dxdevice);
                 }
 
-                SDL_PrivateJoystickAdded(device_index);
+                SDL_PrivateJoystickAdded(pNewJoystick->nInstanceID);
 
                 pNewJoystick->send_add_event = SDL_FALSE;
             }
@@ -394,8 +396,8 @@ SDL_SYS_JoystickDetect(void)
 }
 
 /* Function to get the device-dependent name of a joystick */
-const char *
-SDL_SYS_JoystickNameForDeviceIndex(int device_index)
+static const char *
+WINDOWS_JoystickGetDeviceName(int device_index)
 {
     JoyStick_DeviceData *device = SYS_Joystick;
 
@@ -405,9 +407,22 @@ SDL_SYS_JoystickNameForDeviceIndex(int device_index)
     return device->joystickname;
 }
 
+/* return the stable device guid for this device index */
+static SDL_JoystickGUID
+WINDOWS_JoystickGetDeviceGUID(int device_index)
+{
+    JoyStick_DeviceData *device = SYS_Joystick;
+    int index;
+
+    for (index = device_index; index > 0; index--)
+        device = device->pNext;
+
+    return device->guid;
+}
+
 /* Function to perform the mapping between current device instance and this joysticks instance id */
-SDL_JoystickID
-SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
+static SDL_JoystickID
+WINDOWS_JoystickGetDeviceInstanceID(int device_index)
 {
     JoyStick_DeviceData *device = SYS_Joystick;
     int index;
@@ -423,8 +438,8 @@ SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
    This should fill the nbuttons and naxes fields of the joystick structure.
    It returns 0, or -1 if there is an error.
  */
-int
-SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+static int
+WINDOWS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
     JoyStick_DeviceData *joystickdevice = SYS_Joystick;
 
@@ -449,14 +464,24 @@ SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 /* return true if this joystick is plugged in right now */
-SDL_bool 
-SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
+static SDL_bool 
+WINDOWS_JoystickIsAttached(SDL_Joystick * joystick)
 {
     return joystick->hwdata && !joystick->hwdata->removed;
 }
 
-void
-SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
+static int
+WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    if (joystick->hwdata->bXInputDevice) {
+        return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
+    } else {
+        return SDL_DINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
+    }
+}
+
+static void
+WINDOWS_JoystickUpdate(SDL_Joystick * joystick)
 {
     if (!joystick->hwdata || joystick->hwdata->removed) {
         return;
@@ -474,8 +499,8 @@ SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
 }
 
 /* Function to close a joystick after use */
-void
-SDL_SYS_JoystickClose(SDL_Joystick * joystick)
+static void
+WINDOWS_JoystickClose(SDL_Joystick * joystick)
 {
     if (joystick->hwdata->bXInputDevice) {
         SDL_XINPUT_JoystickClose(joystick);
@@ -487,8 +512,8 @@ SDL_SYS_JoystickClose(SDL_Joystick * joystick)
 }
 
 /* Function to perform any system-specific joystick related cleanup */
-void
-SDL_SYS_JoystickQuit(void)
+static void
+WINDOWS_JoystickQuit(void)
 {
     JoyStick_DeviceData *device = SYS_Joystick;
 
@@ -524,24 +549,21 @@ SDL_SYS_JoystickQuit(void)
     s_bDeviceRemoved = SDL_FALSE;
 }
 
-/* return the stable device guid for this device index */
-SDL_JoystickGUID
-SDL_SYS_JoystickGetDeviceGUID(int device_index)
+SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
 {
-    JoyStick_DeviceData *device = SYS_Joystick;
-    int index;
-
-    for (index = device_index; index > 0; index--)
-        device = device->pNext;
-
-    return device->guid;
-}
-
-SDL_JoystickGUID
-SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
-{
-    return joystick->hwdata->guid;
-}
+    WINDOWS_JoystickInit,
+    WINDOWS_JoystickGetCount,
+    WINDOWS_JoystickDetect,
+    WINDOWS_JoystickGetDeviceName,
+    WINDOWS_JoystickGetDeviceGUID,
+    WINDOWS_JoystickGetDeviceInstanceID,
+    WINDOWS_JoystickOpen,
+    WINDOWS_JoystickIsAttached,
+    WINDOWS_JoystickRumble,
+    WINDOWS_JoystickUpdate,
+    WINDOWS_JoystickClose,
+    WINDOWS_JoystickQuit,
+};
 
 #endif /* SDL_JOYSTICK_DINPUT || SDL_JOYSTICK_XINPUT */
 

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

@@ -68,6 +68,7 @@ struct joystick_hwdata
     SDL_JoystickGUID guid;
     SDL_bool removed;
     SDL_bool send_remove_event;
+    Uint32 rumble_expiration;
 
 #if SDL_JOYSTICK_DINPUT
     LPDIRECTINPUTDEVICE8 InputDevice;
@@ -76,6 +77,9 @@ struct joystick_hwdata
     input_t Inputs[MAX_INPUTS];
     int NumInputs;
     int NumSliders;
+    SDL_bool ff_initialized;
+    DIEFFECT *ffeffect;
+    LPDIRECTINPUTEFFECT ffeffect_ref;
 #endif
 
     SDL_bool bXInputDevice; /* SDL_TRUE if this device supports using the xinput API rather than DirectInput */
@@ -88,6 +92,6 @@ struct joystick_hwdata
 extern const DIDATAFORMAT SDL_c_dfDIJoystick2;
 #endif
 
-extern void SDL_SYS_AddJoystickDevice(JoyStick_DeviceData *device);
+extern void WINDOWS_AddJoystickDevice(JoyStick_DeviceData *device);
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 58 - 13
src/joystick/windows/SDL_xinputjoystick.c

@@ -26,8 +26,10 @@
 
 #include "SDL_assert.h"
 #include "SDL_hints.h"
+#include "SDL_timer.h"
 #include "SDL_windowsjoystick_c.h"
 #include "SDL_xinputjoystick_c.h"
+#include "../hidapi/SDL_hidapijoystick_c.h"
 
 /*
  * Internal stuff.
@@ -186,6 +188,9 @@ GuessXInputDevice(Uint8 userid, Uint16 *pVID, Uint16 *pPID, Uint16 *pVersion)
 static void
 AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
 {
+    Uint16 vendor = 0;
+    Uint16 product = 0;
+    Uint16 version = 0;
     JoyStick_DeviceData *pPrevJoystick = NULL;
     JoyStick_DeviceData *pNewJoystick = *pContext;
 
@@ -229,15 +234,11 @@ AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
     if (SDL_XInputUseOldJoystickMapping()) {
         SDL_zero(pNewJoystick->guid);
     } else {
-        const Uint16 BUS_USB = 0x03;
-        Uint16 vendor = 0;
-        Uint16 product = 0;
-        Uint16 version = 0;
         Uint16 *guid16 = (Uint16 *)pNewJoystick->guid.data;
 
         GuessXInputDevice(userid, &vendor, &product, &version);
 
-        *guid16++ = SDL_SwapLE16(BUS_USB);
+        *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_USB);
         *guid16++ = 0;
         *guid16++ = SDL_SwapLE16(vendor);
         *guid16++ = 0;
@@ -253,12 +254,20 @@ AddXInputDevice(Uint8 userid, BYTE SubType, JoyStick_DeviceData **pContext)
     pNewJoystick->SubType = SubType;
     pNewJoystick->XInputUserId = userid;
 
-    if (SDL_ShouldIgnoreGameController(pNewJoystick->joystickname, pNewJoystick->guid)) {
+    if (SDL_ShouldIgnoreJoystick(pNewJoystick->joystickname, pNewJoystick->guid)) {
         SDL_free(pNewJoystick);
         return;
     }
 
-    SDL_SYS_AddJoystickDevice(pNewJoystick);
+#ifdef SDL_JOYSTICK_HIDAPI
+    if (HIDAPI_IsDevicePresent(vendor, product)) {
+        /* The HIDAPI driver is taking care of this device */
+        SDL_free(pNewJoystick);
+        return;
+    }
+#endif
+
+    WINDOWS_AddJoystickDevice(pNewJoystick);
 }
 
 void
@@ -384,12 +393,12 @@ UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState
     Uint8 button;
     Uint8 hat = SDL_HAT_CENTERED;
 
-    SDL_PrivateJoystickAxis(joystick, 0, (Sint16)pXInputState->Gamepad.sThumbLX);
-    SDL_PrivateJoystickAxis(joystick, 1, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbLY)));
-    SDL_PrivateJoystickAxis(joystick, 2, (Sint16)(((int)pXInputState->Gamepad.bLeftTrigger * 65535 / 255) - 32768));
-    SDL_PrivateJoystickAxis(joystick, 3, (Sint16)pXInputState->Gamepad.sThumbRX);
-    SDL_PrivateJoystickAxis(joystick, 4, (Sint16)(-SDL_max(-32767, pXInputState->Gamepad.sThumbRY)));
-    SDL_PrivateJoystickAxis(joystick, 5, (Sint16)(((int)pXInputState->Gamepad.bRightTrigger * 65535 / 255) - 32768));
+    SDL_PrivateJoystickAxis(joystick, 0, pXInputState->Gamepad.sThumbLX);
+    SDL_PrivateJoystickAxis(joystick, 1, ~pXInputState->Gamepad.sThumbLY);
+    SDL_PrivateJoystickAxis(joystick, 2, ((int)pXInputState->Gamepad.bLeftTrigger * 257) - 32768);
+    SDL_PrivateJoystickAxis(joystick, 3, pXInputState->Gamepad.sThumbRX);
+    SDL_PrivateJoystickAxis(joystick, 4, ~pXInputState->Gamepad.sThumbRY);
+    SDL_PrivateJoystickAxis(joystick, 5, ((int)pXInputState->Gamepad.bRightTrigger * 257) - 32768);
 
     for (button = 0; button < SDL_arraysize(s_XInputButtons); ++button) {
         SDL_PrivateJoystickButton(joystick, button, (wButtons & s_XInputButtons[button]) ? SDL_PRESSED : SDL_RELEASED);
@@ -412,6 +421,29 @@ UpdateXInputJoystickState(SDL_Joystick * joystick, XINPUT_STATE_EX *pXInputState
     UpdateXInputJoystickBatteryInformation(joystick, pBatteryInformation);
 }
 
+int
+SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    XINPUT_VIBRATION XVibration;
+
+    if (!XINPUTSETSTATE) {
+        return SDL_Unsupported();
+    }
+
+    XVibration.wLeftMotorSpeed = low_frequency_rumble;
+    XVibration.wRightMotorSpeed = high_frequency_rumble;
+    if (XINPUTSETSTATE(joystick->hwdata->userid, &XVibration) != ERROR_SUCCESS) {
+        return SDL_SetError("XInputSetState() failed");
+    }
+
+    if ((low_frequency_rumble || high_frequency_rumble) && duration_ms) {
+        joystick->hwdata->rumble_expiration = SDL_GetTicks() + duration_ms;
+    } else {
+        joystick->hwdata->rumble_expiration = 0;
+    }
+    return 0;
+}
+
 void
 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
 {
@@ -449,6 +481,13 @@ SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
         }
         joystick->hwdata->dwPacketNumber = XInputState.dwPacketNumber;
     }
+
+    if (joystick->hwdata->rumble_expiration) {
+        Uint32 now = SDL_GetTicks();
+        if (SDL_TICKS_PASSED(now, joystick->hwdata->rumble_expiration)) {
+            SDL_XINPUT_JoystickRumble(joystick, 0, 0, 0);
+        }
+    }
 }
 
 void
@@ -490,6 +529,12 @@ SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickde
     return SDL_Unsupported();
 }
 
+int
+SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    return SDL_Unsupported();
+}
+
 void
 SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick)
 {

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

@@ -26,6 +26,7 @@ extern SDL_bool SDL_XINPUT_Enabled(void);
 extern int SDL_XINPUT_JoystickInit(void);
 extern void SDL_XINPUT_JoystickDetect(JoyStick_DeviceData **pContext);
 extern int SDL_XINPUT_JoystickOpen(SDL_Joystick * joystick, JoyStick_DeviceData *joystickdevice);
+extern int SDL_XINPUT_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
 extern void SDL_XINPUT_JoystickUpdate(SDL_Joystick * joystick);
 extern void SDL_XINPUT_JoystickClose(SDL_Joystick * joystick);
 extern void SDL_XINPUT_JoystickQuit(void);

+ 12 - 1
src/stdlib/SDL_string.c

@@ -416,6 +416,17 @@ SDL_strlen(const char *string)
 #endif /* HAVE_STRLEN */
 }
 
+wchar_t *
+SDL_wcsdup(const wchar_t *string)
+{
+    size_t len = ((SDL_wcslen(string) + 1) * sizeof(wchar_t));
+    wchar_t *newstr = (wchar_t *)SDL_malloc(len);
+    if (newstr) {
+        SDL_memcpy(newstr, string, len);
+    }
+    return newstr;
+}
+
 size_t
 SDL_wcslen(const wchar_t * string)
 {
@@ -562,7 +573,7 @@ char *
 SDL_strdup(const char *string)
 {
     size_t len = SDL_strlen(string) + 1;
-    char *newstr = SDL_malloc(len);
+    char *newstr = (char *)SDL_malloc(len);
     if (newstr) {
         SDL_memcpy(newstr, string, len);
     }

+ 5 - 0
test/testgamecontroller.c

@@ -114,6 +114,11 @@ loop(void *arg)
         case SDL_CONTROLLERBUTTONDOWN:
         case SDL_CONTROLLERBUTTONUP:
             SDL_Log("Controller button %s %s\n", SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
+            /* First button triggers a 0.5 second full strength rumble */
+            if (event.type == SDL_CONTROLLERBUTTONDOWN &&
+                event.cbutton.button == SDL_CONTROLLER_BUTTON_A) {
+                SDL_GameControllerRumble(gamecontroller, 0xFFFF, 0xFFFF, 500);
+            }
             break;
         case SDL_KEYDOWN:
             if (event.key.keysym.sym != SDLK_ESCAPE) {

+ 4 - 0
test/testjoystick.c

@@ -90,6 +90,10 @@ loop(void *arg)
             case SDL_JOYBUTTONDOWN:
                 SDL_Log("Joystick %d button %d down\n",
                        event.jbutton.which, event.jbutton.button);
+                /* First button triggers a 0.5 second full strength rumble */
+                if (event.jbutton.button == 0) {
+                    SDL_JoystickRumble(joystick, 0xFFFF, 0xFFFF, 500);
+                }
                 break;
             case SDL_JOYBUTTONUP:
                 SDL_Log("Joystick %d button %d up\n",