From 18b7955384b55ded848ca6b55a6cc142b2b78768 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 15 Oct 2022 21:01:38 +0200 Subject: [PATCH] SDL handler --- .ci/build-freebsd.sh | 1 + .ci/build-linux.sh | 1 + .ci/build-mac.sh | 2 +- .gitmodules | 4 + 3rdparty/CMakeLists.txt | 27 +- 3rdparty/libsdl-org/CMakeLists.txt | 11 + 3rdparty/libsdl-org/SDL | 1 + 3rdparty/libsdl-org/SDL.vcxproj | 461 ++++++++++++ 3rdparty/libsdl-org/SDL.vcxproj.filters | 413 +++++++++++ CMakeLists.txt | 2 + rpcs3.sln | 25 +- rpcs3/CMakeLists.txt | 1 + rpcs3/Emu/CMakeLists.txt | 2 +- rpcs3/Emu/Io/pad_config_types.cpp | 3 + rpcs3/Emu/Io/pad_config_types.h | 3 + rpcs3/Input/pad_thread.cpp | 12 + rpcs3/Input/sdl_pad_handler.cpp | 899 ++++++++++++++++++++++++ rpcs3/Input/sdl_pad_handler.h | 136 ++++ rpcs3/emucore.vcxproj | 4 +- rpcs3/rpcs3.vcxproj | 10 +- rpcs3/rpcs3.vcxproj.filters | 9 + rpcs3/rpcs3qt/pad_settings_dialog.cpp | 45 +- rpcs3/rpcs3qt/tooltips.h | 1 + 23 files changed, 2028 insertions(+), 45 deletions(-) create mode 100644 3rdparty/libsdl-org/CMakeLists.txt create mode 160000 3rdparty/libsdl-org/SDL create mode 100644 3rdparty/libsdl-org/SDL.vcxproj create mode 100644 3rdparty/libsdl-org/SDL.vcxproj.filters create mode 100644 rpcs3/Input/sdl_pad_handler.cpp create mode 100644 rpcs3/Input/sdl_pad_handler.h diff --git a/.ci/build-freebsd.sh b/.ci/build-freebsd.sh index 688dda5a30..24038f3ba6 100755 --- a/.ci/build-freebsd.sh +++ b/.ci/build-freebsd.sh @@ -21,6 +21,7 @@ export LDFLAGS="$LDFLAGS -nostdlib++ -L$PWD/libcxx_prefix/lib -l:libc++.a -lcxxr CONFIGURE_ARGS=" -DWITH_LLVM=OFF + -DUSE_SDL=OFF -DUSE_PRECOMPILED_HEADERS=OFF -DUSE_NATIVE_INSTRUCTIONS=OFF -DUSE_SYSTEM_FFMPEG=ON diff --git a/.ci/build-linux.sh b/.ci/build-linux.sh index e8cb1d8fab..2ea86dbff3 100755 --- a/.ci/build-linux.sh +++ b/.ci/build-linux.sh @@ -51,6 +51,7 @@ cmake .. \ -DCMAKE_AR="$AR" \ -DCMAKE_RANLIB="$RANLIB" \ -DUSE_SYSTEM_CURL=ON \ + -DUSE_SDL=OFF \ -DOpenGL_GL_PREFERENCE=LEGACY \ -G Ninja diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index f195a96c36..7325f5b89d 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -19,7 +19,7 @@ sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVer mkdir build && cd build || exit 1 cmake .. \ - -DUSE_DISCORD_RPC=OFF -DUSE_VULKAN=ON -DUSE_ALSA=OFF -DUSE_PULSE=OFF -DUSE_AUDIOUNIT=ON \ + -DUSE_SDL=ON -DUSE_DISCORD_RPC=OFF -DUSE_VULKAN=ON -DUSE_ALSA=OFF -DUSE_PULSE=OFF -DUSE_AUDIOUNIT=ON \ -DLLVM_CCACHE_BUILD=OFF -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_BUILD_RUNTIME=OFF -DLLVM_BUILD_TOOLS=OFF \ -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_TOOLS=OFF \ -DLLVM_INCLUDE_UTILS=OFF -DLLVM_USE_PERF=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF \ diff --git a/.gitmodules b/.gitmodules index 809724a81b..0d6c707eb6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -76,3 +76,7 @@ path = 3rdparty/SoundTouch/soundtouch url = ../../RPCS3/soundtouch.git ignore = dirty +[submodule "3rdparty/libsdl-org/SDL"] + path = 3rdparty/libsdl-org/SDL + url = https://github.com/libsdl-org/SDL.git + ignore = dirty diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 3d84d3f1f5..b15d59106e 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -165,10 +165,10 @@ if(USE_VULKAN) message("-- RPCS3: MoltenVK submodule") execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" ) execute_process(COMMAND "${CMAKE_COMMAND}" --build . - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK" ) add_library(moltenvk_lib SHARED IMPORTED) @@ -329,6 +329,28 @@ add_subdirectory(wolfssl EXCLUDE_FROM_ALL) # CURL add_subdirectory(curl EXCLUDE_FROM_ALL) +# SDL2 +set(SDL2_TARGET 3rdparty_dummy_lib) +if(USE_SDL) + if(USE_SYSTEM_SDL) + find_package(SDL2) + if(SDL2_FOUND AND NOT SDL2_VERSION VERSION_LESS 2.24.0) + message(STATUS "Using system SDL2") + add_library(3rdparty_sdl2 INTERFACE) + target_compile_definitions(3rdparty_sdl2 INTERFACE -DHAVE_SDL2=1) + set(SDL2_TARGET 3rdparty_sdl2) + else() + message(FATAL_ERROR "SDL2 is not available on this system") + endif() + else() + message(STATUS "Using static SDL2 from 3rdparty") + add_library(3rdparty_sdl2 INTERFACE) + target_compile_definitions(3rdparty_sdl2 INTERFACE -DHAVE_SDL2=1) + add_subdirectory(libsdl-org EXCLUDE_FROM_ALL) + set(SDL2_TARGET 3rdparty_sdl2) + endif() +endif() + # add nice ALIAS targets for ease of use if(USE_SYSTEM_LIBUSB) add_library(3rdparty::libusb ALIAS usb-1.0-shared) @@ -355,3 +377,4 @@ add_library(3rdparty::glew ALIAS 3rdparty_glew) add_library(3rdparty::wolfssl ALIAS wolfssl) add_library(3rdparty::libcurl ALIAS libcurl) add_library(3rdparty::soundtouch ALIAS soundtouch) +add_library(3rdparty::sdl2 ALIAS ${SDL2_TARGET}) diff --git a/3rdparty/libsdl-org/CMakeLists.txt b/3rdparty/libsdl-org/CMakeLists.txt new file mode 100644 index 0000000000..2d1190c68d --- /dev/null +++ b/3rdparty/libsdl-org/CMakeLists.txt @@ -0,0 +1,11 @@ +option(SDL2_DISABLE_SDL2MAIN "" ON) +option(SDL2_DISABLE_INSTALL "" ON) +option(SDL2_DISABLE_UNINSTALL "" ON) +set(SDL_SHARED OFF) +set(SDL_SHARED_ENABLED_BY_DEFAULT OFF) +set(SDL_STATIC ON) +set(SDL_STATIC_ENABLED_BY_DEFAULT ON) +set(SDL_TEST OFF) +set(SDL_TEST_ENABLED_BY_DEFAULT OFF) +set(OPT_DEF_LIBC ON) +add_subdirectory(SDL EXCLUDE_FROM_ALL) diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL new file mode 160000 index 0000000000..f17058b562 --- /dev/null +++ b/3rdparty/libsdl-org/SDL @@ -0,0 +1 @@ +Subproject commit f17058b562c8a1090c0c996b42982721ace90903 diff --git a/3rdparty/libsdl-org/SDL.vcxproj b/3rdparty/libsdl-org/SDL.vcxproj new file mode 100644 index 0000000000..2f69808fb8 --- /dev/null +++ b/3rdparty/libsdl-org/SDL.vcxproj @@ -0,0 +1,461 @@ + + + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} + + + + + + StaticLibrary + false + true + Unicode + x64 + + + + + + + + + + + + + + + + + + TurnOffAllWarnings + false + SDL\include;%(AdditionalIncludeDirectories) + ProgramDatabase + + + + + + \ No newline at end of file diff --git a/3rdparty/libsdl-org/SDL.vcxproj.filters b/3rdparty/libsdl-org/SDL.vcxproj.filters new file mode 100644 index 0000000000..e9d2239b00 --- /dev/null +++ b/3rdparty/libsdl-org/SDL.vcxproj.filters @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index baac35ee1b..ca8cc08c1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ option(USE_DISCORD_RPC "Discord rich presence integration" OFF) option(USE_SYSTEM_ZLIB "Prefer system ZLIB instead of the builtin one" ON) option(USE_VULKAN "Vulkan render backend" ON) option(USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) +option(USE_SDL "Enables SDL input handler" OFF) +option(USE_SYSTEM_SDL "Prefer system SDL instead of the builtin one" OFF) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/buildfiles/cmake") diff --git a/rpcs3.sln b/rpcs3.sln index 23bdba5992..173837a6e6 100644 --- a/rpcs3.sln +++ b/rpcs3.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29306.81 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "asmjit", "3rdparty\asmjit\asmjit.vcxproj", "{AC40FF01-426E-4838-A317-66354CEFAE88}" EndProject @@ -50,7 +50,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rpcs3", "rpcs3\rpcs3.vcxpro {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {D6973076-9317-4EF2-A0B8-B7A18AC0713E} {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} {FDA7B080-03B0-48C8-B24F-88118981422A} = {FDA7B080-03B0-48C8-B24F-88118981422A} - {508c291a-3d18-49f5-b25d-f7c8db92cb21} = {508c291a-3d18-49f5-b25d-f7c8db92cb21} + {508C291A-3D18-49F5-B25D-F7C8DB92CB21} = {508C291A-3D18-49F5-B25D-F7C8DB92CB21} {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} = {DA6F56B4-06A4-441D-AD70-AC5A7D51FADB} {FDC361C5-7734-493B-8CFB-037308B35122} = {FDC361C5-7734-493B-8CFB-037308B35122} {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} = {8F85B6CC-250F-4ACA-A617-E820A74E3E3C} @@ -83,7 +83,9 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cubeb", "rpcs3\Cubeb.vcxpro EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcubeb", "3rdparty\cubeb\libcubeb.vcxproj", "{FDA7B080-03B0-48C8-B24F-88118981422A}" EndProject -Project("{508c291a-3d18-49f5-b25d-f7c8db92cb21}") = "soundtouch", "3rdparty\SoundTouch\soundtouch.vcxproj", "{508c291a-3d18-49f5-b25d-f7c8db92cb21}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "soundtouch", "3rdparty\SoundTouch\soundtouch.vcxproj", "{508C291A-3D18-49F5-B25D-F7C8DB92CB21}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL", "3rdparty\libsdl-org\SDL.vcxproj", "{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pine", "pine", "{A55DA1B5-CC17-4525-BE7F-1659CE17BB56}" ProjectSection(SolutionItems) = preProject @@ -176,10 +178,14 @@ Global {FDA7B080-03B0-48C8-B24F-88118981422A}.Debug|x64.Build.0 = Debug|x64 {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.ActiveCfg = Release|x64 {FDA7B080-03B0-48C8-B24F-88118981422A}.Release|x64.Build.0 = Release|x64 - {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Debug|x64.ActiveCfg = Debug|x64 - {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Debug|x64.Build.0 = Debug|x64 - {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Release|x64.ActiveCfg = Release|x64 - {508c291a-3d18-49f5-b25d-f7c8db92cb21}.Release|x64.Build.0 = Release|x64 + {508C291A-3D18-49F5-B25D-F7C8DB92CB21}.Debug|x64.ActiveCfg = Debug|x64 + {508C291A-3D18-49F5-B25D-F7C8DB92CB21}.Debug|x64.Build.0 = Debug|x64 + {508C291A-3D18-49F5-B25D-F7C8DB92CB21}.Release|x64.ActiveCfg = Release|x64 + {508C291A-3D18-49F5-B25D-F7C8DB92CB21}.Release|x64.Build.0 = Release|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|x64.ActiveCfg = Debug|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Debug|x64.Build.0 = Debug|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.ActiveCfg = Release|x64 + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -206,7 +212,8 @@ Global {A37E4273-85DB-4217-B775-CE971B87D9DF} = {B0AC29FD-7B01-4B5E-9C8D-0A081E4C5668} {9610627D-20FE-4B07-8CE3-9FF68A5F1EC2} = {10FBF193-D532-4CCF-B875-4C7091A7F6C2} {FDA7B080-03B0-48C8-B24F-88118981422A} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} - {508c291a-3d18-49f5-b25d-f7c8db92cb21} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} + {508C291A-3D18-49F5-B25D-F7C8DB92CB21} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} + {8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} {A55DA1B5-CC17-4525-BE7F-1659CE17BB56} = {6C3B64A0-8F8A-4DC4-8C0B-D71EBEED7FA8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 6d78ae237b..72b3e06e0b 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -71,6 +71,7 @@ set(RPCS3_SRC Input/keyboard_pad_handler.cpp Input/mm_joystick_handler.cpp Input/pad_thread.cpp + Input/sdl_pad_handler.cpp Input/xinput_pad_handler.cpp ) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 0669bed610..cc17f58a70 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -541,7 +541,7 @@ endif() target_link_libraries(rpcs3_emu PUBLIC - 3rdparty::ffmpeg + 3rdparty::ffmpeg 3rdparty::sdl2 3rdparty::opengl 3rdparty::stblib 3rdparty::vulkan 3rdparty::glew 3rdparty::libusb 3rdparty::wolfssl diff --git a/rpcs3/Emu/Io/pad_config_types.cpp b/rpcs3/Emu/Io/pad_config_types.cpp index 7facfa0aae..9425939f47 100644 --- a/rpcs3/Emu/Io/pad_config_types.cpp +++ b/rpcs3/Emu/Io/pad_config_types.cpp @@ -17,6 +17,9 @@ void fmt_class_string::format(std::string& out, u64 arg) case pad_handler::xinput: return "XInput"; case pad_handler::mm: return "MMJoystick"; #endif +#ifdef HAVE_SDL2 + case pad_handler::sdl: return "SDL"; +#endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: return "Evdev"; #endif diff --git a/rpcs3/Emu/Io/pad_config_types.h b/rpcs3/Emu/Io/pad_config_types.h index 85f3632087..b7f04f8510 100644 --- a/rpcs3/Emu/Io/pad_config_types.h +++ b/rpcs3/Emu/Io/pad_config_types.h @@ -13,6 +13,9 @@ enum class pad_handler xinput, mm, #endif +#ifdef HAVE_SDL2 + sdl, +#endif #ifdef HAVE_LIBEVDEV evdev, #endif diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index a58977c0ca..fb4f509e48 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -10,6 +10,9 @@ #elif HAVE_LIBEVDEV #include "evdev_joystick_handler.h" #endif +#ifdef HAVE_SDL2 +#include "sdl_pad_handler.h" +#endif #include "keyboard_pad_handler.h" #include "Emu/Io/Null/NullPadHandler.h" #include "Emu/Io/PadHandler.h" @@ -171,6 +174,11 @@ void pad_thread::Init() cur_pad_handler = std::make_shared(); break; #endif +#ifdef HAVE_SDL2 + case pad_handler::sdl: + cur_pad_handler = std::make_shared(); + break; +#endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: cur_pad_handler = std::make_shared(); @@ -546,6 +554,10 @@ std::shared_ptr pad_thread::GetHandler(pad_handler type) case pad_handler::mm: return std::make_unique(); #endif +#ifdef HAVE_SDL2 + case pad_handler::sdl: + return std::make_unique(); +#endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: return std::make_unique(); diff --git a/rpcs3/Input/sdl_pad_handler.cpp b/rpcs3/Input/sdl_pad_handler.cpp new file mode 100644 index 0000000000..0b28f38606 --- /dev/null +++ b/rpcs3/Input/sdl_pad_handler.cpp @@ -0,0 +1,899 @@ +#ifdef HAVE_SDL2 + +#include "stdafx.h" +#include "sdl_pad_handler.h" + +LOG_CHANNEL(sdl_log, "SDL"); + +constexpr u32 rumble_duration_ms = 500; // Some high number to keep rumble updates at a minimum. +constexpr u32 rumble_refresh_ms = rumble_duration_ms - 100; // We need to keep updating the rumble. Choose a refresh timeout that is unlikely to run into missed rumble updates. + +sdl_pad_handler::sdl_pad_handler() : PadHandlerBase(pad_handler::sdl) +{ + button_list = + { + { SDLKeyCodes::None, "" }, + { SDLKeyCodes::A, "A" }, + { SDLKeyCodes::B, "B" }, + { SDLKeyCodes::X, "X" }, + { SDLKeyCodes::Y, "Y" }, + { SDLKeyCodes::Left, "Left" }, + { SDLKeyCodes::Right, "Right" }, + { SDLKeyCodes::Up, "Up" }, + { SDLKeyCodes::Down, "Down" }, + { SDLKeyCodes::LB, "LB" }, + { SDLKeyCodes::RB, "RB" }, + { SDLKeyCodes::Back, "Back" }, + { SDLKeyCodes::Start, "Start" }, + { SDLKeyCodes::LS, "LS" }, + { SDLKeyCodes::RS, "RS" }, + { SDLKeyCodes::Guide, "Guide" }, + { SDLKeyCodes::Misc1, "Misc 1" }, + { SDLKeyCodes::Paddle1, "Paddle 1" }, + { SDLKeyCodes::Paddle2, "Paddle 2" }, + { SDLKeyCodes::Paddle3, "Paddle 3" }, + { SDLKeyCodes::Paddle4, "Paddle 4" }, + { SDLKeyCodes::Touchpad, "Touchpad" }, + { SDLKeyCodes::LT, "LT" }, + { SDLKeyCodes::RT, "RT" }, + { SDLKeyCodes::LSXNeg, "LS X-" }, + { SDLKeyCodes::LSXPos, "LS X+" }, + { SDLKeyCodes::LSYPos, "LS Y+" }, + { SDLKeyCodes::LSYNeg, "LS Y-" }, + { SDLKeyCodes::RSXNeg, "RS X-" }, + { SDLKeyCodes::RSXPos, "RS X+" }, + { SDLKeyCodes::RSYPos, "RS Y+" }, + { SDLKeyCodes::RSYNeg, "RS Y-" }, + }; + + init_configs(); + + // Define border values + thumb_max = SDL_JOYSTICK_AXIS_MAX; + trigger_min = 0; + trigger_max = SDL_JOYSTICK_AXIS_MAX; + + // set capabilities + b_has_config = true; + b_has_deadzones = true; + b_has_rumble = true; + b_has_motion = true; + b_has_led = true; + b_has_rgb = true; + b_has_battery = true; + + m_trigger_threshold = trigger_max / 2; + m_thumb_threshold = thumb_max / 2; +} + +sdl_pad_handler::~sdl_pad_handler() +{ + if (!m_is_init) + return; + + for (auto& controller : m_controllers) + { + if (controller.second && controller.second->sdl.game_controller) + { + set_rumble(controller.second.get(), 0, 0); + SDL_GameControllerClose(controller.second->sdl.game_controller); + controller.second->sdl.game_controller = nullptr; + } + } + + SDL_Quit(); +} + +void sdl_pad_handler::init_config(cfg_pad* cfg) +{ + if (!cfg) return; + + // Set default button mapping + cfg->ls_left.def = ::at32(button_list, SDLKeyCodes::LSXNeg); + cfg->ls_down.def = ::at32(button_list, SDLKeyCodes::LSYNeg); + cfg->ls_right.def = ::at32(button_list, SDLKeyCodes::LSXPos); + cfg->ls_up.def = ::at32(button_list, SDLKeyCodes::LSYPos); + cfg->rs_left.def = ::at32(button_list, SDLKeyCodes::RSXNeg); + cfg->rs_down.def = ::at32(button_list, SDLKeyCodes::RSYNeg); + cfg->rs_right.def = ::at32(button_list, SDLKeyCodes::RSXPos); + cfg->rs_up.def = ::at32(button_list, SDLKeyCodes::RSYPos); + cfg->start.def = ::at32(button_list, SDLKeyCodes::Start); + cfg->select.def = ::at32(button_list, SDLKeyCodes::Back); + cfg->ps.def = ::at32(button_list, SDLKeyCodes::Guide); + cfg->square.def = ::at32(button_list, SDLKeyCodes::X); + cfg->cross.def = ::at32(button_list, SDLKeyCodes::A); + cfg->circle.def = ::at32(button_list, SDLKeyCodes::B); + cfg->triangle.def = ::at32(button_list, SDLKeyCodes::Y); + cfg->left.def = ::at32(button_list, SDLKeyCodes::Left); + cfg->down.def = ::at32(button_list, SDLKeyCodes::Down); + cfg->right.def = ::at32(button_list, SDLKeyCodes::Right); + cfg->up.def = ::at32(button_list, SDLKeyCodes::Up); + cfg->r1.def = ::at32(button_list, SDLKeyCodes::RB); + cfg->r2.def = ::at32(button_list, SDLKeyCodes::RT); + cfg->r3.def = ::at32(button_list, SDLKeyCodes::RS); + cfg->l1.def = ::at32(button_list, SDLKeyCodes::LB); + cfg->l2.def = ::at32(button_list, SDLKeyCodes::LT); + cfg->l3.def = ::at32(button_list, SDLKeyCodes::LS); + + cfg->pressure_intensity_button.def = ::at32(button_list, SDLKeyCodes::None); + + // Set default misc variables + cfg->lstickdeadzone.def = 8000; // between 0 and SDL_JOYSTICK_AXIS_MAX + cfg->rstickdeadzone.def = 8000; // between 0 and SDL_JOYSTICK_AXIS_MAX + cfg->ltriggerthreshold.def = 0; // between 0 and SDL_JOYSTICK_AXIS_MAX + cfg->rtriggerthreshold.def = 0; // between 0 and SDL_JOYSTICK_AXIS_MAX + cfg->lpadsquircling.def = 8000; + cfg->rpadsquircling.def = 8000; + + // Set default color value + cfg->colorR.def = 0; + cfg->colorG.def = 0; + cfg->colorB.def = 20; + + // Set default LED options + cfg->led_battery_indicator.def = false; + cfg->led_battery_indicator_brightness.def = 10; + cfg->led_low_battery_blink.def = true; + + // apply defaults + cfg->from_default(); +} + +bool sdl_pad_handler::Init() +{ + if (m_is_init) + return true; + + // Set non-dynamic hints before SDL_Init + if (!SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1")) + { + sdl_log.error("Could not set SDL_HINT_JOYSTICK_THREAD: %s", SDL_GetError()); + } + + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) < 0) + { + sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError()); + return false; + } + + SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE); + SDL_LogSetOutputFunction([](void*, int category, SDL_LogPriority priority, const char* message) + { + std::string category_name; + switch (category) + { + case SDL_LOG_CATEGORY_APPLICATION: + category_name = "app"; + break; + case SDL_LOG_CATEGORY_ERROR: + category_name = "error"; + break; + case SDL_LOG_CATEGORY_ASSERT: + category_name = "assert"; + break; + case SDL_LOG_CATEGORY_SYSTEM: + category_name = "system"; + break; + case SDL_LOG_CATEGORY_AUDIO: + category_name = "audio"; + break; + case SDL_LOG_CATEGORY_VIDEO: + category_name = "video"; + break; + case SDL_LOG_CATEGORY_RENDER: + category_name = "render"; + break; + case SDL_LOG_CATEGORY_INPUT: + category_name = "input"; + break; + case SDL_LOG_CATEGORY_TEST: + category_name = "test"; + break; + default: + category_name = fmt::format("unknown(%d)", category); + break; + } + + switch (priority) + { + case SDL_LOG_PRIORITY_VERBOSE: + case SDL_LOG_PRIORITY_DEBUG: + sdl_log.trace("%s: %s", category_name, message); + break; + case SDL_LOG_PRIORITY_INFO: + sdl_log.notice("%s: %s", category_name, message); + break; + case SDL_LOG_PRIORITY_WARN: + sdl_log.warning("%s: %s", category_name, message); + break; + case SDL_LOG_PRIORITY_ERROR: + sdl_log.error("%s: %s", category_name, message); + break; + case SDL_LOG_PRIORITY_CRITICAL: + sdl_log.error("%s: %s", category_name, message); + break; + default: + break; + } + }, nullptr); + + m_is_init = true; + enumerate_devices(); + + return true; +} + +void sdl_pad_handler::process() +{ + if (!m_is_init) + return; + + SDL_PumpEvents(); + + PadHandlerBase::process(); +} + +SDLDevice::sdl_info sdl_pad_handler::get_sdl_info(int i) +{ + SDLDevice::sdl_info info{}; + info.game_controller = SDL_GameControllerOpen(i); + + if (!info.game_controller) + { + sdl_log.error("Could not open device %d! SDL Error: %s", i, SDL_GetError()); + return {}; + } + + if (const char* name = SDL_GameControllerName(info.game_controller)) + { + info.name = name; + } + + if (const char* path = SDL_GameControllerPath(info.game_controller)) + { + info.path = path; + } + + if (const char* serial = SDL_GameControllerGetSerial(info.game_controller)) + { + info.serial = serial; + } + + info.joystick = SDL_GameControllerGetJoystick(info.game_controller); + info.type = SDL_GameControllerGetType(info.game_controller); + info.vid = SDL_GameControllerGetVendor(info.game_controller); + info.pid = SDL_GameControllerGetProduct(info.game_controller); + info.product_version= SDL_GameControllerGetProductVersion(info.game_controller); + info.firmware_version = SDL_GameControllerGetFirmwareVersion(info.game_controller); + info.has_led = SDL_GameControllerHasLED(info.game_controller); + info.has_rumble = SDL_GameControllerHasRumble(info.game_controller); + info.has_rumble_triggers = SDL_GameControllerHasRumbleTriggers(info.game_controller); + info.has_accel = SDL_GameControllerHasSensor(info.game_controller, SDL_SENSOR_ACCEL); + info.has_gyro = SDL_GameControllerHasSensor(info.game_controller, SDL_SENSOR_GYRO); + + sdl_log.notice("Found game controller %d: type=%d, name='%s', path='%s', serial='%s', vid=0x%x, pid=0x%x, product_version=0x%x, firmware_version=0x%x, has_led=%d, has_rumble=%d, has_rumble_triggers=%d, has_accel=%d, has_gyro=%d", + i, static_cast(info.type), info.name, info.path, info.serial, info.vid, info.pid, info.product_version, info.firmware_version, info.has_led, info.has_rumble, info.has_rumble_triggers, info.has_accel, info.has_gyro); + + if (info.has_accel) + { + if (SDL_GameControllerSetSensorEnabled(info.game_controller, SDL_SENSOR_ACCEL, SDL_TRUE) != 0 || + !SDL_GameControllerIsSensorEnabled(info.game_controller, SDL_SENSOR_ACCEL)) + { + sdl_log.error("Could not activate acceleration sensor of device %d! SDL Error: %s", i, SDL_GetError()); + info.has_accel = false; + } + else + { + info.data_rate_accel = SDL_GameControllerGetSensorDataRate(info.game_controller, SDL_SENSOR_ACCEL); + sdl_log.notice("Acceleration sensor data rate of device %d = %.2f/s", i, info.data_rate_accel); + } + } + + if (info.has_gyro) + { + if (SDL_GameControllerSetSensorEnabled(info.game_controller, SDL_SENSOR_GYRO, SDL_TRUE) != 0 || + !SDL_GameControllerIsSensorEnabled(info.game_controller, SDL_SENSOR_GYRO)) + { + sdl_log.error("Could not activate gyro sensor of device %d! SDL Error: %s", i, SDL_GetError()); + info.has_gyro = false; + } + else + { + info.data_rate_gyro = SDL_GameControllerGetSensorDataRate(info.game_controller, SDL_SENSOR_GYRO); + sdl_log.notice("Gyro sensor data rate of device %d = %.2f/s", i, info.data_rate_accel); + } + } + + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + const SDL_GameControllerButton button_id = static_cast(i); + if (SDL_GameControllerHasButton(info.game_controller, button_id)) + { + info.button_ids.insert(button_id); + } + } + + for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) + { + const SDL_GameControllerAxis axis_id = static_cast(i); + if (SDL_GameControllerHasAxis(info.game_controller, axis_id)) + { + info.axis_ids.insert(axis_id); + } + } + + return info; +} + +std::vector sdl_pad_handler::list_devices() +{ + std::vector pads_list; + + if (!Init()) + return pads_list; + + for (const auto& controller : m_controllers) + { + pads_list.emplace_back(controller.first, false); + } + + return pads_list; +} + +void sdl_pad_handler::enumerate_devices() +{ + if (!m_is_init) + return; + + for (int i = 0; i < SDL_NumJoysticks(); i++) + { + if (!SDL_IsGameController(i)) + { + sdl_log.error("Joystick %d is not game controller interface compatible! SDL Error: %s", i, SDL_GetError()); + continue; + } + + if (SDLDevice::sdl_info info = get_sdl_info(i); info.game_controller) + { + std::string name = info.name; + std::shared_ptr dev = std::make_shared(); + dev->sdl = std::move(info); + m_controllers[std::move(name)] = std::move(dev); + } + } +} + +std::shared_ptr sdl_pad_handler::get_device(const std::string& device) +{ + if (!Init() || device.empty()) + return nullptr; + + if (auto it = m_controllers.find(device); it != m_controllers.end()) + { + return it->second; + } + + // Add a virtual controller until it is actually attached + std::shared_ptr dev = std::make_unique(); + dev->sdl.name = device; + m_controllers.emplace(device, dev); + sdl_log.warning("Adding empty device: %s", device); + + return dev; +} + +PadHandlerBase::connection sdl_pad_handler::update_connection(const std::shared_ptr& device) +{ + if (SDLDevice* dev = static_cast(device.get())) + { + if (dev->sdl.game_controller) + { + if (SDL_GameControllerGetAttached(dev->sdl.game_controller)) + { + if (SDL_HasEvent(SDL_EventType::SDL_CONTROLLERBUTTONDOWN) || + SDL_HasEvent(SDL_EventType::SDL_CONTROLLERBUTTONUP) || + SDL_HasEvent(SDL_EventType::SDL_CONTROLLERAXISMOTION) || + SDL_HasEvent(SDL_EventType::SDL_CONTROLLERSENSORUPDATE) || + SDL_HasEvent(SDL_EventType::SDL_CONTROLLERTOUCHPADUP) || + SDL_HasEvent(SDL_EventType::SDL_CONTROLLERTOUCHPADDOWN) || + SDL_HasEvent(SDL_EventType::SDL_JOYBATTERYUPDATED)) + { + return connection::connected; + } + + return connection::no_data; + } + + SDL_GameControllerClose(dev->sdl.game_controller); + dev->sdl.game_controller = nullptr; + dev->sdl.joystick = nullptr; + } + + // Try to reconnect + + for (int i = 0; i < SDL_NumJoysticks(); i++) + { + if (!SDL_IsGameController(i)) + { + continue; + } + + if (const char* name = SDL_GameControllerNameForIndex(i)) + { + if (dev->sdl.name == name) + { + if (SDLDevice::sdl_info info = get_sdl_info(i); info.game_controller) + { + dev->sdl = std::move(info); + } + break; + } + } + } + } + + return connection::disconnected; +} + +void sdl_pad_handler::SetPadData(const std::string& padId, u8 player_id, u8 large_motor, u8 small_motor, s32 r, s32 g, s32 b, bool /*player_led*/, bool battery_led, u32 battery_led_brightness) +{ + std::shared_ptr device = get_device(padId); + SDLDevice* dev = static_cast(device.get()); + if (!dev) + return; + + dev->player_id = player_id; + + int index = 0; + for (uint i = 0; i < MAX_GAMEPADS; i++) + { + if (g_cfg_input.player[i]->handler == m_type) + { + if (g_cfg_input.player[i]->device.to_string() == padId) + { + m_pad_configs[index].from_string(g_cfg_input.player[i]->config.to_string()); + device->config = &m_pad_configs[index]; + break; + } + index++; + } + } + + set_rumble(dev, large_motor, small_motor); + + if (battery_led) + { + const u32 combined_color = get_battery_color(dev->sdl.power_level, battery_led_brightness); + dev->config->colorR.set(combined_color >> 8); + dev->config->colorG.set(combined_color & 0xff); + dev->config->colorB.set(0); + } + else if (r >= 0 && g >= 0 && b >= 0 && r <= 255 && g <= 255 && b <= 255) + { + dev->config->colorR.set(r); + dev->config->colorG.set(g); + dev->config->colorB.set(b); + } + + if (dev->sdl.has_led && SDL_GameControllerSetLED(dev->sdl.game_controller, r, g, b) != 0) + { + sdl_log.error("Could not set LED of device %d! SDL Error: %s", player_id, SDL_GetError()); + } +} + +u32 sdl_pad_handler::get_battery_color(SDL_JoystickPowerLevel power_level, u32 brightness) const +{ + u32 combined_color{}; + + switch (power_level) + { + default: combined_color = 0xFF00; break; + case SDL_JOYSTICK_POWER_UNKNOWN: combined_color = 0xFF00; break; + case SDL_JOYSTICK_POWER_EMPTY: combined_color = 0xFF33; break; + case SDL_JOYSTICK_POWER_LOW: combined_color = 0xFFCC; break; + case SDL_JOYSTICK_POWER_MEDIUM: combined_color = 0x66FF; break; + case SDL_JOYSTICK_POWER_FULL: combined_color = 0x00FF; break; + case SDL_JOYSTICK_POWER_WIRED: combined_color = 0x00FF; break; + case SDL_JOYSTICK_POWER_MAX: combined_color = 0x00FF; break; + } + + const u32 red = (combined_color >> 8) * brightness / 100; + const u32 green = (combined_color & 0xff) * brightness / 100; + return ((red << 8) | green); +} + +u32 sdl_pad_handler::get_battery_level(const std::string& padId) +{ + std::shared_ptr device = get_device(padId); + SDLDevice* dev = static_cast(device.get()); + if (!dev) + return 0; + + if (dev->sdl.joystick) + { + dev->sdl.power_level = SDL_JoystickCurrentPowerLevel(dev->sdl.joystick); + + switch (dev->sdl.power_level) + { + case SDL_JOYSTICK_POWER_UNKNOWN: return 0; + case SDL_JOYSTICK_POWER_EMPTY: return 5; + case SDL_JOYSTICK_POWER_LOW: return 20; + case SDL_JOYSTICK_POWER_MEDIUM: return 70; + case SDL_JOYSTICK_POWER_FULL: return 100; + case SDL_JOYSTICK_POWER_WIRED: return 100; + case SDL_JOYSTICK_POWER_MAX: return 100; + default: return 0; + } + } + + return 0; +} + +void sdl_pad_handler::get_extended_info(const pad_ensemble& binding) +{ + const auto& pad = binding.pad; + SDLDevice* dev = static_cast(binding.device.get()); + if (!dev || !dev->sdl.game_controller || !pad) + return; + + if (dev->sdl.joystick) + { + dev->sdl.power_level = SDL_JoystickCurrentPowerLevel(dev->sdl.joystick); + pad->m_cable_state = dev->sdl.power_level == SDL_JOYSTICK_POWER_WIRED; + + switch (dev->sdl.power_level) + { + case SDL_JOYSTICK_POWER_UNKNOWN: pad->m_battery_level = 0; break; + case SDL_JOYSTICK_POWER_EMPTY: pad->m_battery_level = 5; break; + case SDL_JOYSTICK_POWER_LOW: pad->m_battery_level = 20; break; + case SDL_JOYSTICK_POWER_MEDIUM: pad->m_battery_level = 70; break; + case SDL_JOYSTICK_POWER_FULL: pad->m_battery_level = 100; break; + case SDL_JOYSTICK_POWER_WIRED: pad->m_battery_level = 100; break; + case SDL_JOYSTICK_POWER_MAX: pad->m_battery_level = 100; break; + default: pad->m_battery_level = 0; break; + } + } + else + { + pad->m_cable_state = 1; + pad->m_battery_level = 100; + } + + if (dev->sdl.has_accel) + { + if (SDL_GameControllerGetSensorData(dev->sdl.game_controller, SDL_SENSOR_ACCEL, dev->values_accel.data(), 3) != 0) + { + sdl_log.error("Could not get acceleration sensor data of device %d! SDL Error: %s", dev->player_id, SDL_GetError()); + } + else + { + const float& accel_x = dev->values_accel[0]; // Angular speed around the x axis (pitch) + const float& accel_y = dev->values_accel[1]; // Angular speed around the y axis (yaw) + const float& accel_z = dev->values_accel[2]; // Angular speed around the z axis (roll + + // Convert to ds3. The ds3 resolution is 113/G. + pad->m_sensors[0].m_value = Clamp0To1023((accel_x / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); + pad->m_sensors[1].m_value = Clamp0To1023((accel_y / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); + pad->m_sensors[2].m_value = Clamp0To1023((accel_z / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); + } + } + + if (dev->sdl.has_gyro) + { + if (SDL_GameControllerGetSensorData(dev->sdl.game_controller, SDL_SENSOR_GYRO, dev->values_gyro.data(), 3) != 0) + { + sdl_log.error("Could not get gyro sensor data of device %d! SDL Error: %s", dev->player_id, SDL_GetError()); + } + else + { + //const float& gyro_x = dev->values_gyro[0]; // Angular speed around the x axis (pitch) + const float& gyro_y = dev->values_gyro[1]; // Angular speed around the y axis (yaw) + //const float& gyro_z = dev->values_gyro[2]; // Angular speed around the z axis (roll) + + // Convert to ds3. The ds3 resolution is 123/90°/sec. The SDL gyro is measured in rad/sec. + static constexpr f32 PI = 3.14159265f; + const float degree = (gyro_y * 180.0f / PI); + pad->m_sensors[3].m_value = Clamp0To1023(degree * (123.f / 90.f) + 512); + } + } +} + +void sdl_pad_handler::get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors) +{ + if (!m_is_init) + return; + + SDL_PumpEvents(); + + PadHandlerBase::get_motion_sensors(pad_id, callback, fail_callback, preview_values, sensors); +} + +PadHandlerBase::connection sdl_pad_handler::get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist, const std::vector& buttons) +{ + if (!m_is_init) + return connection::disconnected; + + SDL_PumpEvents(); + + return PadHandlerBase::get_next_button_press(padId, callback, fail_callback, get_blacklist, buttons); +} + +void sdl_pad_handler::apply_pad_data(const pad_ensemble& binding) +{ + const auto& pad = binding.pad; + SDLDevice* dev = static_cast(binding.device.get()); + if (!dev || !pad) + return; + + cfg_pad* cfg = dev->config; + + // The left motor is the low-frequency rumble motor. The right motor is the high-frequency rumble motor. + // The two motors are not the same, and they create different vibration effects. Values range between 0 to 65535. + const usz idx_l = cfg->switch_vibration_motors ? 1 : 0; + const usz idx_s = cfg->switch_vibration_motors ? 0 : 1; + + if (dev->sdl.has_rumble || dev->sdl.has_rumble_triggers) + { + const u8 speed_large = cfg->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; + const u8 speed_small = cfg->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + + dev->has_new_rumble_data |= dev->large_motor != speed_large || dev->small_motor != speed_small; + + dev->large_motor = speed_large; + dev->small_motor = speed_small; + + const steady_clock::time_point now = steady_clock::now(); + const s64 elapsed_ms = std::chrono::duration_cast(now - dev->last_vibration).count(); + + // XBox One Controller can't handle faster vibration updates than ~10ms. Elite is even worse. So I'll use 20ms to be on the safe side. No lag was noticable. + if ((dev->has_new_rumble_data && elapsed_ms > 20) || (elapsed_ms > rumble_refresh_ms)) + { + set_rumble(dev, speed_large, speed_small); + + dev->has_new_rumble_data = false; + dev->last_vibration = steady_clock::now(); + } + } + + if (dev->sdl.has_led) + { + // Use LEDs to indicate battery level + if (cfg->led_battery_indicator) + { + // This makes sure that the LED color doesn't update every 1ms. DS4 only reports battery level in 10% increments + if (dev->sdl.last_power_level != dev->sdl.power_level) + { + const u32 combined_color = get_battery_color(dev->sdl.power_level, cfg->led_battery_indicator_brightness); + cfg->colorR.set(combined_color >> 8); + cfg->colorG.set(combined_color & 0xff); + cfg->colorB.set(0); + + dev->sdl.last_power_level = dev->sdl.power_level; + dev->led_needs_update = true; + } + } + + // Blink LED when battery is low + if (cfg->led_low_battery_blink) + { + const bool low_battery = pad->m_battery_level <= 20; + + if (dev->led_is_blinking && !low_battery) + { + dev->led_is_blinking = false; + dev->led_needs_update = true; + } + else if (!dev->led_is_blinking && low_battery) + { + dev->led_is_blinking = true; + dev->led_needs_update = true; + } + + if (dev->led_is_blinking) + { + if (const steady_clock::time_point now = steady_clock::now(); (now - dev->led_timestamp) > 500ms) + { + dev->led_is_on = !dev->led_is_on; + dev->led_timestamp = now; + dev->led_needs_update = true; + } + } + } + else if (!dev->led_is_on) + { + dev->led_is_on = true; + dev->led_needs_update = true; + } + + if (dev->led_needs_update) + { + const u8 r = dev->led_is_on ? cfg->colorR : 0; + const u8 g = dev->led_is_on ? cfg->colorG : 0; + const u8 b = dev->led_is_on ? cfg->colorB : 0; + + if (SDL_GameControllerSetLED(dev->sdl.game_controller, r, g, b) != 0) + { + sdl_log.error("Could not set LED of device %d! SDL Error: %s", dev->player_id, SDL_GetError()); + } + + dev->led_needs_update = false; + } + } +} + +void sdl_pad_handler::set_rumble(SDLDevice* dev, u8 speed_large, u8 speed_small) +{ + if (!dev || !dev->sdl.game_controller) return; + + if (dev->sdl.has_rumble) + { + if (SDL_GameControllerRumble(dev->sdl.game_controller, speed_large * 257, speed_small * 257, rumble_duration_ms) != 0) + { + sdl_log.error("Unable to play game controller rumble of player %d! SDL Error: %s", dev->player_id, SDL_GetError()); + } + } + + // NOTE: Disabled for now. The triggers do not seem to be implemented correctly in SDL in the current version. + // The rumble is way too intensive and has a high frequency. It is very displeasing at the moment. + // In the future we could use additional trigger rumble to enhance the existing rumble. + if (false && dev->sdl.has_rumble_triggers) + { + // Only the large motor shall control both triggers. It wouldn't make sense to differentiate here. + if (SDL_GameControllerRumbleTriggers(dev->sdl.game_controller, speed_large * 257, speed_large * 257, rumble_duration_ms) != 0) + { + sdl_log.error("Unable to play game controller trigger rumble of player %d! SDL Error: %s", dev->player_id, SDL_GetError()); + } + } +} + +bool sdl_pad_handler::get_is_left_trigger(const std::shared_ptr& /*device*/, u64 keyCode) +{ + return keyCode == SDLKeyCodes::LT; +} + +bool sdl_pad_handler::get_is_right_trigger(const std::shared_ptr& /*device*/, u64 keyCode) +{ + return keyCode == SDLKeyCodes::RT; +} + +bool sdl_pad_handler::get_is_left_stick(const std::shared_ptr& /*device*/, u64 keyCode) +{ + switch (keyCode) + { + case SDLKeyCodes::LSXNeg: + case SDLKeyCodes::LSXPos: + case SDLKeyCodes::LSYPos: + case SDLKeyCodes::LSYNeg: + return true; + default: + return false; + } +} + +bool sdl_pad_handler::get_is_right_stick(const std::shared_ptr& /*device*/, u64 keyCode) +{ + switch (keyCode) + { + case SDLKeyCodes::RSXNeg: + case SDLKeyCodes::RSXPos: + case SDLKeyCodes::RSYPos: + case SDLKeyCodes::RSYNeg: + return true; + default: + return false; + } +} + +std::unordered_map sdl_pad_handler::get_button_values(const std::shared_ptr& device) +{ + std::unordered_map values; + SDLDevice* dev = static_cast(device.get()); + if (!dev || !dev->sdl.game_controller) + return values; + + for (SDL_GameControllerButton button_id : dev->sdl.button_ids) + { + const u8 value = SDL_GameControllerGetButton(dev->sdl.game_controller, button_id); + const SDLKeyCodes key_code = get_button_code(button_id); + values[key_code] = value; + } + + for (SDL_GameControllerAxis axis_id : dev->sdl.axis_ids) + { + const s16 value = SDL_GameControllerGetAxis(dev->sdl.game_controller, axis_id); + + switch (axis_id) + { + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERLEFT: + values[SDLKeyCodes::LT] = std::max(0, value); + break; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + values[SDLKeyCodes::RT] = std::max(0, value); + break; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX: + values[SDLKeyCodes::LSXNeg] = value < 0 ? std::abs(value) - 1 : 0; + values[SDLKeyCodes::LSXPos] = value > 0 ? value : 0; + break; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY: + values[SDLKeyCodes::LSYNeg] = value > 0 ? value : 0; + values[SDLKeyCodes::LSYPos] = value < 0 ? std::abs(value) - 1 : 0; + break; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTX: + values[SDLKeyCodes::RSXNeg] = value < 0 ? std::abs(value) - 1 : 0; + values[SDLKeyCodes::RSXPos] = value > 0 ? value : 0; + break; + case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_RIGHTY: + values[SDLKeyCodes::RSYNeg] = value > 0 ? value : 0; + values[SDLKeyCodes::RSYPos] = value < 0 ? std::abs(value) - 1 : 0; + break; + default: + break; + } + } + + return values; +} + +pad_preview_values sdl_pad_handler::get_preview_values(const std::unordered_map& data) +{ + return { + ::at32(data, LT), + ::at32(data, RT), + ::at32(data, LSXPos) - ::at32(data, LSXNeg), + ::at32(data, LSYPos) - ::at32(data, LSYNeg), + ::at32(data, RSXPos) - ::at32(data, RSXNeg), + ::at32(data, RSYPos) - ::at32(data, RSYNeg) + }; +} + +std::string sdl_pad_handler::button_to_string(SDL_GameControllerButton button) +{ + if (const char* name = SDL_GameControllerGetStringForButton(button)) + { + return name; + } + + return {}; +} + +std::string sdl_pad_handler::axis_to_string(SDL_GameControllerAxis axis) +{ + if (const char* name = SDL_GameControllerGetStringForAxis(axis)) + { + return name; + } + + return {}; +} + +sdl_pad_handler::SDLKeyCodes sdl_pad_handler::get_button_code(SDL_GameControllerButton button) +{ + switch (button) + { + default: + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_INVALID: return SDLKeyCodes::None; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_A: return SDLKeyCodes::A; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_B: return SDLKeyCodes::B; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_X: return SDLKeyCodes::X; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_Y: return SDLKeyCodes::Y; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT: return SDLKeyCodes::Left; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return SDLKeyCodes::Right; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP: return SDLKeyCodes::Up; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN: return SDLKeyCodes::Down; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return SDLKeyCodes::LB; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return SDLKeyCodes::RB; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_BACK: return SDLKeyCodes::Back; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_START: return SDLKeyCodes::Start; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_LEFTSTICK: return SDLKeyCodes::LS; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_RIGHTSTICK: return SDLKeyCodes::RS; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_GUIDE: return SDLKeyCodes::Guide; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MISC1: return SDLKeyCodes::Misc1; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE1: return SDLKeyCodes::Paddle1; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE2: return SDLKeyCodes::Paddle2; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE3: return SDLKeyCodes::Paddle3; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_PADDLE4: return SDLKeyCodes::Paddle4; + case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_TOUCHPAD: return SDLKeyCodes::Touchpad; + } +} + +#endif diff --git a/rpcs3/Input/sdl_pad_handler.h b/rpcs3/Input/sdl_pad_handler.h new file mode 100644 index 0000000000..7ce8c92af0 --- /dev/null +++ b/rpcs3/Input/sdl_pad_handler.h @@ -0,0 +1,136 @@ +#pragma once + +#ifdef HAVE_SDL2 + +#include "Emu/Io/PadHandler.h" +#include "SDL.h" + +class SDLDevice : public PadDevice +{ +public: + struct sdl_info + { + SDL_GameController* game_controller = nullptr; + SDL_GameControllerType type = SDL_GameControllerType::SDL_CONTROLLER_TYPE_UNKNOWN; + SDL_Joystick* joystick = nullptr; + SDL_JoystickPowerLevel power_level = SDL_JoystickPowerLevel::SDL_JOYSTICK_POWER_UNKNOWN; + SDL_JoystickPowerLevel last_power_level = SDL_JoystickPowerLevel::SDL_JOYSTICK_POWER_UNKNOWN; + + std::string name; + std::string path; + std::string serial; + u16 vid = 0; + u16 pid = 0; + u16 product_version = 0; + u16 firmware_version = 0; + + bool has_led = false; + bool has_rumble = false; + bool has_rumble_triggers = false; + bool has_accel = false; + bool has_gyro = false; + + float data_rate_accel = 0.0f; + float data_rate_gyro = 0.0f; + + std::set button_ids; + std::set axis_ids; + }; + + sdl_info sdl{}; + + std::array values_accel{}; + std::array values_gyro{}; + + bool led_needs_update = true; + bool led_is_on = true; + bool led_is_blinking = false; + steady_clock::time_point led_timestamp{}; + + bool has_new_rumble_data = true; + steady_clock::time_point last_vibration{}; +}; + +class sdl_pad_handler : public PadHandlerBase +{ + enum SDLKeyCodes + { + None = 0, + + A, + B, + X, + Y, + Left, + Right, + Up, + Down, + LB, + RB, + LS, + RS, + Start, + Back, + Guide, + Misc1, + Paddle1, + Paddle2, + Paddle3, + Paddle4, + Touchpad, + + LT, + RT, + + LSXNeg, + LSXPos, + LSYNeg, + LSYPos, + RSXNeg, + RSXPos, + RSYNeg, + RSYPos + }; + +public: + sdl_pad_handler(); + ~sdl_pad_handler(); + + SDLDevice::sdl_info get_sdl_info(int i); + + bool Init() override; + void process() override; + void init_config(cfg_pad* cfg) override; + std::vector list_devices() override; + void SetPadData(const std::string& padId, u8 player_id, u8 large_motor, u8 small_motor, s32 r, s32 g, s32 b, bool player_led, bool battery_led, u32 battery_led_brightness) override; + u32 get_battery_level(const std::string& padId) override; + void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array& sensors) override; + connection get_next_button_press(const std::string& padId, const pad_callback& callback, const pad_fail_callback& fail_callback, bool get_blacklist, const std::vector& buttons) override; + +private: + // pseudo 'controller id' to keep track of unique controllers + std::map> m_controllers; + + void enumerate_devices(); + + std::shared_ptr get_device(const std::string& device) override; + PadHandlerBase::connection update_connection(const std::shared_ptr& device) override; + void get_extended_info(const pad_ensemble& binding) override; + void apply_pad_data(const pad_ensemble& binding) override; + bool get_is_left_trigger(const std::shared_ptr& device, u64 keyCode) override; + bool get_is_right_trigger(const std::shared_ptr& device, u64 keyCode) override; + bool get_is_left_stick(const std::shared_ptr& device, u64 keyCode) override; + bool get_is_right_stick(const std::shared_ptr& device, u64 keyCode) override; + std::unordered_map get_button_values(const std::shared_ptr& device) override; + pad_preview_values get_preview_values(const std::unordered_map& data) override; + + u32 get_battery_color(SDL_JoystickPowerLevel power_level, u32 brightness) const; + void set_rumble(SDLDevice* dev, u8 speed_large, u8 speed_small); + + static std::string button_to_string(SDL_GameControllerButton button); + static std::string axis_to_string(SDL_GameControllerAxis axis); + + static SDLKeyCodes get_button_code(SDL_GameControllerButton button); +}; + +#endif diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index a644dc7c82..fa8fb35f1f 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -42,8 +42,8 @@ Use ..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\zlib\zlib;..\llvm\include;..\llvm_build\include;$(VULKAN_SDK)\Include MaxSpeed - HAVE_VULKAN;%(PreprocessorDefinitions) - HAVE_VULKAN;%(PreprocessorDefinitions) + HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) + HAVE_VULKAN;HAVE_SDL2;%(PreprocessorDefinitions) cmd.exe /c "$(SolutionDir)\Utilities\git-version-gen.cmd" diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 1d95dcd361..96c5a60163 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -67,11 +67,11 @@ $(SolutionDir)lib\$(Configuration)-$(Platform)\;$(UniversalCRT_LibraryPath_x64);$(LibraryPath) - ..\3rdparty\7z\src;..\3rdparty\hidapi\hidapi\hidapi;.\;..\;..\3rdparty\asmjit\asmjit\src;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\ffmpeg\include;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(UniversalCRT_IncludePath);..\3rdparty\XAudio2Redist\include;..\3rdparty\libpng\libpng;..\3rdparty\GL;..\3rdparty\stblib\include;..\3rdparty\OpenAL\include;..\3rdparty\pugixml\src;..\3rdparty\Optional;..\3rdparty\discord-rpc\include;..\3rdparty\zlib\zlib + ..\3rdparty\7z\src;..\3rdparty\hidapi\hidapi\hidapi;.\;..\;..\3rdparty\asmjit\asmjit\src;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\ffmpeg\include;$(VC_IncludePath);$(WindowsSDK_IncludePath);$(UniversalCRT_IncludePath);..\3rdparty\XAudio2Redist\include;..\3rdparty\libpng\libpng;..\3rdparty\GL;..\3rdparty\stblib\include;..\3rdparty\OpenAL\include;..\3rdparty\pugixml\src;..\3rdparty\Optional;..\3rdparty\discord-rpc\include;..\3rdparty\zlib\zlib;..\3rdparty\libsdl-org\SDL\include - ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) + ..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\cubeb\extra;..\3rdparty\cubeb\cubeb\include\;..\3rdparty\flatbuffers\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\curl\curl\include;..\3rdparty\libusb\libusb\libusb;$(VULKAN_SDK)\Include;..\3rdparty\libsdl-org\SDL\include;..\3rdparty\XAudio2Redist\include;$(QTDIR)\include;$(QTDIR)\include\QtWidgets;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtANGLE;$(QTDIR)\include\QtCore;.\release;$(QTDIR)\mkspecs\win32-msvc2015;.\QTGeneratedFiles\$(ConfigurationName);.\QTGeneratedFiles;$(QTDIR)\include\QtWinExtras;$(QTDIR)\include\QtConcurrent;$(QTDIR)\include\QtMultimedia;$(QTDIR)\include\QtMultimediaWidgets;$(QTDIR)\include\QtSvg;%(AdditionalIncludeDirectories) -Zc:strictStrings -Zc:throwingNew- -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) release\ false @@ -79,7 +79,7 @@ 4577;4467;%(DisableSpecificWarnings) $(IntDir) MaxSpeed - _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) + _WINDOWS;UNICODE;WIN32;WIN64;WIN32_LEAN_AND_MEAN;HAVE_VULKAN;HAVE_SDL2;WITH_DISCORD_RPC;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB;NDEBUG;QT_WINEXTRAS_LIB;QT_CONCURRENT_LIB;QT_MULTIMEDIA_LIB;QT_MULTIMEDIAWIDGETS_LIB;QT_SVG_LIB;%(PreprocessorDefinitions) false $(IntDir)vc$(PlatformToolsetVersion).pdb true @@ -88,7 +88,7 @@ Level3 - DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;ksuser.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;%(AdditionalDependencies) + DbgHelp.lib;Ole32.lib;gdi32.lib;..\hidapi.lib;..\libusb-1.0.lib;winmm.lib;imm32.lib;ksuser.lib;version.lib;OpenAL32.lib;XAudio.lib;GLGSRender.lib;shlwapi.lib;VKGSRender.lib;vulkan-1.lib;wolfssl.lib;libcurl.lib;Wldap32.lib;glslang.lib;OSDependent.lib;OGLCompiler.lib;SPIRV.lib;MachineIndependent.lib;GenericCodeGen.lib;Advapi32.lib;user32.lib;zlib.lib;..\libpng16.lib;asmjit.lib;yaml-cpp.lib;discord-rpc.lib;emucore.lib;dxgi.lib;$(QTDIR)\lib\qtmain.lib;shell32.lib;$(QTDIR)\lib\Qt5Widgets.lib;$(QTDIR)\lib\Qt5Gui.lib;$(QTDIR)\lib\Qt5Core.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5WinExtras.lib;Qt5Concurrent.lib;7zlib.lib;SPIRV-Tools.lib;SPIRV-Tools-opt.lib;Qt5Multimedia.lib;Qt5MultimediaWidgets.lib;Qt5Svg.lib;libcubeb.lib;cubeb.lib;soundtouch.lib;Avrt.lib;SDL.lib;%(AdditionalDependencies) ..\3rdparty\OpenAL\libs\Win64;..\3rdparty\glslang\build\hlsl\Release;..\3rdparty\glslang\build\SPIRV\Release;..\3rdparty\glslang\build\OGLCompilersDLL\Release;..\3rdparty\glslang\build\glslang\OSDependent\Windows\Release;..\3rdparty\glslang\build\glslang\Release;..\3rdparty\SPIRV\build\source\Release;..\3rdparty\SPIRV\build\source\opt\Release;..\lib\$(CONFIGURATION)-$(PLATFORM);..\3rdparty\XAudio2Redist\libs;..\3rdparty\discord-rpc\lib;$(QTDIR)\lib;%(AdditionalLibraryDirectories);$(VULKAN_SDK)\Lib "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) true @@ -177,6 +177,7 @@ + @@ -820,6 +821,7 @@ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 2e2c9b8f42..fa8881660a 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -142,6 +142,9 @@ {91389c5b-9ebd-43fe-a288-1bbad775e528} + + {00fa44cd-ccae-48d9-8541-5f3b2b26a845} + @@ -876,6 +879,9 @@ Io\evdev + + Io\SDL + @@ -1040,6 +1046,9 @@ rpcs3 + + Io\SDL + diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 875db6764a..3ef0614994 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -1329,37 +1329,27 @@ void pad_settings_dialog::ChangeHandler() else m_description = tooltips.gamepad_settings.null; break; - case pad_handler::keyboard: - m_description = tooltips.gamepad_settings.keyboard; break; + case pad_handler::keyboard: m_description = tooltips.gamepad_settings.keyboard; break; #ifdef _WIN32 - case pad_handler::xinput: - m_description = tooltips.gamepad_settings.xinput; break; - case pad_handler::mm: - m_description = tooltips.gamepad_settings.mmjoy; break; - case pad_handler::ds3: - m_description = tooltips.gamepad_settings.ds3_windows; break; - case pad_handler::ds4: - m_description = tooltips.gamepad_settings.ds4_windows; break; - case pad_handler::dualsense: - m_description = tooltips.gamepad_settings.dualsense_windows; break; + case pad_handler::xinput: m_description = tooltips.gamepad_settings.xinput; break; + case pad_handler::mm: m_description = tooltips.gamepad_settings.mmjoy; break; + case pad_handler::ds3: m_description = tooltips.gamepad_settings.ds3_windows; break; + case pad_handler::ds4: m_description = tooltips.gamepad_settings.ds4_windows; break; + case pad_handler::dualsense: m_description = tooltips.gamepad_settings.dualsense_windows; break; #elif __linux__ - case pad_handler::ds3: - m_description = tooltips.gamepad_settings.ds3_linux; break; - case pad_handler::ds4: - m_description = tooltips.gamepad_settings.ds4_linux; break; - case pad_handler::dualsense: - m_description = tooltips.gamepad_settings.dualsense_linux; break; + case pad_handler::ds3: m_description = tooltips.gamepad_settings.ds3_linux; break; + case pad_handler::ds4: m_description = tooltips.gamepad_settings.ds4_linux; break; + case pad_handler::dualsense: m_description = tooltips.gamepad_settings.dualsense_linux; break; #else - case pad_handler::ds3: - m_description = tooltips.gamepad_settings.ds3_other; break; - case pad_handler::ds4: - m_description = tooltips.gamepad_settings.ds4_other; break; - case pad_handler::dualsense: - m_description = tooltips.gamepad_settings.dualsense_other; break; + case pad_handler::ds3: m_description = tooltips.gamepad_settings.ds3_other; break; + case pad_handler::ds4: m_description = tooltips.gamepad_settings.ds4_other; break; + case pad_handler::dualsense: m_description = tooltips.gamepad_settings.dualsense_other; break; +#endif +#ifdef HAVE_SDL2 + case pad_handler::sdl: m_description = tooltips.gamepad_settings.sdl; break; #endif #ifdef HAVE_LIBEVDEV - case pad_handler::evdev: - m_description = tooltips.gamepad_settings.evdev; break; + case pad_handler::evdev: m_description = tooltips.gamepad_settings.evdev; break; #endif } ui->l_description->setText(m_description); @@ -1848,6 +1838,9 @@ QString pad_settings_dialog::GetLocalizedPadHandler(const QString& original, pad case pad_handler::xinput: return tr("XInput"); case pad_handler::mm: return tr("MMJoystick"); #endif +#ifdef HAVE_SDL2 + case pad_handler::sdl: return tr("SDL"); +#endif #ifdef HAVE_LIBEVDEV case pad_handler::evdev: return tr("Evdev"); #endif diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 2a1b4dc79f..c484aceff6 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -258,6 +258,7 @@ public: const QString xinput = tr("The XInput handler will work with Xbox controllers and many third-party PC-compatible controllers. Pressure sensitive buttons from SCP are supported when SCP's XInput1_3.dll is placed in the main RPCS3 directory. For more details, see the RPCS3 Wiki."); const QString evdev = tr("The evdev handler should work with any controller that has linux support.
If your joystick is not being centered properly, read the RPCS3 Wiki for instructions."); const QString mmjoy = tr("The MMJoystick handler should work with almost any controller recognized by Windows. However, it is recommended that you use the more specific handlers if you have a controller that supports them."); + const QString sdl = tr("The SDL handler supports a variety of controllers across different platforms."); const QString pressure_intensity = tr("Controls the intensity of pressure sensitive buttons while this special button is pressed.
Use the percentage to change how hard you want to press a button."); const QString squircle_factor = tr("The actual DualShock 3's stick range is not circular but formed like a rounded square (or squircle) which represents the maximum range of the emulated sticks. You can use the squircle values to modify the stick input if your sticks can't reach the corners of that range. A value of 0 does not apply any so called squircling. A value of 8000 is usually recommended.");