| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- // Copyright 2017 Citra Emulator Project
- // Licensed under GPLv2 or any later version
- // Refer to the license.txt file included.
- #include <cmath>
- #include <string>
- #include <tuple>
- #include <unordered_map>
- #include <utility>
- #include <SDL.h>
- #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 {
- namespace SDL {
- class SDLJoystick;
- class SDLButtonFactory;
- class SDLAnalogFactory;
- static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list;
- static std::shared_ptr<SDLButtonFactory> button_factory;
- static std::shared_ptr<SDLAnalogFactory> analog_factory;
- static bool initialized = false;
- class SDLJoystick {
- public:
- explicit SDLJoystick(int joystick_index)
- : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
- if (!joystick) {
- NGLOG_ERROR(Input, "failed to open joystick {}", joystick_index);
- }
- }
- bool GetButton(int button) const {
- if (!joystick)
- return {};
- SDL_JoystickUpdate();
- return SDL_JoystickGetButton(joystick.get(), button) == 1;
- }
- float GetAxis(int axis) const {
- if (!joystick)
- return {};
- SDL_JoystickUpdate();
- return SDL_JoystickGetAxis(joystick.get(), 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);
- }
- bool GetHatDirection(int hat, Uint8 direction) const {
- return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
- }
- SDL_JoystickID GetJoystickID() const {
- return SDL_JoystickInstanceID(joystick.get());
- }
- private:
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
- };
- class SDLButton final : public Input::ButtonDevice {
- public:
- explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
- : joystick(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(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(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(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;
- };
- static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) {
- std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock();
- if (!joystick) {
- joystick = std::make_shared<SDLJoystick>(joystick_index);
- joystick_list[joystick_index] = joystick;
- }
- return joystick;
- }
- /// 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:
- * - "joystick": the index of the joystick 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 int joystick_index = params.Get("joystick", 0);
- 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;
- }
- return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), 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;
- NGLOG_ERROR(Input, "Unknown direction '{}'", direction_name);
- }
- return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold,
- trigger_if_greater);
- }
- const int button = params.Get("button", 0);
- return std::make_unique<SDLButton>(GetJoystick(joystick_index), 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:
- * - "joystick": the index of the joystick to bind
- * - "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 int joystick_index = params.Get("joystick", 0);
- const int axis_x = params.Get("axis_x", 0);
- const int axis_y = params.Get("axis_y", 1);
- return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y);
- }
- };
- void Init() {
- if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
- NGLOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
- } else {
- using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
- initialized = true;
- }
- }
- void Shutdown() {
- if (initialized) {
- using namespace Input;
- UnregisterFactory<ButtonDevice>("sdl");
- UnregisterFactory<AnalogDevice>("sdl");
- SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
- }
- }
- /**
- * 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<std::shared_ptr<SDLJoystick>> 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<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
- InputCommon::Polling::DeviceType type) {
- std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
- switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.push_back(std::make_unique<SDLAnalogPoller>());
- break;
- case InputCommon::Polling::DeviceType::Button:
- pollers.push_back(std::make_unique<SDLButtonPoller>());
- break;
- }
- return pollers;
- }
- } // namespace Polling
- } // namespace SDL
- } // namespace InputCommon
|