From eaff98dbb3da3c7524a504abb1cdd5daa3480dda Mon Sep 17 00:00:00 2001 From: muemart Date: Wed, 6 Dec 2017 05:26:29 +0100 Subject: Adding meumart's Citra SDL Joystick support. Citra PR #3116 --- src/input_common/main.cpp | 11 +++ src/input_common/main.h | 32 ++++++++ src/input_common/sdl/sdl.cpp | 183 ++++++++++++++++++++++++++++++++++++++++++- src/input_common/sdl/sdl.h | 23 ++++++ 4 files changed, 248 insertions(+), 1 deletion(-) (limited to 'src/input_common') diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 557353740..95d40f09f 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -71,4 +71,15 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, return circle_pad_param.Serialize(); } +namespace Polling { + +std::vector> GetPollers(DeviceType type) { +#ifdef HAVE_SDL2 + return SDL::Polling::GetPollers(type); +#else + return {}; +#endif +} + +} // namespace Polling } // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h index 5604f0fa8..77a0ce90b 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -4,7 +4,13 @@ #pragma once +#include #include +#include + +namespace Common { +class ParamPackage; +} namespace InputCommon { @@ -31,4 +37,30 @@ std::string GenerateKeyboardParam(int key_code); std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, int key_modifier, float modifier_scale); +namespace Polling { + +enum class DeviceType { Button, Analog }; + +/** + * A class that can be used to get inputs from an input device like controllers without having to + * poll the device's status yourself + */ +class DevicePoller { +public: + virtual ~DevicePoller() = default; + /// Setup and start polling for inputs, should be called before GetNextInput + virtual void Start() = 0; + /// Stop polling + virtual void Stop() = 0; + /** + * Every call to this function returns the next input recorded since calling Start + * @return A ParamPackage of the recorded input, which can be used to create an InputDevice. + * If there has been no input, the package is empty + */ + virtual Common::ParamPackage GetNextInput() = 0; +}; + +// Get all DevicePoller from all backends for a specific device type +std::vector> GetPollers(DeviceType type); +} // namespace Polling } // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index d404afa89..88b557c5d 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -3,13 +3,15 @@ // Refer to the license.txt file included. #include -#include #include #include #include +#include #include #include "common/logging/log.h" #include "common/math_util.h" +#include "common/param_package.h" +#include "input_common/main.h" #include "input_common/sdl/sdl.h" namespace InputCommon { @@ -69,6 +71,10 @@ public: return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0; } + SDL_JoystickID GetJoystickID() const { + return SDL_JoystickInstanceID(joystick.get()); + } + private: std::unique_ptr joystick; }; @@ -247,5 +253,180 @@ void Shutdown() { } } +/** + * This function converts a joystick ID used in SDL events to the device index. This is necessary + * because Citra opens joysticks using their indices, not their IDs. + */ +static int JoystickIDToDeviceIndex(SDL_JoystickID id) { + int num_joysticks = SDL_NumJoysticks(); + for (int i = 0; i < num_joysticks; i++) { + auto joystick = GetJoystick(i); + if (joystick->GetJoystickID() == id) { + return i; + } + } + return -1; +} + +Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { + Common::ParamPackage params({{"engine", "sdl"}}); + switch (event.type) { + case SDL_JOYAXISMOTION: + params.Set("joystick", JoystickIDToDeviceIndex(event.jaxis.which)); + 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: + params.Set("joystick", JoystickIDToDeviceIndex(event.jbutton.which)); + params.Set("button", event.jbutton.button); + break; + case SDL_JOYHATMOTION: + params.Set("joystick", JoystickIDToDeviceIndex(event.jhat.which)); + 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: + SDLPoller() = default; + + ~SDLPoller() = default; + + void Start() override { + // SDL joysticks must be opened, otherwise they don't generate events + SDL_JoystickUpdate(); + int num_joysticks = SDL_NumJoysticks(); + for (int i = 0; i < num_joysticks; i++) { + joysticks_opened.emplace_back(GetJoystick(i)); + } + // Empty event queue to get rid of old events. citra-qt doesn't use the queue + SDL_Event dummy; + while (SDL_PollEvent(&dummy)) { + } + } + + void Stop() override { + joysticks_opened.clear(); + } + +private: + std::vector> joysticks_opened; +}; + +class SDLButtonPoller final : public SDLPoller { +public: + SDLButtonPoller() = default; + + ~SDLButtonPoller() = default; + + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (SDL_PollEvent(&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: + SDLAnalogPoller() = default; + + ~SDLAnalogPoller() = default; + + 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 (SDL_PollEvent(&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) { + params.Set("engine", "sdl"); + params.Set("joystick", JoystickIDToDeviceIndex(analog_axes_joystick)); + 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; +}; + +std::vector> GetPollers( + InputCommon::Polling::DeviceType type) { + std::vector> pollers; + switch (type) { + case InputCommon::Polling::DeviceType::Analog: + pollers.push_back(std::make_unique()); + break; + case InputCommon::Polling::DeviceType::Button: + pollers.push_back(std::make_unique()); + break; + } + return std::move(pollers); +} +} // namespace Polling } // namespace SDL } // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 3e72debcc..7934099d4 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -4,8 +4,21 @@ #pragma once +#include +#include #include "core/frontend/input.h" +union SDL_Event; +namespace Common { +class ParamPackage; +} +namespace InputCommon { +namespace Polling { +class DevicePoller; +enum class DeviceType; +} // namespace Polling +} // namespace InputCommon + namespace InputCommon { namespace SDL { @@ -15,5 +28,15 @@ void Init(); /// Unresisters SDL device factories and shut them down. void Shutdown(); +/// 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 +std::vector> GetPollers( + InputCommon::Polling::DeviceType type); + +} // namespace Polling } // namespace SDL } // namespace InputCommon -- cgit v1.2.3