input_common: Implement SDL motion
This commit is contained in:
		| @@ -153,6 +153,11 @@ struct InputSubsystem::Impl { | |||||||
|             // TODO return the correct motion device |             // TODO return the correct motion device | ||||||
|             return {}; |             return {}; | ||||||
|         } |         } | ||||||
|  | #ifdef HAVE_SDL2 | ||||||
|  |         if (params.Get("class", "") == "sdl") { | ||||||
|  |             return sdl->GetMotionMappingForDevice(params); | ||||||
|  |         } | ||||||
|  | #endif | ||||||
|         return {}; |         return {}; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -37,6 +37,9 @@ public: | |||||||
|     virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { |     virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { | ||||||
|         return {}; |         return {}; | ||||||
|     } |     } | ||||||
|  |     virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class NullState : public State { | class NullState : public State { | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | #include "common/math_util.h" | ||||||
| #include "common/param_package.h" | #include "common/param_package.h" | ||||||
| #include "common/settings_input.h" | #include "common/settings_input.h" | ||||||
| #include "common/threadsafe_queue.h" | #include "common/threadsafe_queue.h" | ||||||
| @@ -68,13 +69,57 @@ public: | |||||||
|     SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, |     SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, | ||||||
|                 SDL_GameController* game_controller) |                 SDL_GameController* game_controller) | ||||||
|         : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, |         : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, | ||||||
|           sdl_controller{game_controller, &SDL_GameControllerClose} {} |           sdl_controller{game_controller, &SDL_GameControllerClose} { | ||||||
|  |         EnableMotion(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void EnableMotion() { | ||||||
|  |         if (sdl_controller) { | ||||||
|  |             SDL_GameController* controller = sdl_controller.get(); | ||||||
|  |             if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { | ||||||
|  |                 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); | ||||||
|  |                 has_accel = true; | ||||||
|  |             } | ||||||
|  |             if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { | ||||||
|  |                 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); | ||||||
|  |                 has_gyro = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     void SetButton(int button, bool value) { |     void SetButton(int button, bool value) { | ||||||
|         std::lock_guard lock{mutex}; |         std::lock_guard lock{mutex}; | ||||||
|         state.buttons.insert_or_assign(button, value); |         state.buttons.insert_or_assign(button, value); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     void SetMotion(SDL_ControllerSensorEvent event) { | ||||||
|  |         constexpr float gravity_constant = 9.80665f; | ||||||
|  |         std::lock_guard lock{mutex}; | ||||||
|  |         u64 time_difference = event.timestamp - last_motion_update; | ||||||
|  |         last_motion_update = event.timestamp; | ||||||
|  |         switch (event.sensor) { | ||||||
|  |         case SDL_SENSOR_ACCEL: { | ||||||
|  |             const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]}; | ||||||
|  |             motion.SetAcceleration(acceleration / gravity_constant); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         case SDL_SENSOR_GYRO: { | ||||||
|  |             const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]}; | ||||||
|  |             motion.SetGyroscope(gyroscope / (Common::PI * 2)); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Ignore duplicated timestamps | ||||||
|  |         if (time_difference == 0) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         motion.SetGyroThreshold(0.0001f); | ||||||
|  |         motion.UpdateRotation(time_difference * 1000); | ||||||
|  |         motion.UpdateOrientation(time_difference * 1000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     bool GetButton(int button) const { |     bool GetButton(int button) const { | ||||||
|         std::lock_guard lock{mutex}; |         std::lock_guard lock{mutex}; | ||||||
|         return state.buttons.at(button); |         return state.buttons.at(button); | ||||||
| @@ -121,6 +166,14 @@ public: | |||||||
|         return std::make_tuple(x, y); |         return std::make_tuple(x, y); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     bool HasGyro() const { | ||||||
|  |         return has_gyro; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool HasAccel() const { | ||||||
|  |         return has_accel; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const MotionInput& GetMotion() const { |     const MotionInput& GetMotion() const { | ||||||
|         return motion; |         return motion; | ||||||
|     } |     } | ||||||
| @@ -173,8 +226,11 @@ private: | |||||||
|     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; |     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; | ||||||
|     mutable std::mutex mutex; |     mutable std::mutex mutex; | ||||||
|  |  | ||||||
|     // Motion is initialized without PID values as motion input is not aviable for SDL2 |     // Motion is initialized with the PID values | ||||||
|     MotionInput motion{0.0f, 0.0f, 0.0f}; |     MotionInput motion{0.3f, 0.005f, 0.0f}; | ||||||
|  |     u64 last_motion_update{}; | ||||||
|  |     bool has_gyro{false}; | ||||||
|  |     bool has_accel{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { | std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { | ||||||
| @@ -296,6 +352,12 @@ void SDLState::HandleGameControllerEvent(const SDL_Event& event) { | |||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |     case SDL_CONTROLLERSENSORUPDATE: { | ||||||
|  |         if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { | ||||||
|  |             joystick->SetMotion(event.csensor); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     case SDL_JOYDEVICEREMOVED: |     case SDL_JOYDEVICEREMOVED: | ||||||
|         LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); |         LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); | ||||||
|         CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); |         CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); | ||||||
| @@ -449,6 +511,18 @@ private: | |||||||
|     std::shared_ptr<SDLJoystick> joystick; |     std::shared_ptr<SDLJoystick> joystick; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class SDLMotion final : public Input::MotionDevice { | ||||||
|  | public: | ||||||
|  |     explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {} | ||||||
|  |  | ||||||
|  |     Input::MotionStatus GetStatus() const override { | ||||||
|  |         return joystick->GetMotion().GetMotion(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<SDLJoystick> joystick; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class SDLDirectionMotion final : public Input::MotionDevice { | class SDLDirectionMotion final : public Input::MotionDevice { | ||||||
| public: | public: | ||||||
|     explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) |     explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) | ||||||
| @@ -658,6 +732,10 @@ public: | |||||||
|  |  | ||||||
|         auto joystick = state.GetSDLJoystickByGUID(guid, port); |         auto joystick = state.GetSDLJoystickByGUID(guid, port); | ||||||
|  |  | ||||||
|  |         if (params.Has("motion")) { | ||||||
|  |             return std::make_unique<SDLMotion>(joystick); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (params.Has("hat")) { |         if (params.Has("hat")) { | ||||||
|             const int hat = params.Get("hat", 0); |             const int hat = params.Get("hat", 0); | ||||||
|             const std::string direction_name = params.Get("direction", ""); |             const std::string direction_name = params.Get("direction", ""); | ||||||
| @@ -717,6 +795,17 @@ SDLState::SDLState() { | |||||||
|     RegisterFactory<VibrationDevice>("sdl", vibration_factory); |     RegisterFactory<VibrationDevice>("sdl", vibration_factory); | ||||||
|     RegisterFactory<MotionDevice>("sdl", motion_factory); |     RegisterFactory<MotionDevice>("sdl", motion_factory); | ||||||
|  |  | ||||||
|  |     // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers | ||||||
|  |     SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); | ||||||
|  |     SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); | ||||||
|  |  | ||||||
|  |     // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a | ||||||
|  |     // GameController and not a generic one | ||||||
|  |     SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); | ||||||
|  |  | ||||||
|  |     // Turn off Pro controller home led | ||||||
|  |     SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); | ||||||
|  |  | ||||||
|     // If the frontend is going to manage the event loop, then we don't start one here |     // If the frontend is going to manage the event loop, then we don't start one here | ||||||
|     start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; |     start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; | ||||||
|     if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { |     if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { | ||||||
| @@ -853,6 +942,13 @@ Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s | |||||||
|     return params; |     return params; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Common::ParamPackage BuildMotionParam(int port, std::string guid) { | ||||||
|  |     Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}}); | ||||||
|  |     params.Set("port", port); | ||||||
|  |     params.Set("guid", std::move(guid)); | ||||||
|  |     return params; | ||||||
|  | } | ||||||
|  |  | ||||||
| Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { | Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { | ||||||
|     switch (event.type) { |     switch (event.type) { | ||||||
|     case SDL_JOYAXISMOTION: { |     case SDL_JOYAXISMOTION: { | ||||||
| @@ -907,6 +1003,35 @@ Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Eve | |||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |     case SDL_CONTROLLERSENSORUPDATE: { | ||||||
|  |         bool is_motion_shaking = false; | ||||||
|  |         constexpr float gyro_threshold = 5.0f; | ||||||
|  |         constexpr float accel_threshold = 11.0f; | ||||||
|  |         if (event.csensor.sensor == SDL_SENSOR_ACCEL) { | ||||||
|  |             const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2], | ||||||
|  |                                                 -event.csensor.data[1]}; | ||||||
|  |             if (acceleration.Length() > accel_threshold) { | ||||||
|  |                 is_motion_shaking = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (event.csensor.sensor == SDL_SENSOR_GYRO) { | ||||||
|  |             const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2], | ||||||
|  |                                              event.csensor.data[1]}; | ||||||
|  |             if (gyroscope.Length() > gyro_threshold) { | ||||||
|  |                 is_motion_shaking = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!is_motion_shaking) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) { | ||||||
|  |             return BuildMotionParam(joystick->GetPort(), joystick->GetGUID()); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     } |     } | ||||||
|     return {}; |     return {}; | ||||||
| } | } | ||||||
| @@ -1036,6 +1161,27 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa | |||||||
|     return mapping; |     return mapping; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) { | ||||||
|  |     if (!params.Has("guid") || !params.Has("port")) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |     const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); | ||||||
|  |     auto* controller = joystick->GetSDLGameController(); | ||||||
|  |     if (controller == nullptr) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     joystick->EnableMotion(); | ||||||
|  |  | ||||||
|  |     if (!joystick->HasGyro() && !joystick->HasAccel()) { | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     MotionMapping mapping = {}; | ||||||
|  |     mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, | ||||||
|  |                              BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); | ||||||
|  |     return mapping; | ||||||
|  | } | ||||||
| namespace Polling { | namespace Polling { | ||||||
| class SDLPoller : public InputCommon::Polling::DevicePoller { | class SDLPoller : public InputCommon::Polling::DevicePoller { | ||||||
| public: | public: | ||||||
| @@ -1149,6 +1295,7 @@ public: | |||||||
|             [[fallthrough]]; |             [[fallthrough]]; | ||||||
|         case SDL_JOYBUTTONUP: |         case SDL_JOYBUTTONUP: | ||||||
|         case SDL_JOYHATMOTION: |         case SDL_JOYHATMOTION: | ||||||
|  |         case SDL_CONTROLLERSENSORUPDATE: | ||||||
|             return {SDLEventToMotionParamPackage(state, event)}; |             return {SDLEventToMotionParamPackage(state, event)}; | ||||||
|         } |         } | ||||||
|         return std::nullopt; |         return std::nullopt; | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ public: | |||||||
|  |  | ||||||
|     ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; |     ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||||||
|     AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; |     AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||||||
|  |     MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     void InitJoystick(int joystick_index); |     void InitJoystick(int joystick_index); | ||||||
|   | |||||||
| @@ -153,6 +153,10 @@ QString ButtonToText(const Common::ParamPackage& param) { | |||||||
|             return QObject::tr("Button %1").arg(button_str); |             return QObject::tr("Button %1").arg(button_str); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (param.Has("motion")) { | ||||||
|  |             return QObject::tr("SDL Motion"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return {}; |         return {}; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1245,12 +1249,16 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() { | |||||||
|     const auto& device = input_devices[ui->comboDevices->currentIndex()]; |     const auto& device = input_devices[ui->comboDevices->currentIndex()]; | ||||||
|     auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); |     auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); | ||||||
|     auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); |     auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); | ||||||
|  |     auto motion_mapping = input_subsystem->GetMotionMappingForDevice(device); | ||||||
|     for (std::size_t i = 0; i < buttons_param.size(); ++i) { |     for (std::size_t i = 0; i < buttons_param.size(); ++i) { | ||||||
|         buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; |         buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; | ||||||
|     } |     } | ||||||
|     for (std::size_t i = 0; i < analogs_param.size(); ++i) { |     for (std::size_t i = 0; i < analogs_param.size(); ++i) { | ||||||
|         analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; |         analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; | ||||||
|     } |     } | ||||||
|  |     for (std::size_t i = 0; i < motions_param.size(); ++i) { | ||||||
|  |         motions_param[i] = motion_mapping[static_cast<Settings::NativeMotion::Values>(i)]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     UpdateUI(); |     UpdateUI(); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user