From d2042e1ed4e0808103168bf1bf2135601b969c4f Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 9 Aug 2018 16:00:17 -0700 Subject: [PATCH] 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 --- Android.mk | 19 +- VisualC/SDL/SDL.vcxproj | 30 +- VisualC/SDL/SDL.vcxproj.filters | 928 +++++++++--------- Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj | 88 +- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 91 ++ .../main/java/org/libsdl/app/HIDDevice.java | 19 + .../app/HIDDeviceBLESteamController.java | 640 ++++++++++++ .../java/org/libsdl/app/HIDDeviceManager.java | 614 ++++++++++++ .../java/org/libsdl/app/HIDDeviceUSB.java | 298 ++++++ .../main/java/org/libsdl/app/SDLActivity.java | 22 +- configure | 121 ++- configure.in | 70 +- include/SDL_config.h.in | 1 + include/SDL_config_android.h | 1 + include/SDL_config_iphoneos.h | 1 + include/SDL_config_macosx.h | 1 + include/SDL_config_windows.h | 1 + include/SDL_gamecontroller.h | 13 + include/SDL_haptic.h | 6 +- include/SDL_hints.h | 78 ++ include/SDL_joystick.h | 21 +- include/SDL_stdinc.h | 1 + src/dynapi/SDL_dynapi_overrides.h | 3 + src/dynapi/SDL_dynapi_procs.h | 5 +- src/joystick/SDL_gamecontroller.c | 29 +- src/joystick/SDL_gamecontrollerdb.h | 2 + src/joystick/SDL_joystick.c | 376 +++++-- src/joystick/SDL_joystick_c.h | 31 +- src/joystick/SDL_sysjoystick.h | 131 ++- src/joystick/android/SDL_sysjoystick.c | 201 ++-- src/joystick/android/SDL_sysjoystick_c.h | 3 - src/joystick/darwin/SDL_sysjoystick.c | 352 +++++-- src/joystick/darwin/SDL_sysjoystick_c.h | 6 + src/joystick/dummy/SDL_sysjoystick.c | 105 +- src/joystick/hidapi/SDL_hidapi_ps4.c | 533 ++++++++++ src/joystick/hidapi/SDL_hidapi_switch.c | 899 +++++++++++++++++ src/joystick/hidapi/SDL_hidapi_xbox360.c | 363 +++++++ src/joystick/hidapi/SDL_hidapi_xboxone.c | 364 +++++++ src/joystick/hidapi/SDL_hidapijoystick.c | 541 ++++++++++ src/joystick/hidapi/SDL_hidapijoystick_c.h | 70 ++ src/joystick/iphoneos/SDL_sysjoystick.m | 216 ++-- src/joystick/iphoneos/SDL_sysjoystick_c.h | 3 - src/joystick/linux/SDL_sysjoystick.c | 155 ++- src/joystick/linux/SDL_sysjoystick_c.h | 4 + src/joystick/windows/SDL_dinputjoystick.c | 537 ++++++---- src/joystick/windows/SDL_dinputjoystick_c.h | 1 + src/joystick/windows/SDL_windowsjoystick.c | 112 ++- src/joystick/windows/SDL_windowsjoystick_c.h | 6 +- src/joystick/windows/SDL_xinputjoystick.c | 71 +- src/joystick/windows/SDL_xinputjoystick_c.h | 1 + src/stdlib/SDL_string.c | 13 +- test/testgamecontroller.c | 5 + test/testjoystick.c | 4 + 53 files changed, 6833 insertions(+), 1373 deletions(-) mode change 100644 => 100755 Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj mode change 100644 => 100755 Xcode/SDL/SDL.xcodeproj/project.pbxproj create mode 100644 android-project/app/src/main/java/org/libsdl/app/HIDDevice.java create mode 100644 android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java create mode 100644 android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java create mode 100644 android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java create mode 100644 src/joystick/hidapi/SDL_hidapi_ps4.c create mode 100644 src/joystick/hidapi/SDL_hidapi_switch.c create mode 100644 src/joystick/hidapi/SDL_hidapi_xbox360.c create mode 100644 src/joystick/hidapi/SDL_hidapi_xboxone.c create mode 100644 src/joystick/hidapi/SDL_hidapijoystick.c create mode 100644 src/joystick/hidapi/SDL_hidapijoystick_c.h diff --git a/Android.mk b/Android.mk index 976cbab98b252..76125d3d53309 100755 --- a/Android.mk +++ b/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) diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index e2c1909b67f57..7883e405c8ee2 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -80,6 +80,18 @@ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;$(LibraryPath) + + D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath) + + + D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath) + + + D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath) + + + D:\dev\steam\rel\streaming_client\SDL\src\hidapi\hidapi;$(IncludePath) + @@ -109,7 +121,7 @@ _DEBUG;%(PreprocessorDefinitions) - winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) true true Windows @@ -140,7 +152,7 @@ _DEBUG;%(PreprocessorDefinitions) - winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) true true Windows @@ -174,7 +186,7 @@ NDEBUG;%(PreprocessorDefinitions) - winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) true true Windows @@ -206,7 +218,7 @@ NDEBUG;%(PreprocessorDefinitions) - winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) + setupapi.lib;winmm.lib;imm32.lib;version.lib;%(AdditionalDependencies) true true Windows @@ -318,6 +330,8 @@ + + @@ -411,6 +425,12 @@ + + + + + + @@ -523,4 +543,4 @@ - + \ No newline at end of file diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index e0ebb08116ee1..56f88692d676c 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1,461 +1,469 @@ - - - - - {395b3af0-33d0-411b-b153-de1676bf1ef8} - - - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +??? + + + + {395b3af0-33d0-411b-b153-de1676bf1ef8} + + + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj b/Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index 0032309cba259..cef9f4f2434af --- a/Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj +++ b/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 = ""; }; 93CB792513FC5F5300BD3E05 /* SDL_uikitviewcontroller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_uikitviewcontroller.m; sourceTree = ""; }; - A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_steamcontroller.c; sourceTree = ""; }; - A7A9EEA81F702631002A5589 /* SDL_steamcontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_steamcontroller.h; sourceTree = ""; }; + A704172D20F7E74800A82227 /* controller_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_type.h; sourceTree = ""; }; AA0AD06116647BBB00CE5896 /* SDL_gamecontroller.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_gamecontroller.c; sourceTree = ""; }; AA0AD06416647BD400CE5896 /* SDL_gamecontroller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_gamecontroller.h; sourceTree = ""; }; AA0F8494178D5F1A00823F9D /* SDL_systls.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_systls.c; sourceTree = ""; }; @@ -510,6 +519,13 @@ AADA5B8E16CCAB7C00107CF7 /* SDL_bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_bits.h; sourceTree = ""; }; AADC5A611FDA10C800960936 /* SDL_shaders_metal_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_shaders_metal_ios.h; sourceTree = ""; }; AADC5A621FDA10C800960936 /* SDL_render_metal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_render_metal.m; sourceTree = ""; }; + F3BDD77520F51C3C004ECBF3 /* hid.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = hid.mm; sourceTree = ""; }; + F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360.c; sourceTree = ""; }; + F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = ""; }; + F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = ""; }; + F3BDD78E20F51CB8004ECBF3 /* SDL_hidapi_ps4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps4.c; sourceTree = ""; }; + F3BDD79020F51CB8004ECBF3 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = ""; }; + F3BDD79120F51CB8004ECBF3 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = ""; }; FA1DC2701C62BE65008F99A0 /* SDL_uikitclipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_uikitclipboard.h; sourceTree = ""; }; FA1DC2711C62BE65008F99A0 /* SDL_uikitclipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_uikitclipboard.m; sourceTree = ""; }; FAB598141BB5C1B100BE72C5 /* libSDL2.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDL2.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -778,15 +794,6 @@ name = uikit; sourceTree = ""; }; - A7A9EEA61F702607002A5589 /* steam */ = { - isa = PBXGroup; - children = ( - A7A9EEA71F702631002A5589 /* SDL_steamcontroller.c */, - A7A9EEA81F702631002A5589 /* SDL_steamcontroller.h */, - ); - path = steam; - sourceTree = ""; - }; AA13B3521FB8B41700D9FEE6 /* yuv2rgb */ = { isa = PBXGroup; children = ( @@ -807,6 +814,36 @@ path = metal; sourceTree = ""; }; + 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 = ""; + }; + 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 = ""; + }; 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 */, diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj old mode 100644 new mode 100755 index a39186b19fa5c..252eceaa0d786 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/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 = ""; }; 5C2EF6F61FC9EE35003F5197 /* SDL_egl_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_egl_c.h; sourceTree = ""; }; 5C2EF7001FC9EF0F003F5197 /* SDL_egl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_egl.h; sourceTree = ""; }; + A704170820F09A9800A82227 /* hid.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hid.c; sourceTree = ""; }; + A704170D20F09AC800A82227 /* SDL_hidapijoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapijoystick.c; sourceTree = ""; }; + A704170E20F09AC800A82227 /* SDL_hidapijoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_hidapijoystick_c.h; sourceTree = ""; }; + A704170F20F09AC800A82227 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = ""; }; + A704171020F09AC900A82227 /* controller_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = controller_type.h; sourceTree = ""; }; + A704171120F09AC900A82227 /* SDL_hidapi_ps4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps4.c; sourceTree = ""; }; + A704171220F09AC900A82227 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = ""; }; + A704171320F09AC900A82227 /* SDL_hidapi_xbox360.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360.c; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; }; + A704170720F09A6700A82227 /* mac */ = { + isa = PBXGroup; + children = ( + A704170820F09A9800A82227 /* hid.c */, + ); + path = mac; + sourceTree = ""; + }; + 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 = ""; + }; + 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; diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDevice.java b/android-project/app/src/main/java/org/libsdl/app/HIDDevice.java new file mode 100644 index 0000000000000..aa358d1fc341e --- /dev/null +++ b/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(); +} diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java new file mode 100644 index 0000000000000..e8616c0b2aa67 --- /dev/null +++ b/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 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(); + 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(); + } + +} + diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java new file mode 100644 index 0000000000000..ffe6dc0808941 --- /dev/null +++ b/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 mDevicesById = new HashMap(); + private HashMap mUSBDevices = new HashMap(); + private HashMap mBluetoothDevices = new HashMap(); + private int mNextDeviceId = 0; + private SharedPreferences mSharedPreferences = null; + private boolean mIsChromebook = false; + private UsbManager mUsbManager; + private Handler mHandler; + private BluetoothManager mBluetoothManager; + private List 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 disconnected = new ArrayList<>(); + ArrayList connected = new ArrayList<>(); + + List 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); +} diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java new file mode 100644 index 0000000000000..48a87e24d71a3 --- /dev/null +++ b/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); + } + } + } + } + } +} diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java index fd95b0f83fe9e..ebd52abf7d83b 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -80,7 +80,7 @@ public enum NativeState { protected static Hashtable 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 void onClick(DialogInterface dialog,int id) { mClipboardHandler = new SDLClipboardHandler_Old(); } + mHIDDeviceManager = new HIDDeviceManager(this); + // Set up the surface mSurface = new SDLSurface(getApplication()); @@ -276,6 +278,10 @@ protected void onPause() { return; } + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(true); + } + SDLActivity.handleNativeState(); } @@ -290,6 +296,10 @@ protected void onResume() { return; } + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(false); + } + SDLActivity.handleNativeState(); } @@ -330,6 +340,11 @@ public void onLowMemory() { 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 static void handleNativeState() { /* 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(); } diff --git a/configure b/configure index 6c9cde86e5944..e06743de43804 100755 --- a/configure +++ b/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 + #include 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 - #include - #include + #include + #include + #include 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 diff --git a/configure.in b/configure.in index a8f89b5ee457e..6f8103f5d0e32 100644 --- a/configure.in +++ b/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 ]) - 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 + #include ],[ 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 - #include - #include - ],[ + have_xinput2_multitouch=no + AC_TRY_COMPILE([ + #include + #include + #include + ],[ 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 diff --git a/include/SDL_config.h.in b/include/SDL_config.h.in index abcf2f2f36d20..069539e489ee8 100644 --- a/include/SDL_config.h.in +++ b/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 diff --git a/include/SDL_config_android.h b/include/SDL_config_android.h index efe5203123add..2aa17deabb7ac 100644 --- a/include/SDL_config_android.h +++ b/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 */ diff --git a/include/SDL_config_iphoneos.h b/include/SDL_config_iphoneos.h index bc628ed30ea7a..080fd85bf80bd 100644 --- a/include/SDL_config_iphoneos.h +++ b/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 diff --git a/include/SDL_config_macosx.h b/include/SDL_config_macosx.h index cb3d42f94341b..5e0f836dce367 100644 --- a/include/SDL_config_macosx.h +++ b/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 */ diff --git a/include/SDL_config_windows.h b/include/SDL_config_windows.h index 8631a2fe656d5..2a208f8845359 100644 --- a/include/SDL_config_windows.h +++ b/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 diff --git a/include/SDL_gamecontroller.h b/include/SDL_gamecontroller.h index f7e03cdacac49..fb8a7a52540c3 100644 --- a/include/SDL_gamecontroller.h +++ b/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(). */ diff --git a/include/SDL_haptic.h b/include/SDL_haptic.h index e3a2bca5fb198..cfb91c53139e1 100644 --- a/include/SDL_haptic.h +++ b/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. */ diff --git a/include/SDL_hints.h b/include/SDL_hints.h index cb8bed2c0cdc0..fcfc5152c638b 100644 --- a/include/SDL_hints.h +++ b/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" diff --git a/include/SDL_joystick.h b/include/SDL_joystick.h index f67772d713ed7..8303c5e8620bf 100644 --- a/include/SDL_joystick.h +++ b/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(). */ diff --git a/include/SDL_stdinc.h b/include/SDL_stdinc.h index b2ca7ae97b879..e373bc380f688 100644 --- a/include/SDL_stdinc.h +++ b/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); diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 50a39be750864..100b1d063704a 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/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 diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 97cdab1bd6896..c3983f89dd9a6 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/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) diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 6af3220c1c19a..6c85bb544d807 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/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) { diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h index 0302e085ab91b..a33a8c983905f 100644 --- a/src/joystick/SDL_gamecontrollerdb.h +++ b/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 }; diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index ebbd2bf9cff2f..f806387ddf9f9 100644 --- a/src/joystick/SDL_joystick.c +++ b/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 +#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; } diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 2ce622eae2c00..b457617147e5b 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/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); diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h index efcc2d7b2ce59..99ed52b4e34bb 100644 --- a/src/joystick/SDL_sysjoystick.h +++ b/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_ */ diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index 7132a5581b33f..6b12c511bf7b1 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/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 */ diff --git a/src/joystick/android/SDL_sysjoystick_c.h b/src/joystick/android/SDL_sysjoystick_c.h index 0cbf537b59c7a..20d73810d1dc7 100644 --- a/src/joystick/android/SDL_sysjoystick_c.h +++ b/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; diff --git a/src/joystick/darwin/SDL_sysjoystick.c b/src/joystick/darwin/SDL_sysjoystick.c index abfb1c62bf8a0..0b72d052cb7f4 100644 --- a/src/joystick/darwin/SDL_sysjoystick.c +++ b/src/joystick/darwin/SDL_sysjoystick.c @@ -22,29 +22,79 @@ #ifdef SDL_JOYSTICK_IOKIT -#include - -/* For force feedback testing. */ -#include -#include - +#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 */ diff --git a/src/joystick/darwin/SDL_sysjoystick_c.h b/src/joystick/darwin/SDL_sysjoystick_c.h index cde6a5c211814..2168f912e135b 100644 --- a/src/joystick/darwin/SDL_sysjoystick_c.h +++ b/src/joystick/darwin/SDL_sysjoystick_c.h @@ -24,6 +24,8 @@ #define SDL_JOYSTICK_IOKIT_H #include +#include +#include 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 */ diff --git a/src/joystick/dummy/SDL_sysjoystick.c b/src/joystick/dummy/SDL_sysjoystick.c index 3dd96a001406c..c491e13f7e463 100644 --- a/src/joystick/dummy/SDL_sysjoystick.c +++ b/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: */ diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c new file mode 100644 index 0000000000000..a37fcf3b6ac30 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -0,0 +1,533 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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 +#include +#include + +#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: */ diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c new file mode 100644 index 0000000000000..4726949dfe8fa --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -0,0 +1,899 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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: */ diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c new file mode 100644 index 0000000000000..e7e5980074ade --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c @@ -0,0 +1,363 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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: */ diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c new file mode 100644 index 0000000000000..959b281a7f535 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -0,0 +1,364 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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: */ diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c new file mode 100644 index 0000000000000..98720aacddd47 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -0,0 +1,541 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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: */ diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h new file mode 100644 index 0000000000000..fbfd3a33adf63 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -0,0 +1,70 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2018 Sam Lantinga + + 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: */ diff --git a/src/joystick/iphoneos/SDL_sysjoystick.m b/src/joystick/iphoneos/SDL_sysjoystick.m index 2da1e6c2b8e6f..db5ab9a796941 100644 --- a/src/joystick/iphoneos/SDL_sysjoystick.m +++ b/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 SDL_JoystickDeviceItem *deviceList = NULL; static int numjoysticks = 0; -static SDL_JoystickID instancecounter = 0; int SDL_AppleTVRemoteOpenedAsJoystick = 0; static SDL_JoystickDeviceItem * @@ -80,10 +78,9 @@ } 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 @@ /* 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 @@ } 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 @@ } device->accelerometer = accelerometer; - device->instance_id = instancecounter++; + device->instance_id = SDL_GetNextJoystickInstanceID(); if (accelerometer) { #if TARGET_OS_TV @@ -199,7 +196,7 @@ 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 @@ ++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 @@ } #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 @@ static void SteamControllerDisconnectedCallback(int device_instance) 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 @@ static void SteamControllerDisconnectedCallback(int device_instance) 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 @@ static void SteamControllerDisconnectedCallback(int device_instance) #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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) #ifdef SDL_JOYSTICK_MFI static Uint8 -SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) +IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) { Uint8 hat = 0; @@ -551,7 +502,7 @@ SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) #endif static void -SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick) +IOS_MFIJoystickUpdate(SDL_Joystick * joystick) { #if SDL_JOYSTICK_MFI @autoreleasepool { @@ -581,7 +532,7 @@ SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) 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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) 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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) #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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) 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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) } } -/* 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_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) #endif /* SDL_JOYSTICK_MFI */ while (deviceList != NULL) { - SDL_SYS_RemoveJoystickDevice(deviceList); + IOS_RemoveJoystickDevice(deviceList); } #if !TARGET_OS_TV @@ -767,34 +712,23 @@ SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) #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: */ diff --git a/src/joystick/iphoneos/SDL_sysjoystick_c.h b/src/joystick/iphoneos/SDL_sysjoystick_c.h index 54bc549aa0f6a..12aa296b195a2 100644 --- a/src/joystick/iphoneos/SDL_sysjoystick_c.h +++ b/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; diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 457c4b85bc030..5a69aa8b38400 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -29,10 +29,11 @@ /* This is the Linux implementation of the SDL joystick API */ #include -#include +#include /* errno, strerror */ #include -#include #include /* For the definition of PATH_MAX */ +#include +#include #include #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 */ diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h index d06b387eed49a..617e3200dec40 100644 --- a/src/joystick/linux/SDL_sysjoystick_c.h +++ b/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 { diff --git a/src/joystick/windows/SDL_dinputjoystick.c b/src/joystick/windows/SDL_dinputjoystick.c index cff868b28eb60..b6691c2229e87 100644 --- a/src/joystick/windows/SDL_dinputjoystick.c +++ b/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) { diff --git a/src/joystick/windows/SDL_dinputjoystick_c.h b/src/joystick/windows/SDL_dinputjoystick_c.h index 5cc18903b9102..9f29fc751900e 100644 --- a/src/joystick/windows/SDL_dinputjoystick_c.h +++ b/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); diff --git a/src/joystick/windows/SDL_windowsjoystick.c b/src/joystick/windows/SDL_windowsjoystick.c index 45cbea688a4d8..47866b50c5265 100644 --- a/src/joystick/windows/SDL_windowsjoystick.c +++ b/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 */ diff --git a/src/joystick/windows/SDL_windowsjoystick_c.h b/src/joystick/windows/SDL_windowsjoystick_c.h index 01b8b3ab1b2fd..2bccfb52f893d 100644 --- a/src/joystick/windows/SDL_windowsjoystick_c.h +++ b/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: */ diff --git a/src/joystick/windows/SDL_xinputjoystick.c b/src/joystick/windows/SDL_xinputjoystick.c index 977a36bb3fe33..88e33269dc1c8 100644 --- a/src/joystick/windows/SDL_xinputjoystick.c +++ b/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) { diff --git a/src/joystick/windows/SDL_xinputjoystick_c.h b/src/joystick/windows/SDL_xinputjoystick_c.h index 63616ee30f36b..8d57b62f68e0f 100644 --- a/src/joystick/windows/SDL_xinputjoystick_c.h +++ b/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); diff --git a/src/stdlib/SDL_string.c b/src/stdlib/SDL_string.c index 5cbcc4bba769b..22aceedb18106 100644 --- a/src/stdlib/SDL_string.c +++ b/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); } diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c index ef3a02b2f488c..c8616d7cd21ab 100644 --- a/test/testgamecontroller.c +++ b/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) { diff --git a/test/testjoystick.c b/test/testjoystick.c index d2d1c4e2ca3dc..bca7492440104 100644 --- a/test/testjoystick.c +++ b/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",