| | |
| | |
| | |
| |
|
| | #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" |
| |
|
| | |
| | |
| | typedef struct { |
| | SDL_GameControllerBindType inputType; |
| | union { |
| | int button; |
| |
|
| | struct { |
| | int axis; |
| | int axis_min; |
| | int axis_max; |
| | } axis; |
| |
|
| | struct { |
| | int hat; |
| | int hat_mask; |
| | } hat; |
| |
|
| | } input; |
| |
|
| | SDL_GameControllerBindType outputType; |
| | union { |
| | SDL_GameControllerButton button; |
| |
|
| | struct { |
| | SDL_GameControllerAxis axis; |
| | int axis_min; |
| | int axis_max; |
| | } axis; |
| |
|
| | } output; |
| |
|
| | } SDL_ExtendedGameControllerBind; |
| |
|
| | #if SDL_VERSION_ATLEAST(2, 26, 0) |
| | |
| | typedef enum { |
| | SDL_CONTROLLER_MAPPING_PRIORITY_DEFAULT, |
| | SDL_CONTROLLER_MAPPING_PRIORITY_API, |
| | SDL_CONTROLLER_MAPPING_PRIORITY_USER, |
| | } SDL_ControllerMappingPriority; |
| |
|
| | typedef struct _ControllerMapping_t { |
| | SDL_JoystickGUID guid; |
| | char* name; |
| | char* mapping; |
| | SDL_ControllerMappingPriority priority; |
| | struct _ControllerMapping_t* next; |
| | } ControllerMapping_t; |
| | #endif |
| |
|
| | struct _SDL_GameController { |
| | #if SDL_VERSION_ATLEAST(2, 26, 0) |
| | const void* magic; |
| | #endif |
| |
|
| | SDL_Joystick* joystick; |
| | int ref_count; |
| |
|
| | const char* name; |
| | #if SDL_VERSION_ATLEAST(2, 26, 0) |
| | ControllerMapping_t* mapping; |
| | #endif |
| | int num_bindings; |
| | SDL_ExtendedGameControllerBind* bindings; |
| | SDL_ExtendedGameControllerBind** last_match_axis; |
| | Uint8* last_hat_mask; |
| | Uint32 guide_button_down; |
| |
|
| | struct _SDL_GameController* next; |
| | }; |
| |
|
| | 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; |
| | } |
| |
|
| | |
| | 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); |
| | |
| | if (sdl_state->polling) { |
| | sdl_state->event_queue.Push(*event); |
| | } else { |
| | sdl_state->HandleGameControllerEvent(*event); |
| | } |
| | return 0; |
| | } |
| |
|
| | constexpr std::array<SDL_GameControllerButton, Settings::NativeButton::NumButtons> |
| | xinput_to_3ds_mapping = {{ |
| | SDL_CONTROLLER_BUTTON_B, |
| | SDL_CONTROLLER_BUTTON_A, |
| | SDL_CONTROLLER_BUTTON_Y, |
| | SDL_CONTROLLER_BUTTON_X, |
| | SDL_CONTROLLER_BUTTON_DPAD_UP, |
| | SDL_CONTROLLER_BUTTON_DPAD_DOWN, |
| | SDL_CONTROLLER_BUTTON_DPAD_LEFT, |
| | SDL_CONTROLLER_BUTTON_DPAD_RIGHT, |
| | SDL_CONTROLLER_BUTTON_LEFTSHOULDER, |
| | SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, |
| | SDL_CONTROLLER_BUTTON_START, |
| | SDL_CONTROLLER_BUTTON_BACK, |
| | SDL_CONTROLLER_BUTTON_INVALID, |
| | SDL_CONTROLLER_BUTTON_INVALID, |
| | SDL_CONTROLLER_BUTTON_INVALID, |
| | SDL_CONTROLLER_BUTTON_INVALID, |
| | SDL_CONTROLLER_BUTTON_GUIDE, |
| | SDL_CONTROLLER_BUTTON_INVALID, |
| | }}; |
| |
|
| | struct SDLJoystickDeleter { |
| | void operator()(SDL_Joystick* object) { |
| | SDL_JoystickClose(object); |
| | } |
| | }; |
| | class SDLJoystick { |
| | public: |
| | SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, |
| | SDL_GameController* game_controller) |
| | : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, |
| | sdl_controller{game_controller, &SDL_GameControllerClose} { |
| | EnableMotion(); |
| | } |
| |
|
| | void EnableMotion() { |
| | if (!sdl_controller) { |
| | return; |
| | } |
| | #if SDL_VERSION_ATLEAST(2, 0, 14) |
| | SDL_GameController* controller = sdl_controller.get(); |
| |
|
| | if (HasMotion()) { |
| | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); |
| | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); |
| | } |
| | has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; |
| | has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; |
| | if (has_accel) { |
| | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); |
| | } |
| | if (has_gyro) { |
| | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); |
| | } |
| | #endif |
| | } |
| |
|
| | bool HasMotion() const { |
| | return has_gyro || has_accel; |
| | } |
| |
|
| | void SetButton(int button, bool value) { |
| | std::lock_guard lock{mutex}; |
| | state.buttons[button] = value; |
| | } |
| |
|
| | bool GetButton(int button) const { |
| | std::lock_guard lock{mutex}; |
| | return state.buttons.at(button); |
| | } |
| |
|
| | void SetAxis(int axis, Sint16 value) { |
| | std::lock_guard lock{mutex}; |
| | state.axes[axis] = value; |
| | } |
| |
|
| | float GetAxis(int axis) const { |
| | std::lock_guard 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; |
| |
|
| | |
| | |
| | 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 lock{mutex}; |
| | state.hats[hat] = direction; |
| | } |
| |
|
| | bool GetHatDirection(int hat, Uint8 direction) const { |
| | std::lock_guard lock{mutex}; |
| | return (state.hats.at(hat) & direction) != 0; |
| | } |
| |
|
| | void SetAccel(const float x, const float y, const float z) { |
| | std::lock_guard lock{mutex}; |
| | state.accel.x = x; |
| | state.accel.y = y; |
| | state.accel.z = z; |
| | } |
| | void SetGyro(const float pitch, const float yaw, const float roll) { |
| | std::lock_guard lock{mutex}; |
| | state.gyro.x = pitch; |
| | state.gyro.y = yaw; |
| | state.gyro.z = roll; |
| | } |
| | std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetMotion() const { |
| | std::lock_guard lock{mutex}; |
| | return std::make_tuple(state.accel, state.gyro); |
| | } |
| |
|
| | |
| | |
| | |
| | const std::string& GetGUID() const { |
| | return guid; |
| | } |
| |
|
| | |
| | |
| | |
| | int GetPort() const { |
| | return port; |
| | } |
| |
|
| | SDL_Joystick* GetSDLJoystick() const { |
| | return sdl_joystick.get(); |
| | } |
| |
|
| | SDL_GameController* GetSDLGameController() const { |
| | return sdl_controller.get(); |
| | } |
| |
|
| | void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { |
| | sdl_joystick.reset(joystick); |
| | sdl_controller.reset(controller); |
| | } |
| |
|
| | private: |
| | struct State { |
| | std::unordered_map<int, bool> buttons; |
| | std::unordered_map<int, Sint16> axes; |
| | std::unordered_map<int, Uint8> hats; |
| | Common::Vec3<float> accel; |
| | Common::Vec3<float> gyro; |
| | } state; |
| | std::string guid; |
| | int port; |
| | bool has_gyro{false}; |
| | bool has_accel{false}; |
| | std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; |
| | std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; |
| | mutable std::mutex mutex; |
| | }; |
| |
|
| | struct SDLGameControllerDeleter { |
| | void operator()(SDL_GameController* object) { |
| | SDL_GameControllerClose(object); |
| | } |
| | }; |
| | class SDLGameController { |
| | public: |
| | SDLGameController(std::string guid_, int port_, SDL_GameController* controller) |
| | : guid{std::move(guid_)}, port{port_}, sdl_controller{controller} {} |
| |
|
| | |
| | |
| | |
| | const std::string& GetGUID() const { |
| | return guid; |
| | } |
| |
|
| | |
| | |
| | |
| | int GetPort() const { |
| | return port; |
| | } |
| |
|
| | SDL_GameController* GetSDLGameController() const { |
| | return sdl_controller.get(); |
| | } |
| |
|
| | void SetSDLGameController(SDL_GameController* controller) { |
| | sdl_controller = std::unique_ptr<SDL_GameController, SDLGameControllerDeleter>(controller); |
| | } |
| |
|
| | private: |
| | std::string guid; |
| | int port; |
| | std::unique_ptr<SDL_GameController, SDLGameControllerDeleter> sdl_controller; |
| | }; |
| |
|
| | |
| | |
| | |
| | std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { |
| | std::lock_guard lock{joystick_map_mutex}; |
| | const auto it = joystick_map.find(guid); |
| | if (it != joystick_map.end()) { |
| | while (it->second.size() <= static_cast<std::size_t>(port)) { |
| | auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), |
| | nullptr, nullptr); |
| | it->second.emplace_back(std::move(joystick)); |
| | } |
| | return it->second[static_cast<std::size_t>(port)]; |
| | } |
| | auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); |
| | return joystick_map[guid].emplace_back(std::move(joystick)); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { |
| | auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); |
| | const std::string guid = GetGUID(sdl_joystick); |
| |
|
| | std::lock_guard lock{joystick_map_mutex}; |
| | auto map_it = joystick_map.find(guid); |
| |
|
| | if (map_it == joystick_map.end()) { |
| | return nullptr; |
| | } |
| |
|
| | const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), |
| | [&sdl_joystick](const auto& joystick) { |
| | return joystick->GetSDLJoystick() == sdl_joystick; |
| | }); |
| |
|
| | if (vec_it == map_it->second.end()) { |
| | return nullptr; |
| | } |
| |
|
| | return *vec_it; |
| | } |
| |
|
| | Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID( |
| | const std::string& guid, int port, Settings::NativeButton::Values button) { |
| | Common::ParamPackage params({{"engine", "sdl"}}); |
| | params.Set("guid", guid); |
| | params.Set("port", port); |
| | SDL_GameController* controller = GetSDLJoystickByGUID(guid, port)->GetSDLGameController(); |
| | SDL_GameControllerButtonBind button_bind; |
| |
|
| | if (!controller) { |
| | LOG_WARNING(Input, "failed to open controller {}", guid); |
| | return {{}}; |
| | } |
| |
|
| | auto mapped_button = xinput_to_3ds_mapping[static_cast<int>(button)]; |
| | if (mapped_button == SDL_CONTROLLER_BUTTON_INVALID) { |
| | if (button == Settings::NativeButton::Values::ZL) { |
| | button_bind = |
| | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); |
| | } else if (button == Settings::NativeButton::Values::ZR) { |
| | button_bind = |
| | SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); |
| | } else { |
| | return {{}}; |
| | } |
| | } else { |
| | button_bind = SDL_GameControllerGetBindForButton(controller, mapped_button); |
| | } |
| |
|
| | switch (button_bind.bindType) { |
| | case SDL_CONTROLLER_BINDTYPE_BUTTON: |
| | params.Set("button", button_bind.value.button); |
| | break; |
| | case SDL_CONTROLLER_BINDTYPE_HAT: |
| | params.Set("hat", button_bind.value.hat.hat); |
| | switch (button_bind.value.hat.hat_mask) { |
| | 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; |
| | case SDL_CONTROLLER_BINDTYPE_AXIS: |
| | params.Set("axis", button_bind.value.axis); |
| |
|
| | #if SDL_VERSION_ATLEAST(2, 0, 6) |
| | { |
| | if (mapped_button != SDL_CONTROLLER_BUTTON_INVALID) { |
| | const SDL_ExtendedGameControllerBind extended_bind = |
| | controller->bindings[mapped_button]; |
| | if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) { |
| | params.Set("direction", "-"); |
| | } else { |
| | params.Set("direction", "+"); |
| | } |
| | params.Set("threshold", (extended_bind.input.axis.axis_min + |
| | (extended_bind.input.axis.axis_max - |
| | extended_bind.input.axis.axis_min) / |
| | 2.0f) / |
| | SDL_JOYSTICK_AXIS_MAX); |
| | } |
| | } |
| | #else |
| | params.Set("direction", "+"); |
| | #endif |
| | break; |
| | case SDL_CONTROLLER_BINDTYPE_NONE: |
| | LOG_WARNING(Input, "Button not bound: {}", Settings::NativeButton::mapping[button]); |
| | return {{}}; |
| | default: |
| | LOG_WARNING(Input, "unknown SDL bind type {}", button_bind.bindType); |
| | return {{}}; |
| | } |
| |
|
| | return params; |
| | } |
| |
|
| | Common::ParamPackage SDLState::GetSDLControllerAnalogBindByGUID( |
| | const std::string& guid, int port, Settings::NativeAnalog::Values analog) { |
| | Common::ParamPackage params({{"engine", "sdl"}}); |
| | params.Set("guid", guid); |
| | params.Set("port", port); |
| | SDL_GameController* controller = GetSDLJoystickByGUID(guid, port)->GetSDLGameController(); |
| | SDL_GameControllerButtonBind button_bind_x; |
| | SDL_GameControllerButtonBind button_bind_y; |
| |
|
| | if (!controller) { |
| | LOG_WARNING(Input, "failed to open controller {}", guid); |
| | return {{}}; |
| | } |
| |
|
| | if (analog == Settings::NativeAnalog::Values::CirclePad) { |
| | button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); |
| | button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); |
| | } else if (analog == Settings::NativeAnalog::Values::CStick) { |
| | button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); |
| | button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); |
| | } else { |
| | LOG_WARNING(Input, "analog value out of range {}", analog); |
| | return {{}}; |
| | } |
| |
|
| | if (button_bind_x.bindType != SDL_CONTROLLER_BINDTYPE_AXIS || |
| | button_bind_y.bindType != SDL_CONTROLLER_BINDTYPE_AXIS) { |
| | return {{}}; |
| | } |
| | params.Set("axis_x", button_bind_x.value.axis); |
| | params.Set("axis_y", button_bind_y.value.axis); |
| | return params; |
| | } |
| |
|
| | void SDLState::InitJoystick(int joystick_index) { |
| | SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); |
| | SDL_GameController* sdl_gamecontroller = nullptr; |
| |
|
| | if (SDL_IsGameController(joystick_index)) { |
| | sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); |
| | } |
| |
|
| | if (!sdl_joystick) { |
| | LOG_ERROR(Input, "failed to open joystick {}, with error: {}", joystick_index, |
| | SDL_GetError()); |
| | return; |
| | } |
| | const std::string guid = GetGUID(sdl_joystick); |
| |
|
| | std::lock_guard lock{joystick_map_mutex}; |
| | if (joystick_map.find(guid) == joystick_map.end()) { |
| | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); |
| | joystick->EnableMotion(); |
| | 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 auto& joystick) { return !joystick->GetSDLJoystick(); }); |
| | if (it != joystick_guid_list.end()) { |
| | (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); |
| | (*it)->EnableMotion(); |
| | return; |
| | } |
| | const int port = static_cast<int>(joystick_guid_list.size()); |
| | auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); |
| | joystick->EnableMotion(); |
| | joystick_guid_list.emplace_back(std::move(joystick)); |
| | } |
| |
|
| | void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { |
| | const auto guid = GetGUID(sdl_joystick); |
| |
|
| | std::scoped_lock lock{joystick_map_mutex}; |
| | |
| | const 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 auto& joystick) { |
| | return joystick->GetSDLJoystick() == sdl_joystick; |
| | }); |
| |
|
| | if (joystick_it != joystick_guid_list.end()) { |
| | (*joystick_it)->SetSDLJoystick(nullptr, nullptr); |
| | } |
| | } |
| |
|
| | 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; |
| | } |
| | #if SDL_VERSION_ATLEAST(2, 0, 14) |
| | case SDL_CONTROLLERSENSORUPDATE: { |
| | if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { |
| | switch (event.csensor.sensor) { |
| | case SDL_SENSOR_ACCEL: |
| | joystick->SetAccel(event.csensor.data[0] / SDL_STANDARD_GRAVITY, |
| | -event.csensor.data[1] / SDL_STANDARD_GRAVITY, |
| | event.csensor.data[2] / SDL_STANDARD_GRAVITY); |
| | break; |
| | case SDL_SENSOR_GYRO: |
| | joystick->SetGyro(-event.csensor.data[0] * (180.0f / Common::PI), |
| | event.csensor.data[1] * (180.0f / Common::PI), |
| | -event.csensor.data[2] * (180.0f / Common::PI)); |
| | break; |
| | } |
| | } |
| | break; |
| | } |
| | #endif |
| | case SDL_JOYDEVICEREMOVED: |
| | LOG_DEBUG(Input, "Joystick removed with Instance_ID {}", event.jdevice.which); |
| | CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); |
| | break; |
| | case SDL_JOYDEVICEADDED: |
| | LOG_DEBUG(Input, "Joystick connected with device index {}", event.jdevice.which); |
| | InitJoystick(event.jdevice.which); |
| | break; |
| | } |
| | } |
| |
|
| | void SDLState::CloseJoysticks() { |
| | std::lock_guard 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_, float deadzone_) |
| | : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} |
| |
|
| | std::tuple<float, float> GetStatus() const override { |
| | const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); |
| | const float r = std::sqrt((x * x) + (y * y)); |
| | if (r > deadzone) { |
| | return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), |
| | y / r * (r - deadzone) / (1 - deadzone)); |
| | } |
| | return std::make_tuple<float, float>(0.0f, 0.0f); |
| | } |
| |
|
| | private: |
| | std::shared_ptr<SDLJoystick> joystick; |
| | const int axis_x; |
| | const int axis_y; |
| | const float deadzone; |
| | }; |
| |
|
| | class SDLMotion final : public Input::MotionDevice { |
| | public: |
| | explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {} |
| |
|
| | std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { |
| | return joystick->GetMotion(); |
| | } |
| |
|
| | private: |
| | std::shared_ptr<SDLJoystick> joystick; |
| | }; |
| |
|
| | |
| | class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { |
| | public: |
| | explicit SDLButtonFactory(SDLState& state_) : state(state_) {} |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | } |
| | |
| | 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); |
| | } |
| | |
| | joystick->SetAxis(axis, 0); |
| | return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); |
| | } |
| |
|
| | const int button = params.Get("button", 0); |
| | |
| | joystick->SetButton(button, false); |
| | return std::make_unique<SDLButton>(joystick, button); |
| | } |
| |
|
| | private: |
| | SDLState& state; |
| | }; |
| |
|
| | |
| | class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { |
| | public: |
| | explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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); |
| | float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); |
| |
|
| | auto joystick = state.GetSDLJoystickByGUID(guid, port); |
| |
|
| | |
| | joystick->SetAxis(axis_x, 0); |
| | joystick->SetAxis(axis_y, 0); |
| | return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); |
| | } |
| |
|
| | private: |
| | SDLState& state; |
| | }; |
| |
|
| | class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> { |
| | public: |
| | explicit SDLMotionFactory(SDLState& state_) : state(state_) {} |
| |
|
| | std::unique_ptr<Input::MotionDevice> 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); |
| |
|
| | return std::make_unique<SDLMotion>(joystick); |
| | } |
| |
|
| | private: |
| | SDLState& state; |
| | }; |
| |
|
| | SDLState::SDLState() { |
| | using namespace Input; |
| | RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); |
| | RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); |
| | RegisterFactory<MotionDevice>("sdl", std::make_shared<SDLMotionFactory>(*this)); |
| |
|
| | |
| | start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER); |
| | if (start_thread && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { |
| | LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed with: {}", SDL_GetError()); |
| | return; |
| | } |
| | if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { |
| | LOG_ERROR(Input, "Failed to set Hint for background events: {}", SDL_GetError()); |
| | } |
| | |
| | #if SDL_VERSION_ATLEAST(2, 0, 9) |
| | #if !SDL_VERSION_ATLEAST(2, 0, 12) |
| | |
| | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0"); |
| | #endif |
| | #endif |
| |
|
| | |
| | #ifdef SDL_HINT_ACCELEROMETER_AS_JOYSTICK |
| | SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); |
| | #endif |
| |
|
| | |
| | #ifdef SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE |
| | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); |
| | #endif |
| | #ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE |
| | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); |
| | #endif |
| |
|
| | SDL_AddEventWatch(&SDLEventWatcher, this); |
| |
|
| | initialized = true; |
| | if (start_thread) { |
| | poll_thread = std::thread([this] { |
| | using namespace std::chrono_literals; |
| | while (initialized) { |
| | SDL_PumpEvents(); |
| | std::this_thread::sleep_for(10ms); |
| | } |
| | }); |
| | } |
| | |
| | |
| | for (int i = 0; i < SDL_NumJoysticks(); ++i) { |
| | InitJoystick(i); |
| | } |
| | } |
| |
|
| | SDLState::~SDLState() { |
| | using namespace Input; |
| | UnregisterFactory<ButtonDevice>("sdl"); |
| | UnregisterFactory<AnalogDevice>("sdl"); |
| | UnregisterFactory<MotionDevice>("sdl"); |
| |
|
| | CloseJoysticks(); |
| | SDL_DelEventWatch(&SDLEventWatcher, this); |
| |
|
| | initialized = false; |
| | if (start_thread) { |
| | poll_thread.join(); |
| | SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); |
| | } |
| | } |
| |
|
| | 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 (!axis_memory.count(event.jaxis.which) || |
| | !axis_memory[event.jaxis.which].count(event.jaxis.axis)) { |
| | axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value; |
| | axis_event_count[event.jaxis.which][event.jaxis.axis] = 1; |
| | break; |
| | } else { |
| | axis_event_count[event.jaxis.which][event.jaxis.axis]++; |
| | |
| | |
| | if (std::abs( |
| | (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) / |
| | 32767.0) < 0.5) { |
| | break; |
| | } else { |
| | if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 && |
| | IsAxisAtPole(event.jaxis.value) && |
| | IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | event.jaxis.value = static_cast<Sint16>(std::copysign( |
| | 32767, axis_memory[event.jaxis.which][event.jaxis.axis])); |
| | } else { |
| | |
| | |
| | event.jaxis.value = static_cast<Sint16>(std::copysign( |
| | 32767, event.jaxis.value - |
| | axis_memory[event.jaxis.which][event.jaxis.axis])); |
| | } |
| | axis_memory.clear(); |
| | axis_event_count.clear(); |
| | } |
| | } |
| | case SDL_JOYBUTTONUP: |
| | case SDL_JOYHATMOTION: |
| | return SDLEventToButtonParamPackage(state, event); |
| | } |
| | } |
| | return {}; |
| | } |
| |
|
| | private: |
| | |
| | |
| | |
| | bool IsAxisAtPole(int16_t value) { |
| | return std::abs(value) >= 32767 || std::abs(value) < 327; |
| | } |
| | std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory; |
| | std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count; |
| | }; |
| |
|
| | class SDLAnalogPoller final : public SDLPoller { |
| | public: |
| | explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} |
| |
|
| | void Start() override { |
| | SDLPoller::Start(); |
| |
|
| | |
| | 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; |
| | } |
| | |
| | |
| | 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; |
| | }; |
| | } |
| |
|
| | SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { |
| | Pollers 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; |
| | } |
| |
|
| | return pollers; |
| | } |
| |
|
| | } |
| | } |
| |
|