Merge pull request #4257 from jroweboy/sdl-globals
Input: Remove global variables from SDL Input
This commit is contained in:
		| @@ -81,17 +81,17 @@ void EmuWindow_SDL2::Fullscreen() { | |||||||
| } | } | ||||||
|  |  | ||||||
| EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | ||||||
|     InputCommon::Init(); |  | ||||||
|     Network::Init(); |  | ||||||
|  |  | ||||||
|     SDL_SetMainReady(); |  | ||||||
|  |  | ||||||
|     // Initialize the window |     // Initialize the window | ||||||
|     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { |     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); |         LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     InputCommon::Init(); | ||||||
|  |     Network::Init(); | ||||||
|  |  | ||||||
|  |     SDL_SetMainReady(); | ||||||
|  |  | ||||||
|     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); |     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); | ||||||
|     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); |     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); | ||||||
|     SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); |     SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); | ||||||
| @@ -143,12 +143,10 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { | |||||||
| } | } | ||||||
|  |  | ||||||
| EmuWindow_SDL2::~EmuWindow_SDL2() { | EmuWindow_SDL2::~EmuWindow_SDL2() { | ||||||
|     InputCommon::SDL::CloseSDLJoysticks(); |  | ||||||
|     SDL_GL_DeleteContext(gl_context); |  | ||||||
|     SDL_Quit(); |  | ||||||
|  |  | ||||||
|     Network::Shutdown(); |     Network::Shutdown(); | ||||||
|     InputCommon::Shutdown(); |     InputCommon::Shutdown(); | ||||||
|  |     SDL_GL_DeleteContext(gl_context); | ||||||
|  |     SDL_Quit(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void EmuWindow_SDL2::SwapBuffers() { | void EmuWindow_SDL2::SwapBuffers() { | ||||||
| @@ -190,7 +188,6 @@ void EmuWindow_SDL2::PollEvents() { | |||||||
|             is_open = false; |             is_open = false; | ||||||
|             break; |             break; | ||||||
|         default: |         default: | ||||||
|             InputCommon::SDL::HandleGameControllerEvent(event); |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -114,7 +114,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) | |||||||
|     setWindowTitle(QString::fromStdString(window_title)); |     setWindowTitle(QString::fromStdString(window_title)); | ||||||
|  |  | ||||||
|     InputCommon::Init(); |     InputCommon::Init(); | ||||||
|     InputCommon::StartJoystickEventHandler(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| GRenderWindow::~GRenderWindow() { | GRenderWindow::~GRenderWindow() { | ||||||
|   | |||||||
| @@ -7,20 +7,24 @@ add_library(input_common STATIC | |||||||
|     main.h |     main.h | ||||||
|     motion_emu.cpp |     motion_emu.cpp | ||||||
|     motion_emu.h |     motion_emu.h | ||||||
|  |     sdl/sdl.cpp | ||||||
|  |     sdl/sdl.h | ||||||
|     udp/client.cpp |     udp/client.cpp | ||||||
|     udp/client.h |     udp/client.h | ||||||
|     udp/protocol.cpp |     udp/protocol.cpp | ||||||
|     udp/protocol.h |     udp/protocol.h | ||||||
|     udp/udp.cpp |     udp/udp.cpp | ||||||
|     udp/udp.h |     udp/udp.h | ||||||
|  |  | ||||||
|     $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h> |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| create_target_directory_groups(input_common) |  | ||||||
| target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) |  | ||||||
|  |  | ||||||
| if(SDL2_FOUND) | if(SDL2_FOUND) | ||||||
|  |     target_sources(input_common PRIVATE | ||||||
|  |         sdl/sdl_impl.cpp | ||||||
|  |         sdl/sdl_impl.h | ||||||
|  |     ) | ||||||
|     target_link_libraries(input_common PRIVATE SDL2) |     target_link_libraries(input_common PRIVATE SDL2) | ||||||
|     target_compile_definitions(input_common PRIVATE HAVE_SDL2) |     target_compile_definitions(input_common PRIVATE HAVE_SDL2) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | create_target_directory_groups(input_common) | ||||||
|  | target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) | ||||||
|   | |||||||
| @@ -19,10 +19,7 @@ namespace InputCommon { | |||||||
| static std::shared_ptr<Keyboard> keyboard; | static std::shared_ptr<Keyboard> keyboard; | ||||||
| static std::shared_ptr<MotionEmu> motion_emu; | static std::shared_ptr<MotionEmu> motion_emu; | ||||||
| static std::unique_ptr<CemuhookUDP::State> udp; | static std::unique_ptr<CemuhookUDP::State> udp; | ||||||
|  | static std::unique_ptr<SDL::State> sdl; | ||||||
| #ifdef HAVE_SDL2 |  | ||||||
| static std::thread poll_thread; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| void Init() { | void Init() { | ||||||
|     keyboard = std::make_shared<Keyboard>(); |     keyboard = std::make_shared<Keyboard>(); | ||||||
| @@ -32,30 +29,19 @@ void Init() { | |||||||
|     motion_emu = std::make_shared<MotionEmu>(); |     motion_emu = std::make_shared<MotionEmu>(); | ||||||
|     Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); |     Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); | ||||||
|  |  | ||||||
| #ifdef HAVE_SDL2 |     sdl = SDL::Init(); | ||||||
|     SDL::Init(); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|     udp = CemuhookUDP::Init(); |     udp = CemuhookUDP::Init(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void StartJoystickEventHandler() { |  | ||||||
| #ifdef HAVE_SDL2 |  | ||||||
|     poll_thread = std::thread(SDL::PollLoop); |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Shutdown() { | void Shutdown() { | ||||||
|     Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); |     Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); | ||||||
|     keyboard.reset(); |     keyboard.reset(); | ||||||
|     Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); |     Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); | ||||||
|     Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); |     Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); | ||||||
|     motion_emu.reset(); |     motion_emu.reset(); | ||||||
|  |     sdl.reset(); | ||||||
| #ifdef HAVE_SDL2 |     udp.reset(); | ||||||
|     SDL::Shutdown(); |  | ||||||
|     poll_thread.join(); |  | ||||||
| #endif |  | ||||||
| } | } | ||||||
|  |  | ||||||
| Keyboard* GetKeyboard() { | Keyboard* GetKeyboard() { | ||||||
| @@ -99,7 +85,7 @@ std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { | |||||||
|     std::vector<std::unique_ptr<DevicePoller>> pollers; |     std::vector<std::unique_ptr<DevicePoller>> pollers; | ||||||
|  |  | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
|     SDL::Polling::GetPollers(type, pollers); |     sdl->GetPollers(type, pollers); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     return pollers; |     return pollers; | ||||||
|   | |||||||
| @@ -20,8 +20,6 @@ void Init(); | |||||||
| /// Deregisters all built-in input device factories and shuts them down. | /// Deregisters all built-in input device factories and shuts them down. | ||||||
| void Shutdown(); | void Shutdown(); | ||||||
|  |  | ||||||
| void StartJoystickEventHandler(); |  | ||||||
|  |  | ||||||
| class Keyboard; | class Keyboard; | ||||||
|  |  | ||||||
| /// Gets the keyboard button device factory. | /// Gets the keyboard button device factory. | ||||||
|   | |||||||
| @@ -1,629 +1,19 @@ | |||||||
| // Copyright 2017 Citra Emulator Project | // Copyright 2018 Citra Emulator Project | ||||||
| // Licensed under GPLv2 or any later version | // Licensed under GPLv2 or any later version | ||||||
| // Refer to the license.txt file included. | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
| #include <algorithm> |  | ||||||
| #include <atomic> |  | ||||||
| #include <cmath> |  | ||||||
| #include <functional> |  | ||||||
| #include <iterator> |  | ||||||
| #include <mutex> |  | ||||||
| #include <string> |  | ||||||
| #include <thread> |  | ||||||
| #include <tuple> |  | ||||||
| #include <unordered_map> |  | ||||||
| #include <utility> |  | ||||||
| #include <vector> |  | ||||||
| #include <SDL.h> |  | ||||||
| #include "common/assert.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| #include "common/math_util.h" |  | ||||||
| #include "common/param_package.h" |  | ||||||
| #include "common/threadsafe_queue.h" |  | ||||||
| #include "input_common/main.h" |  | ||||||
| #include "input_common/sdl/sdl.h" | #include "input_common/sdl/sdl.h" | ||||||
|  | #ifdef HAVE_SDL2 | ||||||
|  | #include "input_common/sdl/sdl_impl.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace InputCommon { | namespace InputCommon::SDL { | ||||||
|  |  | ||||||
| namespace SDL { | std::unique_ptr<State> Init() { | ||||||
|  | #ifdef HAVE_SDL2 | ||||||
| class SDLJoystick; |     return std::make_unique<SDLState>(); | ||||||
| class SDLButtonFactory; | #else | ||||||
| class SDLAnalogFactory; |     return std::make_unique<NullState>(); | ||||||
|  | #endif | ||||||
| /// Map of GUID of a list of corresponding virtual Joysticks |  | ||||||
| static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; |  | ||||||
| static std::mutex joystick_map_mutex; |  | ||||||
|  |  | ||||||
| static std::shared_ptr<SDLButtonFactory> button_factory; |  | ||||||
| static std::shared_ptr<SDLAnalogFactory> analog_factory; |  | ||||||
|  |  | ||||||
| /// Used by the Pollers during config |  | ||||||
| static std::atomic<bool> polling; |  | ||||||
| static Common::SPSCQueue<SDL_Event> event_queue; |  | ||||||
|  |  | ||||||
| static std::atomic<bool> initialized = false; |  | ||||||
|  |  | ||||||
| static std::string GetGUID(SDL_Joystick* joystick) { |  | ||||||
|     SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); |  | ||||||
|     char guid_str[33]; |  | ||||||
|     SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); |  | ||||||
|     return guid_str; |  | ||||||
| } | } | ||||||
|  | } // namespace InputCommon::SDL | ||||||
| class SDLJoystick { |  | ||||||
| public: |  | ||||||
|     SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, |  | ||||||
|                 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) |  | ||||||
|         : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} |  | ||||||
|  |  | ||||||
|     void SetButton(int button, bool value) { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         state.buttons[button] = value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool GetButton(int button) const { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         return state.buttons.at(button); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SetAxis(int axis, Sint16 value) { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         state.axes[axis] = value; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     float GetAxis(int axis) const { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         return state.axes.at(axis) / 32767.0f; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { |  | ||||||
|         float x = GetAxis(axis_x); |  | ||||||
|         float y = GetAxis(axis_y); |  | ||||||
|         y = -y; // 3DS uses an y-axis inverse from SDL |  | ||||||
|  |  | ||||||
|         // Make sure the coordinates are in the unit circle, |  | ||||||
|         // otherwise normalize it. |  | ||||||
|         float r = x * x + y * y; |  | ||||||
|         if (r > 1.0f) { |  | ||||||
|             r = std::sqrt(r); |  | ||||||
|             x /= r; |  | ||||||
|             y /= r; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return std::make_tuple(x, y); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SetHat(int hat, Uint8 direction) { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         state.hats[hat] = direction; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool GetHatDirection(int hat, Uint8 direction) const { |  | ||||||
|         std::lock_guard<std::mutex> lock(mutex); |  | ||||||
|         return (state.hats.at(hat) & direction) != 0; |  | ||||||
|     } |  | ||||||
|     /** |  | ||||||
|      * The guid of the joystick |  | ||||||
|      */ |  | ||||||
|     const std::string& GetGUID() const { |  | ||||||
|         return guid; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * The number of joystick from the same type that were connected before this joystick |  | ||||||
|      */ |  | ||||||
|     int GetPort() const { |  | ||||||
|         return port; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_Joystick* GetSDLJoystick() const { |  | ||||||
|         return sdl_joystick.get(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void SetSDLJoystick(SDL_Joystick* joystick, |  | ||||||
|                         decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { |  | ||||||
|         sdl_joystick = |  | ||||||
|             std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     struct State { |  | ||||||
|         std::unordered_map<int, bool> buttons; |  | ||||||
|         std::unordered_map<int, Sint16> axes; |  | ||||||
|         std::unordered_map<int, Uint8> hats; |  | ||||||
|     } state; |  | ||||||
|     std::string guid; |  | ||||||
|     int port; |  | ||||||
|     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; |  | ||||||
|     mutable std::mutex mutex; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Get the nth joystick with the corresponding GUID |  | ||||||
|  */ |  | ||||||
| static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) { |  | ||||||
|     std::lock_guard<std::mutex> lock(joystick_map_mutex); |  | ||||||
|     const auto it = joystick_map.find(guid); |  | ||||||
|     if (it != joystick_map.end()) { |  | ||||||
|         while (it->second.size() <= port) { |  | ||||||
|             auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr, |  | ||||||
|                                                           [](SDL_Joystick*) {}); |  | ||||||
|             it->second.emplace_back(std::move(joystick)); |  | ||||||
|         } |  | ||||||
|         return it->second[port]; |  | ||||||
|     } |  | ||||||
|     auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); |  | ||||||
|     return joystick_map[guid].emplace_back(std::move(joystick)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie |  | ||||||
|  * it to a SDLJoystick with the same guid and that port |  | ||||||
|  */ |  | ||||||
| static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { |  | ||||||
|     std::lock_guard<std::mutex> lock(joystick_map_mutex); |  | ||||||
|     auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); |  | ||||||
|     const std::string guid = GetGUID(sdl_joystick); |  | ||||||
|     auto map_it = joystick_map.find(guid); |  | ||||||
|     if (map_it != joystick_map.end()) { |  | ||||||
|         auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), |  | ||||||
|                                    [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { |  | ||||||
|                                        return sdl_joystick == joystick->GetSDLJoystick(); |  | ||||||
|                                    }); |  | ||||||
|         if (vec_it != map_it->second.end()) { |  | ||||||
|             // This is the common case: There is already an existing SDL_Joystick maped to a |  | ||||||
|             // SDLJoystick. return the SDLJoystick |  | ||||||
|             return *vec_it; |  | ||||||
|         } |  | ||||||
|         // Search for a SDLJoystick without a mapped SDL_Joystick... |  | ||||||
|         auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), |  | ||||||
|                                        [](const std::shared_ptr<SDLJoystick>& joystick) { |  | ||||||
|                                            return !joystick->GetSDLJoystick(); |  | ||||||
|                                        }); |  | ||||||
|         if (nullptr_it != map_it->second.end()) { |  | ||||||
|             // ... and map it |  | ||||||
|             (*nullptr_it)->SetSDLJoystick(sdl_joystick); |  | ||||||
|             return *nullptr_it; |  | ||||||
|         } |  | ||||||
|         // There is no SDLJoystick without a mapped SDL_Joystick |  | ||||||
|         // Create a new SDLJoystick |  | ||||||
|         auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick); |  | ||||||
|         return map_it->second.emplace_back(std::move(joystick)); |  | ||||||
|     } |  | ||||||
|     auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); |  | ||||||
|     return joystick_map[guid].emplace_back(std::move(joystick)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void InitJoystick(int joystick_index) { |  | ||||||
|     std::lock_guard<std::mutex> lock(joystick_map_mutex); |  | ||||||
|     SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); |  | ||||||
|     if (!sdl_joystick) { |  | ||||||
|         LOG_ERROR(Input, "failed to open joystick {}", joystick_index); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     std::string guid = GetGUID(sdl_joystick); |  | ||||||
|     if (joystick_map.find(guid) == joystick_map.end()) { |  | ||||||
|         auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); |  | ||||||
|         joystick_map[guid].emplace_back(std::move(joystick)); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     auto& joystick_guid_list = joystick_map[guid]; |  | ||||||
|     const auto it = std::find_if( |  | ||||||
|         joystick_guid_list.begin(), joystick_guid_list.end(), |  | ||||||
|         [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); |  | ||||||
|     if (it != joystick_guid_list.end()) { |  | ||||||
|         (*it)->SetSDLJoystick(sdl_joystick); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|     auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick); |  | ||||||
|     joystick_guid_list.emplace_back(std::move(joystick)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CloseJoystick(SDL_Joystick* sdl_joystick) { |  | ||||||
|     std::lock_guard<std::mutex> lock(joystick_map_mutex); |  | ||||||
|     std::string guid = GetGUID(sdl_joystick); |  | ||||||
|     // This call to guid is save since the joystick is guranteed to be in that map |  | ||||||
|     auto& joystick_guid_list = joystick_map[guid]; |  | ||||||
|     const auto joystick_it = |  | ||||||
|         std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), |  | ||||||
|                      [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { |  | ||||||
|                          return joystick->GetSDLJoystick() == sdl_joystick; |  | ||||||
|                      }); |  | ||||||
|     (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void HandleGameControllerEvent(const SDL_Event& event) { |  | ||||||
|     switch (event.type) { |  | ||||||
|     case SDL_JOYBUTTONUP: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); |  | ||||||
|         if (joystick) { |  | ||||||
|             joystick->SetButton(event.jbutton.button, false); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYBUTTONDOWN: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); |  | ||||||
|         if (joystick) { |  | ||||||
|             joystick->SetButton(event.jbutton.button, true); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYHATMOTION: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jhat.which); |  | ||||||
|         if (joystick) { |  | ||||||
|             joystick->SetHat(event.jhat.hat, event.jhat.value); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYAXISMOTION: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); |  | ||||||
|         if (joystick) { |  | ||||||
|             joystick->SetAxis(event.jaxis.axis, event.jaxis.value); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYDEVICEREMOVED: |  | ||||||
|         LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); |  | ||||||
|         CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); |  | ||||||
|         break; |  | ||||||
|     case SDL_JOYDEVICEADDED: |  | ||||||
|         LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); |  | ||||||
|         InitJoystick(event.jdevice.which); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CloseSDLJoysticks() { |  | ||||||
|     std::lock_guard<std::mutex> lock(joystick_map_mutex); |  | ||||||
|     joystick_map.clear(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void PollLoop() { |  | ||||||
|     if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { |  | ||||||
|         LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_Event event; |  | ||||||
|     while (initialized) { |  | ||||||
|         // Wait for 10 ms or until an event happens |  | ||||||
|         if (SDL_WaitEventTimeout(&event, 10)) { |  | ||||||
|             // Don't handle the event if we are configuring |  | ||||||
|             if (polling) { |  | ||||||
|                 event_queue.Push(event); |  | ||||||
|             } else { |  | ||||||
|                 HandleGameControllerEvent(event); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     CloseSDLJoysticks(); |  | ||||||
|     SDL_QuitSubSystem(SDL_INIT_JOYSTICK); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class SDLButton final : public Input::ButtonDevice { |  | ||||||
| public: |  | ||||||
|     explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) |  | ||||||
|         : joystick(std::move(joystick_)), button(button_) {} |  | ||||||
|  |  | ||||||
|     bool GetStatus() const override { |  | ||||||
|         return joystick->GetButton(button); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<SDLJoystick> joystick; |  | ||||||
|     int button; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SDLDirectionButton final : public Input::ButtonDevice { |  | ||||||
| public: |  | ||||||
|     explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) |  | ||||||
|         : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} |  | ||||||
|  |  | ||||||
|     bool GetStatus() const override { |  | ||||||
|         return joystick->GetHatDirection(hat, direction); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<SDLJoystick> joystick; |  | ||||||
|     int hat; |  | ||||||
|     Uint8 direction; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SDLAxisButton final : public Input::ButtonDevice { |  | ||||||
| public: |  | ||||||
|     explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, |  | ||||||
|                            bool trigger_if_greater_) |  | ||||||
|         : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), |  | ||||||
|           trigger_if_greater(trigger_if_greater_) {} |  | ||||||
|  |  | ||||||
|     bool GetStatus() const override { |  | ||||||
|         float axis_value = joystick->GetAxis(axis); |  | ||||||
|         if (trigger_if_greater) |  | ||||||
|             return axis_value > threshold; |  | ||||||
|         return axis_value < threshold; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<SDLJoystick> joystick; |  | ||||||
|     int axis; |  | ||||||
|     float threshold; |  | ||||||
|     bool trigger_if_greater; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SDLAnalog final : public Input::AnalogDevice { |  | ||||||
| public: |  | ||||||
|     SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) |  | ||||||
|         : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} |  | ||||||
|  |  | ||||||
|     std::tuple<float, float> GetStatus() const override { |  | ||||||
|         return joystick->GetAnalog(axis_x, axis_y); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     std::shared_ptr<SDLJoystick> joystick; |  | ||||||
|     int axis_x; |  | ||||||
|     int axis_y; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// A button device factory that creates button devices from SDL joystick |  | ||||||
| class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { |  | ||||||
| public: |  | ||||||
|     /** |  | ||||||
|      * Creates a button device from a joystick button |  | ||||||
|      * @param params contains parameters for creating the device: |  | ||||||
|      *     - "guid": the guid of the joystick to bind |  | ||||||
|      *     - "port": the nth joystick of the same type to bind |  | ||||||
|      *     - "button"(optional): the index of the button to bind |  | ||||||
|      *     - "hat"(optional): the index of the hat to bind as direction buttons |  | ||||||
|      *     - "axis"(optional): the index of the axis to bind |  | ||||||
|      *     - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", |  | ||||||
|      *         "down", "left" or "right" |  | ||||||
|      *     - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is |  | ||||||
|      *         triggered if the axis value crosses |  | ||||||
|      *     - "direction"(only used for axis): "+" means the button is triggered when the axis |  | ||||||
|      * value is greater than the threshold; "-" means the button is triggered when the axis |  | ||||||
|      * value is smaller than the threshold |  | ||||||
|      */ |  | ||||||
|     std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { |  | ||||||
|         const std::string guid = params.Get("guid", "0"); |  | ||||||
|         const int port = params.Get("port", 0); |  | ||||||
|  |  | ||||||
|         auto joystick = GetSDLJoystickByGUID(guid, port); |  | ||||||
|  |  | ||||||
|         if (params.Has("hat")) { |  | ||||||
|             const int hat = params.Get("hat", 0); |  | ||||||
|             const std::string direction_name = params.Get("direction", ""); |  | ||||||
|             Uint8 direction; |  | ||||||
|             if (direction_name == "up") { |  | ||||||
|                 direction = SDL_HAT_UP; |  | ||||||
|             } else if (direction_name == "down") { |  | ||||||
|                 direction = SDL_HAT_DOWN; |  | ||||||
|             } else if (direction_name == "left") { |  | ||||||
|                 direction = SDL_HAT_LEFT; |  | ||||||
|             } else if (direction_name == "right") { |  | ||||||
|                 direction = SDL_HAT_RIGHT; |  | ||||||
|             } else { |  | ||||||
|                 direction = 0; |  | ||||||
|             } |  | ||||||
|             // This is necessary so accessing GetHat with hat won't crash |  | ||||||
|             joystick->SetHat(hat, SDL_HAT_CENTERED); |  | ||||||
|             return std::make_unique<SDLDirectionButton>(joystick, hat, direction); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (params.Has("axis")) { |  | ||||||
|             const int axis = params.Get("axis", 0); |  | ||||||
|             const float threshold = params.Get("threshold", 0.5f); |  | ||||||
|             const std::string direction_name = params.Get("direction", ""); |  | ||||||
|             bool trigger_if_greater; |  | ||||||
|             if (direction_name == "+") { |  | ||||||
|                 trigger_if_greater = true; |  | ||||||
|             } else if (direction_name == "-") { |  | ||||||
|                 trigger_if_greater = false; |  | ||||||
|             } else { |  | ||||||
|                 trigger_if_greater = true; |  | ||||||
|                 LOG_ERROR(Input, "Unknown direction {}", direction_name); |  | ||||||
|             } |  | ||||||
|             // This is necessary so accessing GetAxis with axis won't crash |  | ||||||
|             joystick->SetAxis(axis, 0); |  | ||||||
|             return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const int button = params.Get("button", 0); |  | ||||||
|         // This is necessary so accessing GetButton with button won't crash |  | ||||||
|         joystick->SetButton(button, false); |  | ||||||
|         return std::make_unique<SDLButton>(joystick, button); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// An analog device factory that creates analog devices from SDL joystick |  | ||||||
| class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { |  | ||||||
| public: |  | ||||||
|     /** |  | ||||||
|      * Creates analog device from joystick axes |  | ||||||
|      * @param params contains parameters for creating the device: |  | ||||||
|      *     - "guid": the guid of the joystick to bind |  | ||||||
|      *     - "port": the nth joystick of the same type |  | ||||||
|      *     - "axis_x": the index of the axis to be bind as x-axis |  | ||||||
|      *     - "axis_y": the index of the axis to be bind as y-axis |  | ||||||
|      */ |  | ||||||
|     std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { |  | ||||||
|         const std::string guid = params.Get("guid", "0"); |  | ||||||
|         const int port = params.Get("port", 0); |  | ||||||
|         const int axis_x = params.Get("axis_x", 0); |  | ||||||
|         const int axis_y = params.Get("axis_y", 1); |  | ||||||
|  |  | ||||||
|         auto joystick = GetSDLJoystickByGUID(guid, port); |  | ||||||
|  |  | ||||||
|         // This is necessary so accessing GetAxis with axis_x and axis_y won't crash |  | ||||||
|         joystick->SetAxis(axis_x, 0); |  | ||||||
|         joystick->SetAxis(axis_y, 0); |  | ||||||
|         return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| void Init() { |  | ||||||
|     using namespace Input; |  | ||||||
|     RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); |  | ||||||
|     RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); |  | ||||||
|     polling = false; |  | ||||||
|     initialized = true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Shutdown() { |  | ||||||
|     if (initialized) { |  | ||||||
|         using namespace Input; |  | ||||||
|         UnregisterFactory<ButtonDevice>("sdl"); |  | ||||||
|         UnregisterFactory<AnalogDevice>("sdl"); |  | ||||||
|         initialized = false; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { |  | ||||||
|     Common::ParamPackage params({{"engine", "sdl"}}); |  | ||||||
|     switch (event.type) { |  | ||||||
|     case SDL_JOYAXISMOTION: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); |  | ||||||
|         params.Set("port", joystick->GetPort()); |  | ||||||
|         params.Set("guid", joystick->GetGUID()); |  | ||||||
|         params.Set("axis", event.jaxis.axis); |  | ||||||
|         if (event.jaxis.value > 0) { |  | ||||||
|             params.Set("direction", "+"); |  | ||||||
|             params.Set("threshold", "0.5"); |  | ||||||
|         } else { |  | ||||||
|             params.Set("direction", "-"); |  | ||||||
|             params.Set("threshold", "-0.5"); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYBUTTONUP: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); |  | ||||||
|         params.Set("port", joystick->GetPort()); |  | ||||||
|         params.Set("guid", joystick->GetGUID()); |  | ||||||
|         params.Set("button", event.jbutton.button); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     case SDL_JOYHATMOTION: { |  | ||||||
|         auto joystick = GetSDLJoystickBySDLID(event.jhat.which); |  | ||||||
|         params.Set("port", joystick->GetPort()); |  | ||||||
|         params.Set("guid", joystick->GetGUID()); |  | ||||||
|         params.Set("hat", event.jhat.hat); |  | ||||||
|         switch (event.jhat.value) { |  | ||||||
|         case SDL_HAT_UP: |  | ||||||
|             params.Set("direction", "up"); |  | ||||||
|             break; |  | ||||||
|         case SDL_HAT_DOWN: |  | ||||||
|             params.Set("direction", "down"); |  | ||||||
|             break; |  | ||||||
|         case SDL_HAT_LEFT: |  | ||||||
|             params.Set("direction", "left"); |  | ||||||
|             break; |  | ||||||
|         case SDL_HAT_RIGHT: |  | ||||||
|             params.Set("direction", "right"); |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             return {}; |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     } |  | ||||||
|     return params; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| namespace Polling { |  | ||||||
|  |  | ||||||
| class SDLPoller : public InputCommon::Polling::DevicePoller { |  | ||||||
| public: |  | ||||||
|     void Start() override { |  | ||||||
|         event_queue.Clear(); |  | ||||||
|         polling = true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void Stop() override { |  | ||||||
|         polling = false; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SDLButtonPoller final : public SDLPoller { |  | ||||||
| public: |  | ||||||
|     Common::ParamPackage GetNextInput() override { |  | ||||||
|         SDL_Event event; |  | ||||||
|         while (event_queue.Pop(event)) { |  | ||||||
|             switch (event.type) { |  | ||||||
|             case SDL_JOYAXISMOTION: |  | ||||||
|                 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             case SDL_JOYBUTTONUP: |  | ||||||
|             case SDL_JOYHATMOTION: |  | ||||||
|                 return SDLEventToButtonParamPackage(event); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class SDLAnalogPoller final : public SDLPoller { |  | ||||||
| public: |  | ||||||
|     void Start() override { |  | ||||||
|         SDLPoller::Start(); |  | ||||||
|  |  | ||||||
|         // Reset stored axes |  | ||||||
|         analog_xaxis = -1; |  | ||||||
|         analog_yaxis = -1; |  | ||||||
|         analog_axes_joystick = -1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Common::ParamPackage GetNextInput() override { |  | ||||||
|         SDL_Event event; |  | ||||||
|         while (event_queue.Pop(event)) { |  | ||||||
|             if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             // An analog device needs two axes, so we need to store the axis for later and wait for |  | ||||||
|             // a second SDL event. The axes also must be from the same joystick. |  | ||||||
|             int axis = event.jaxis.axis; |  | ||||||
|             if (analog_xaxis == -1) { |  | ||||||
|                 analog_xaxis = axis; |  | ||||||
|                 analog_axes_joystick = event.jaxis.which; |  | ||||||
|             } else if (analog_yaxis == -1 && analog_xaxis != axis && |  | ||||||
|                        analog_axes_joystick == event.jaxis.which) { |  | ||||||
|                 analog_yaxis = axis; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Common::ParamPackage params; |  | ||||||
|         if (analog_xaxis != -1 && analog_yaxis != -1) { |  | ||||||
|             auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); |  | ||||||
|             params.Set("engine", "sdl"); |  | ||||||
|             params.Set("port", joystick->GetPort()); |  | ||||||
|             params.Set("guid", joystick->GetGUID()); |  | ||||||
|             params.Set("axis_x", analog_xaxis); |  | ||||||
|             params.Set("axis_y", analog_yaxis); |  | ||||||
|             analog_xaxis = -1; |  | ||||||
|             analog_yaxis = -1; |  | ||||||
|             analog_axes_joystick = -1; |  | ||||||
|             return params; |  | ||||||
|         } |  | ||||||
|         return params; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|     int analog_xaxis = -1; |  | ||||||
|     int analog_yaxis = -1; |  | ||||||
|     SDL_JoystickID analog_axes_joystick = -1; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| void GetPollers(InputCommon::Polling::DeviceType type, |  | ||||||
|                 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers) { |  | ||||||
|     switch (type) { |  | ||||||
|     case InputCommon::Polling::DeviceType::Analog: |  | ||||||
|         pollers.emplace_back(std::make_unique<SDLAnalogPoller>()); |  | ||||||
|         break; |  | ||||||
|     case InputCommon::Polling::DeviceType::Button: |  | ||||||
|         pollers.emplace_back(std::make_unique<SDLButtonPoller>()); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| } // namespace Polling |  | ||||||
| } // namespace SDL |  | ||||||
| } // namespace InputCommon |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| // Copyright 2017 Citra Emulator Project | // Copyright 2018 Citra Emulator Project | ||||||
| // Licensed under GPLv2 or any later version | // Licensed under GPLv2 or any later version | ||||||
| // Refer to the license.txt file included. | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
| @@ -7,45 +7,38 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include "core/frontend/input.h" | #include "core/frontend/input.h" | ||||||
|  | #include "input_common/main.h" | ||||||
|  |  | ||||||
| union SDL_Event; | union SDL_Event; | ||||||
|  |  | ||||||
| namespace Common { | namespace Common { | ||||||
| class ParamPackage; | class ParamPackage; | ||||||
| } | } // namespace Common | ||||||
| namespace InputCommon { |  | ||||||
| namespace Polling { | namespace InputCommon::Polling { | ||||||
| class DevicePoller; | class DevicePoller; | ||||||
| enum class DeviceType; | enum class DeviceType; | ||||||
| } // namespace Polling | } // namespace InputCommon::Polling | ||||||
| } // namespace InputCommon |  | ||||||
|  |  | ||||||
| namespace InputCommon { | namespace InputCommon::SDL { | ||||||
| namespace SDL { |  | ||||||
|  |  | ||||||
| /// Initializes and registers SDL device factories | class State { | ||||||
| void Init(); | public: | ||||||
|  |     /// Unresisters SDL device factories and shut them down. | ||||||
|  |     virtual ~State() = default; | ||||||
|  |  | ||||||
| /// Unresisters SDL device factories and shut them down. |     virtual void GetPollers( | ||||||
| void Shutdown(); |         InputCommon::Polling::DeviceType type, | ||||||
|  |         std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
| /// Needs to be called before SDL_QuitSubSystem. | class NullState : public State { | ||||||
| void CloseSDLJoysticks(); | public: | ||||||
|  |     void GetPollers( | ||||||
|  |         InputCommon::Polling::DeviceType type, | ||||||
|  |         std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers) override {} | ||||||
|  | }; | ||||||
|  |  | ||||||
| /// Handle SDL_Events for joysticks from SDL_PollEvent | std::unique_ptr<State> Init(); | ||||||
| void HandleGameControllerEvent(const SDL_Event& event); |  | ||||||
|  |  | ||||||
| /// A Loop that calls HandleGameControllerEvent until Shutdown is called | } // namespace InputCommon::SDL | ||||||
| void PollLoop(); |  | ||||||
|  |  | ||||||
| /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice |  | ||||||
| Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); |  | ||||||
|  |  | ||||||
| namespace Polling { |  | ||||||
|  |  | ||||||
| /// Get all DevicePoller that use the SDL backend for a specific device type |  | ||||||
| void GetPollers(InputCommon::Polling::DeviceType type, |  | ||||||
|                 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers); |  | ||||||
|  |  | ||||||
| } // namespace Polling |  | ||||||
| } // namespace SDL |  | ||||||
| } // namespace InputCommon |  | ||||||
|   | |||||||
							
								
								
									
										651
									
								
								src/input_common/sdl/sdl_impl.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										651
									
								
								src/input_common/sdl/sdl_impl.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,651 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include <atomic> | ||||||
|  | #include <cmath> | ||||||
|  | #include <functional> | ||||||
|  | #include <iterator> | ||||||
|  | #include <mutex> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <tuple> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <utility> | ||||||
|  | #include <vector> | ||||||
|  | #include <SDL.h> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/math_util.h" | ||||||
|  | #include "common/param_package.h" | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
|  | #include "core/frontend/input.h" | ||||||
|  | #include "input_common/sdl/sdl_impl.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon { | ||||||
|  |  | ||||||
|  | namespace SDL { | ||||||
|  |  | ||||||
|  | static std::string GetGUID(SDL_Joystick* joystick) { | ||||||
|  |     SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); | ||||||
|  |     char guid_str[33]; | ||||||
|  |     SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); | ||||||
|  |     return guid_str; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice | ||||||
|  | static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); | ||||||
|  |  | ||||||
|  | static int SDLEventWatcher(void* userdata, SDL_Event* event) { | ||||||
|  |     SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata); | ||||||
|  |     // Don't handle the event if we are configuring | ||||||
|  |     if (sdl_state->polling) { | ||||||
|  |         sdl_state->event_queue.Push(*event); | ||||||
|  |     } else { | ||||||
|  |         sdl_state->HandleGameControllerEvent(*event); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SDLJoystick { | ||||||
|  | public: | ||||||
|  |     SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, | ||||||
|  |                 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) | ||||||
|  |         : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} | ||||||
|  |  | ||||||
|  |     void SetButton(int button, bool value) { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         state.buttons[button] = value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool GetButton(int button) const { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         return state.buttons.at(button); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void SetAxis(int axis, Sint16 value) { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         state.axes[axis] = value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     float GetAxis(int axis) const { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         return state.axes.at(axis) / 32767.0f; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { | ||||||
|  |         float x = GetAxis(axis_x); | ||||||
|  |         float y = GetAxis(axis_y); | ||||||
|  |         y = -y; // 3DS uses an y-axis inverse from SDL | ||||||
|  |  | ||||||
|  |         // Make sure the coordinates are in the unit circle, | ||||||
|  |         // otherwise normalize it. | ||||||
|  |         float r = x * x + y * y; | ||||||
|  |         if (r > 1.0f) { | ||||||
|  |             r = std::sqrt(r); | ||||||
|  |             x /= r; | ||||||
|  |             y /= r; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return std::make_tuple(x, y); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void SetHat(int hat, Uint8 direction) { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         state.hats[hat] = direction; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool GetHatDirection(int hat, Uint8 direction) const { | ||||||
|  |         std::lock_guard<std::mutex> lock(mutex); | ||||||
|  |         return (state.hats.at(hat) & direction) != 0; | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * The guid of the joystick | ||||||
|  |      */ | ||||||
|  |     const std::string& GetGUID() const { | ||||||
|  |         return guid; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The number of joystick from the same type that were connected before this joystick | ||||||
|  |      */ | ||||||
|  |     int GetPort() const { | ||||||
|  |         return port; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_Joystick* GetSDLJoystick() const { | ||||||
|  |         return sdl_joystick.get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void SetSDLJoystick(SDL_Joystick* joystick, | ||||||
|  |                         decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { | ||||||
|  |         sdl_joystick = | ||||||
|  |             std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     struct State { | ||||||
|  |         std::unordered_map<int, bool> buttons; | ||||||
|  |         std::unordered_map<int, Sint16> axes; | ||||||
|  |         std::unordered_map<int, Uint8> hats; | ||||||
|  |     } state; | ||||||
|  |     std::string guid; | ||||||
|  |     int port; | ||||||
|  |     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; | ||||||
|  |     mutable std::mutex mutex; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Get the nth joystick with the corresponding GUID | ||||||
|  |  */ | ||||||
|  | std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { | ||||||
|  |     std::lock_guard<std::mutex> lock(joystick_map_mutex); | ||||||
|  |     const auto it = joystick_map.find(guid); | ||||||
|  |     if (it != joystick_map.end()) { | ||||||
|  |         while (it->second.size() <= port) { | ||||||
|  |             auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr, | ||||||
|  |                                                           [](SDL_Joystick*) {}); | ||||||
|  |             it->second.emplace_back(std::move(joystick)); | ||||||
|  |         } | ||||||
|  |         return it->second[port]; | ||||||
|  |     } | ||||||
|  |     auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); | ||||||
|  |     return joystick_map[guid].emplace_back(std::move(joystick)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie | ||||||
|  |  * it to a SDLJoystick with the same guid and that port | ||||||
|  |  */ | ||||||
|  | std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { | ||||||
|  |     std::lock_guard<std::mutex> lock(joystick_map_mutex); | ||||||
|  |     auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); | ||||||
|  |     const std::string guid = GetGUID(sdl_joystick); | ||||||
|  |     auto map_it = joystick_map.find(guid); | ||||||
|  |     if (map_it != joystick_map.end()) { | ||||||
|  |         auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), | ||||||
|  |                                    [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { | ||||||
|  |                                        return sdl_joystick == joystick->GetSDLJoystick(); | ||||||
|  |                                    }); | ||||||
|  |         if (vec_it != map_it->second.end()) { | ||||||
|  |             // This is the common case: There is already an existing SDL_Joystick maped to a | ||||||
|  |             // SDLJoystick. return the SDLJoystick | ||||||
|  |             return *vec_it; | ||||||
|  |         } | ||||||
|  |         // Search for a SDLJoystick without a mapped SDL_Joystick... | ||||||
|  |         auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), | ||||||
|  |                                        [](const std::shared_ptr<SDLJoystick>& joystick) { | ||||||
|  |                                            return !joystick->GetSDLJoystick(); | ||||||
|  |                                        }); | ||||||
|  |         if (nullptr_it != map_it->second.end()) { | ||||||
|  |             // ... and map it | ||||||
|  |             (*nullptr_it)->SetSDLJoystick(sdl_joystick); | ||||||
|  |             return *nullptr_it; | ||||||
|  |         } | ||||||
|  |         // There is no SDLJoystick without a mapped SDL_Joystick | ||||||
|  |         // Create a new SDLJoystick | ||||||
|  |         auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick); | ||||||
|  |         return map_it->second.emplace_back(std::move(joystick)); | ||||||
|  |     } | ||||||
|  |     auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); | ||||||
|  |     return joystick_map[guid].emplace_back(std::move(joystick)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDLState::InitJoystick(int joystick_index) { | ||||||
|  |     std::lock_guard<std::mutex> lock(joystick_map_mutex); | ||||||
|  |     SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); | ||||||
|  |     if (!sdl_joystick) { | ||||||
|  |         LOG_ERROR(Input, "failed to open joystick {}", joystick_index); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     std::string guid = GetGUID(sdl_joystick); | ||||||
|  |     if (joystick_map.find(guid) == joystick_map.end()) { | ||||||
|  |         auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); | ||||||
|  |         joystick_map[guid].emplace_back(std::move(joystick)); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     auto& joystick_guid_list = joystick_map[guid]; | ||||||
|  |     const auto it = std::find_if( | ||||||
|  |         joystick_guid_list.begin(), joystick_guid_list.end(), | ||||||
|  |         [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); | ||||||
|  |     if (it != joystick_guid_list.end()) { | ||||||
|  |         (*it)->SetSDLJoystick(sdl_joystick); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick); | ||||||
|  |     joystick_guid_list.emplace_back(std::move(joystick)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { | ||||||
|  |     std::lock_guard<std::mutex> lock(joystick_map_mutex); | ||||||
|  |     std::string guid = GetGUID(sdl_joystick); | ||||||
|  |     // This call to guid is safe since the joystick is guaranteed to be in the map | ||||||
|  |     auto& joystick_guid_list = joystick_map[guid]; | ||||||
|  |     const auto joystick_it = | ||||||
|  |         std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), | ||||||
|  |                      [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { | ||||||
|  |                          return joystick->GetSDLJoystick() == sdl_joystick; | ||||||
|  |                      }); | ||||||
|  |     (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDLState::HandleGameControllerEvent(const SDL_Event& event) { | ||||||
|  |     switch (event.type) { | ||||||
|  |     case SDL_JOYBUTTONUP: { | ||||||
|  |         if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { | ||||||
|  |             joystick->SetButton(event.jbutton.button, false); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYBUTTONDOWN: { | ||||||
|  |         if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { | ||||||
|  |             joystick->SetButton(event.jbutton.button, true); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYHATMOTION: { | ||||||
|  |         if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { | ||||||
|  |             joystick->SetHat(event.jhat.hat, event.jhat.value); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYAXISMOTION: { | ||||||
|  |         if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { | ||||||
|  |             joystick->SetAxis(event.jaxis.axis, event.jaxis.value); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYDEVICEREMOVED: | ||||||
|  |         LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); | ||||||
|  |         CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); | ||||||
|  |         break; | ||||||
|  |     case SDL_JOYDEVICEADDED: | ||||||
|  |         LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); | ||||||
|  |         InitJoystick(event.jdevice.which); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SDLState::CloseJoysticks() { | ||||||
|  |     std::lock_guard<std::mutex> lock(joystick_map_mutex); | ||||||
|  |     joystick_map.clear(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class SDLButton final : public Input::ButtonDevice { | ||||||
|  | public: | ||||||
|  |     explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) | ||||||
|  |         : joystick(std::move(joystick_)), button(button_) {} | ||||||
|  |  | ||||||
|  |     bool GetStatus() const override { | ||||||
|  |         return joystick->GetButton(button); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<SDLJoystick> joystick; | ||||||
|  |     int button; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SDLDirectionButton final : public Input::ButtonDevice { | ||||||
|  | public: | ||||||
|  |     explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) | ||||||
|  |         : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} | ||||||
|  |  | ||||||
|  |     bool GetStatus() const override { | ||||||
|  |         return joystick->GetHatDirection(hat, direction); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<SDLJoystick> joystick; | ||||||
|  |     int hat; | ||||||
|  |     Uint8 direction; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SDLAxisButton final : public Input::ButtonDevice { | ||||||
|  | public: | ||||||
|  |     explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, | ||||||
|  |                            bool trigger_if_greater_) | ||||||
|  |         : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), | ||||||
|  |           trigger_if_greater(trigger_if_greater_) {} | ||||||
|  |  | ||||||
|  |     bool GetStatus() const override { | ||||||
|  |         float axis_value = joystick->GetAxis(axis); | ||||||
|  |         if (trigger_if_greater) | ||||||
|  |             return axis_value > threshold; | ||||||
|  |         return axis_value < threshold; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<SDLJoystick> joystick; | ||||||
|  |     int axis; | ||||||
|  |     float threshold; | ||||||
|  |     bool trigger_if_greater; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SDLAnalog final : public Input::AnalogDevice { | ||||||
|  | public: | ||||||
|  |     SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_) | ||||||
|  |         : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} | ||||||
|  |  | ||||||
|  |     std::tuple<float, float> GetStatus() const override { | ||||||
|  |         return joystick->GetAnalog(axis_x, axis_y); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<SDLJoystick> joystick; | ||||||
|  |     int axis_x; | ||||||
|  |     int axis_y; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// A button device factory that creates button devices from SDL joystick | ||||||
|  | class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { | ||||||
|  | public: | ||||||
|  |     explicit SDLButtonFactory(SDLState& state_) : state(state_) {} | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Creates a button device from a joystick button | ||||||
|  |      * @param params contains parameters for creating the device: | ||||||
|  |      *     - "guid": the guid of the joystick to bind | ||||||
|  |      *     - "port": the nth joystick of the same type to bind | ||||||
|  |      *     - "button"(optional): the index of the button to bind | ||||||
|  |      *     - "hat"(optional): the index of the hat to bind as direction buttons | ||||||
|  |      *     - "axis"(optional): the index of the axis to bind | ||||||
|  |      *     - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", | ||||||
|  |      *         "down", "left" or "right" | ||||||
|  |      *     - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is | ||||||
|  |      *         triggered if the axis value crosses | ||||||
|  |      *     - "direction"(only used for axis): "+" means the button is triggered when the axis | ||||||
|  |      * value is greater than the threshold; "-" means the button is triggered when the axis | ||||||
|  |      * value is smaller than the threshold | ||||||
|  |      */ | ||||||
|  |     std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { | ||||||
|  |         const std::string guid = params.Get("guid", "0"); | ||||||
|  |         const int port = params.Get("port", 0); | ||||||
|  |  | ||||||
|  |         auto joystick = state.GetSDLJoystickByGUID(guid, port); | ||||||
|  |  | ||||||
|  |         if (params.Has("hat")) { | ||||||
|  |             const int hat = params.Get("hat", 0); | ||||||
|  |             const std::string direction_name = params.Get("direction", ""); | ||||||
|  |             Uint8 direction; | ||||||
|  |             if (direction_name == "up") { | ||||||
|  |                 direction = SDL_HAT_UP; | ||||||
|  |             } else if (direction_name == "down") { | ||||||
|  |                 direction = SDL_HAT_DOWN; | ||||||
|  |             } else if (direction_name == "left") { | ||||||
|  |                 direction = SDL_HAT_LEFT; | ||||||
|  |             } else if (direction_name == "right") { | ||||||
|  |                 direction = SDL_HAT_RIGHT; | ||||||
|  |             } else { | ||||||
|  |                 direction = 0; | ||||||
|  |             } | ||||||
|  |             // This is necessary so accessing GetHat with hat won't crash | ||||||
|  |             joystick->SetHat(hat, SDL_HAT_CENTERED); | ||||||
|  |             return std::make_unique<SDLDirectionButton>(joystick, hat, direction); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (params.Has("axis")) { | ||||||
|  |             const int axis = params.Get("axis", 0); | ||||||
|  |             const float threshold = params.Get("threshold", 0.5f); | ||||||
|  |             const std::string direction_name = params.Get("direction", ""); | ||||||
|  |             bool trigger_if_greater; | ||||||
|  |             if (direction_name == "+") { | ||||||
|  |                 trigger_if_greater = true; | ||||||
|  |             } else if (direction_name == "-") { | ||||||
|  |                 trigger_if_greater = false; | ||||||
|  |             } else { | ||||||
|  |                 trigger_if_greater = true; | ||||||
|  |                 LOG_ERROR(Input, "Unknown direction {}", direction_name); | ||||||
|  |             } | ||||||
|  |             // This is necessary so accessing GetAxis with axis won't crash | ||||||
|  |             joystick->SetAxis(axis, 0); | ||||||
|  |             return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const int button = params.Get("button", 0); | ||||||
|  |         // This is necessary so accessing GetButton with button won't crash | ||||||
|  |         joystick->SetButton(button, false); | ||||||
|  |         return std::make_unique<SDLButton>(joystick, button); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     SDLState& state; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// An analog device factory that creates analog devices from SDL joystick | ||||||
|  | class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { | ||||||
|  | public: | ||||||
|  |     explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} | ||||||
|  |     /** | ||||||
|  |      * Creates analog device from joystick axes | ||||||
|  |      * @param params contains parameters for creating the device: | ||||||
|  |      *     - "guid": the guid of the joystick to bind | ||||||
|  |      *     - "port": the nth joystick of the same type | ||||||
|  |      *     - "axis_x": the index of the axis to be bind as x-axis | ||||||
|  |      *     - "axis_y": the index of the axis to be bind as y-axis | ||||||
|  |      */ | ||||||
|  |     std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { | ||||||
|  |         const std::string guid = params.Get("guid", "0"); | ||||||
|  |         const int port = params.Get("port", 0); | ||||||
|  |         const int axis_x = params.Get("axis_x", 0); | ||||||
|  |         const int axis_y = params.Get("axis_y", 1); | ||||||
|  |  | ||||||
|  |         auto joystick = state.GetSDLJoystickByGUID(guid, port); | ||||||
|  |  | ||||||
|  |         // This is necessary so accessing GetAxis with axis_x and axis_y won't crash | ||||||
|  |         joystick->SetAxis(axis_x, 0); | ||||||
|  |         joystick->SetAxis(axis_y, 0); | ||||||
|  |         return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     SDLState& state; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SDLState::SDLState() { | ||||||
|  |     using namespace Input; | ||||||
|  |     RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); | ||||||
|  |     RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); | ||||||
|  |  | ||||||
|  |     // If the frontend is going to manage the event loop, then we dont start one here | ||||||
|  |     start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); | ||||||
|  |     if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { | ||||||
|  |         LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_AddEventWatch(&SDLEventWatcher, this); | ||||||
|  |  | ||||||
|  |     initialized = true; | ||||||
|  |     if (start_thread) { | ||||||
|  |         poll_thread = std::thread([&] { | ||||||
|  |             using namespace std::chrono_literals; | ||||||
|  |             SDL_Event event; | ||||||
|  |             while (initialized) { | ||||||
|  |                 SDL_PumpEvents(); | ||||||
|  |                 std::this_thread::sleep_for(std::chrono::duration(10ms)); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     // Because the events for joystick connection happens before we have our event watcher added, we | ||||||
|  |     // can just open all the joysticks right here | ||||||
|  |     for (int i = 0; i < SDL_NumJoysticks(); ++i) { | ||||||
|  |         InitJoystick(i); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDLState::~SDLState() { | ||||||
|  |     using namespace Input; | ||||||
|  |     UnregisterFactory<ButtonDevice>("sdl"); | ||||||
|  |     UnregisterFactory<AnalogDevice>("sdl"); | ||||||
|  |  | ||||||
|  |     CloseJoysticks(); | ||||||
|  |     SDL_DelEventWatch(&SDLEventWatcher, this); | ||||||
|  |  | ||||||
|  |     initialized = false; | ||||||
|  |     if (start_thread) { | ||||||
|  |         poll_thread.join(); | ||||||
|  |         SDL_QuitSubSystem(SDL_INIT_JOYSTICK); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { | ||||||
|  |     Common::ParamPackage params({{"engine", "sdl"}}); | ||||||
|  |  | ||||||
|  |     switch (event.type) { | ||||||
|  |     case SDL_JOYAXISMOTION: { | ||||||
|  |         auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); | ||||||
|  |         params.Set("port", joystick->GetPort()); | ||||||
|  |         params.Set("guid", joystick->GetGUID()); | ||||||
|  |         params.Set("axis", event.jaxis.axis); | ||||||
|  |         if (event.jaxis.value > 0) { | ||||||
|  |             params.Set("direction", "+"); | ||||||
|  |             params.Set("threshold", "0.5"); | ||||||
|  |         } else { | ||||||
|  |             params.Set("direction", "-"); | ||||||
|  |             params.Set("threshold", "-0.5"); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYBUTTONUP: { | ||||||
|  |         auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); | ||||||
|  |         params.Set("port", joystick->GetPort()); | ||||||
|  |         params.Set("guid", joystick->GetGUID()); | ||||||
|  |         params.Set("button", event.jbutton.button); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     case SDL_JOYHATMOTION: { | ||||||
|  |         auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); | ||||||
|  |         params.Set("port", joystick->GetPort()); | ||||||
|  |         params.Set("guid", joystick->GetGUID()); | ||||||
|  |         params.Set("hat", event.jhat.hat); | ||||||
|  |         switch (event.jhat.value) { | ||||||
|  |         case SDL_HAT_UP: | ||||||
|  |             params.Set("direction", "up"); | ||||||
|  |             break; | ||||||
|  |         case SDL_HAT_DOWN: | ||||||
|  |             params.Set("direction", "down"); | ||||||
|  |             break; | ||||||
|  |         case SDL_HAT_LEFT: | ||||||
|  |             params.Set("direction", "left"); | ||||||
|  |             break; | ||||||
|  |         case SDL_HAT_RIGHT: | ||||||
|  |             params.Set("direction", "right"); | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             return {}; | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     } | ||||||
|  |     return params; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Polling { | ||||||
|  |  | ||||||
|  | class SDLPoller : public InputCommon::Polling::DevicePoller { | ||||||
|  | public: | ||||||
|  |     explicit SDLPoller(SDLState& state_) : state(state_) {} | ||||||
|  |  | ||||||
|  |     void Start() override { | ||||||
|  |         state.event_queue.Clear(); | ||||||
|  |         state.polling = true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void Stop() override { | ||||||
|  |         state.polling = false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  |     SDLState& state; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SDLButtonPoller final : public SDLPoller { | ||||||
|  | public: | ||||||
|  |     explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} | ||||||
|  |  | ||||||
|  |     Common::ParamPackage GetNextInput() override { | ||||||
|  |         SDL_Event event; | ||||||
|  |         while (state.event_queue.Pop(event)) { | ||||||
|  |             switch (event.type) { | ||||||
|  |             case SDL_JOYAXISMOTION: | ||||||
|  |                 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             case SDL_JOYBUTTONUP: | ||||||
|  |             case SDL_JOYHATMOTION: | ||||||
|  |                 return SDLEventToButtonParamPackage(state, event); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SDLAnalogPoller final : public SDLPoller { | ||||||
|  | public: | ||||||
|  |     explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} | ||||||
|  |  | ||||||
|  |     void Start() override { | ||||||
|  |         SDLPoller::Start(); | ||||||
|  |  | ||||||
|  |         // Reset stored axes | ||||||
|  |         analog_xaxis = -1; | ||||||
|  |         analog_yaxis = -1; | ||||||
|  |         analog_axes_joystick = -1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Common::ParamPackage GetNextInput() override { | ||||||
|  |         SDL_Event event; | ||||||
|  |         while (state.event_queue.Pop(event)) { | ||||||
|  |             if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             // An analog device needs two axes, so we need to store the axis for later and wait for | ||||||
|  |             // a second SDL event. The axes also must be from the same joystick. | ||||||
|  |             int axis = event.jaxis.axis; | ||||||
|  |             if (analog_xaxis == -1) { | ||||||
|  |                 analog_xaxis = axis; | ||||||
|  |                 analog_axes_joystick = event.jaxis.which; | ||||||
|  |             } else if (analog_yaxis == -1 && analog_xaxis != axis && | ||||||
|  |                        analog_axes_joystick == event.jaxis.which) { | ||||||
|  |                 analog_yaxis = axis; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Common::ParamPackage params; | ||||||
|  |         if (analog_xaxis != -1 && analog_yaxis != -1) { | ||||||
|  |             auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); | ||||||
|  |             params.Set("engine", "sdl"); | ||||||
|  |             params.Set("port", joystick->GetPort()); | ||||||
|  |             params.Set("guid", joystick->GetGUID()); | ||||||
|  |             params.Set("axis_x", analog_xaxis); | ||||||
|  |             params.Set("axis_y", analog_yaxis); | ||||||
|  |             analog_xaxis = -1; | ||||||
|  |             analog_yaxis = -1; | ||||||
|  |             analog_axes_joystick = -1; | ||||||
|  |             return params; | ||||||
|  |         } | ||||||
|  |         return params; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     int analog_xaxis = -1; | ||||||
|  |     int analog_yaxis = -1; | ||||||
|  |     SDL_JoystickID analog_axes_joystick = -1; | ||||||
|  | }; | ||||||
|  | } // namespace Polling | ||||||
|  |  | ||||||
|  | void SDLState::GetPollers( | ||||||
|  |     InputCommon::Polling::DeviceType type, | ||||||
|  |     std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers) { | ||||||
|  |     switch (type) { | ||||||
|  |     case InputCommon::Polling::DeviceType::Analog: | ||||||
|  |         pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); | ||||||
|  |         break; | ||||||
|  |     case InputCommon::Polling::DeviceType::Button: | ||||||
|  |         pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace SDL | ||||||
|  | } // namespace InputCommon | ||||||
							
								
								
									
										65
									
								
								src/input_common/sdl/sdl_impl.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/input_common/sdl/sdl_impl.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <memory> | ||||||
|  | #include <thread> | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
|  | #include "input_common/sdl/sdl.h" | ||||||
|  |  | ||||||
|  | union SDL_Event; | ||||||
|  | using SDL_Joystick = struct _SDL_Joystick; | ||||||
|  | using SDL_JoystickID = s32; | ||||||
|  |  | ||||||
|  | namespace InputCommon::SDL { | ||||||
|  |  | ||||||
|  | class SDLJoystick; | ||||||
|  | class SDLButtonFactory; | ||||||
|  | class SDLAnalogFactory; | ||||||
|  |  | ||||||
|  | class SDLState : public State { | ||||||
|  | public: | ||||||
|  |     /// Initializes and registers SDL device factories | ||||||
|  |     SDLState(); | ||||||
|  |  | ||||||
|  |     /// Unresisters SDL device factories and shut them down. | ||||||
|  |     ~SDLState() override; | ||||||
|  |  | ||||||
|  |     /// Handle SDL_Events for joysticks from SDL_PollEvent | ||||||
|  |     void HandleGameControllerEvent(const SDL_Event& event); | ||||||
|  |  | ||||||
|  |     std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); | ||||||
|  |     std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); | ||||||
|  |  | ||||||
|  |     /// Get all DevicePoller that use the SDL backend for a specific device type | ||||||
|  |     void GetPollers( | ||||||
|  |         InputCommon::Polling::DeviceType type, | ||||||
|  |         std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>>& pollers) override; | ||||||
|  |  | ||||||
|  |     /// Used by the Pollers during config | ||||||
|  |     std::atomic<bool> polling = false; | ||||||
|  |     Common::SPSCQueue<SDL_Event> event_queue; | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     void InitJoystick(int joystick_index); | ||||||
|  |     void CloseJoystick(SDL_Joystick* sdl_joystick); | ||||||
|  |  | ||||||
|  |     /// Needs to be called before SDL_QuitSubSystem. | ||||||
|  |     void CloseJoysticks(); | ||||||
|  |  | ||||||
|  |     /// Map of GUID of a list of corresponding virtual Joysticks | ||||||
|  |     std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; | ||||||
|  |     std::mutex joystick_map_mutex; | ||||||
|  |  | ||||||
|  |     std::shared_ptr<SDLButtonFactory> button_factory; | ||||||
|  |     std::shared_ptr<SDLAnalogFactory> analog_factory; | ||||||
|  |  | ||||||
|  |     bool start_thread = false; | ||||||
|  |     std::atomic<bool> initialized = false; | ||||||
|  |  | ||||||
|  |     std::thread poll_thread; | ||||||
|  | }; | ||||||
|  | } // namespace InputCommon::SDL | ||||||
		Reference in New Issue
	
	Block a user