// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include "SDL3/SDL_joystick.h" #include "common/assert.h" #include "common/types.h" #include "core/libraries/move/move.h" #include "core/libraries/pad/pad.h" #include "core/libraries/system/userservice.h" struct SDL_Gamepad; namespace Input { enum class ControllerType { Standard, Move, }; enum class Axis { LeftX = 0, LeftY = 1, RightX = 2, RightY = 3, TriggerLeft = 4, TriggerRight = 5, AxisMax }; struct TouchpadEntry { u8 ID = 0; bool state{}; u16 x{}; u16 y{}; }; struct Colour { u8 r, g, b; }; struct State { private: template using AxisArray = std::array; static constexpr AxisArray axis_defaults{128, 128, 128, 128, 0, 0}; static constexpr u64 axis_smoothing_time{33000}; AxisArray axis_smoothing_flags{true}; AxisArray axis_smoothing_start_times{0}; AxisArray axis_smoothing_start_values{axis_defaults}; AxisArray axis_smoothing_end_values{axis_defaults}; public: void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool); void OnAxis(Axis, int, bool smooth = true); void OnTouchpad(int touchIndex, bool isDown, float x, float y); void OnGyro(const float[3]); void OnAccel(const float[3]); void UpdateAxisSmoothing(); Libraries::Pad::OrbisPadButtonDataOffset buttonsState{}; u64 time = 0; AxisArray axes{axis_defaults}; TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}}; Libraries::Pad::OrbisFVector3 acceleration = {0.0f, -9.81f, 0.0f}; Libraries::Pad::OrbisFVector3 angularVelocity = {0.0f, 0.0f, 0.0f}; Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f}; }; inline int GetAxis(int min, int max, int value) { int v = (255 * (value - min)) / (max - min); return (v < 0 ? 0 : (v > 255 ? 255 : v)); } template class RingBufferQueue { public: RingBufferQueue(size_t size) : m_storage(size) {} void Push(T item) { const size_t index = (m_begin + m_size) % m_storage.size(); m_storage[index] = std::move(item); if (m_size < m_storage.size()) { m_size += 1; } else { m_begin = (m_begin + 1) % m_storage.size(); } } std::optional Pop() { if (m_size == 0) { return {}; } const size_t index = m_begin; m_begin = (m_begin + 1) % m_storage.size(); m_size -= 1; return std::move(m_storage[index]); } private: size_t m_begin = 0; size_t m_size = 0; std::vector m_storage; }; class GameController { friend class GameControllers; public: GameController(); virtual ~GameController() = default; void ReadState(State* state, bool* isConnected, int* connectedCount); int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount); void Button(Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed); void Axis(Input::Axis axis, int value, bool smooth = true); void Gyro(int id); void Acceleration(int id); void UpdateGyro(const float gyro[3]); void UpdateAcceleration(const float acceleration[3]); void UpdateAxisSmoothing(); void SetLightBarRGB(u8 r, u8 g, u8 b); bool SetVibration(u8 smallMotor, u8 largeMotor); void SetTouchpadState(int touchIndex, bool touchDown, float x, float y); u8 GetTouchCount(); void SetTouchCount(u8 touchCount); u8 GetSecondaryTouchCount(); void SetSecondaryTouchCount(u8 touchCount); u8 GetPreviousTouchNum(); void SetPreviousTouchNum(u8 touchNum); bool WasSecondaryTouchReset(); void UnsetSecondaryTouchResetBool(); void SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation); Libraries::Pad::OrbisFQuaternion GetLastOrientation(); std::chrono::steady_clock::time_point GetLastUpdate(); void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate); float gyro_poll_rate; float accel_poll_rate; float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f}; u32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID; SDL_Gamepad* m_sdl_gamepad = nullptr; private: void PushState(); bool m_connected = true; int m_connected_count = 1; u8 m_touch_count = 0; u8 m_secondary_touch_count = 0; u8 m_previous_touchnum = 0; bool m_was_secondary_reset = false; std::chrono::steady_clock::time_point m_last_update = {}; Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f}; u8 player_index = -1; State m_state; std::mutex m_states_queue_mutex; RingBufferQueue m_states_queue; }; class MoveController { friend class GameControllers; public: MoveController(); virtual ~MoveController() = default; void ReadState(State* state, bool* isConnected, int* connectedCount); int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount); void Button(Libraries::Move::OrbisMoveButtonDataOffset button, bool isPressed); void Axis(Input::Axis axis, int value, bool smooth = true); void Gyro(int id); void Acceleration(int id); void UpdateGyro(const float gyro[3]); void UpdateAcceleration(const float acceleration[3]); void UpdateAxisSmoothing(); void SetLightBarRGB(u8 r, u8 g, u8 b); bool SetVibration(u8 smallMotor, u8 largeMotor); void SetTouchpadState(int touchIndex, bool touchDown, float x, float y); u8 GetTouchCount(); void SetTouchCount(u8 touchCount); u8 GetSecondaryTouchCount(); void SetSecondaryTouchCount(u8 touchCount); u8 GetPreviousTouchNum(); void SetPreviousTouchNum(u8 touchNum); bool WasSecondaryTouchReset(); void UnsetSecondaryTouchResetBool(); void SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation); Libraries::Pad::OrbisFQuaternion GetLastOrientation(); std::chrono::steady_clock::time_point GetLastUpdate(); void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate); static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, Libraries::Pad::OrbisFVector3& angularVelocity, float deltaTime, Libraries::Pad::OrbisFQuaternion& lastOrientation, Libraries::Pad::OrbisFQuaternion& orientation); float gyro_poll_rate; float accel_poll_rate; float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f}; u32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID; SDL_Gamepad* m_sdl_gamepad = nullptr; private: void PushState(); bool m_connected = true; int m_connected_count = 1; u8 m_touch_count = 0; u8 m_secondary_touch_count = 0; u8 m_previous_touchnum = 0; bool m_was_secondary_reset = false; std::chrono::steady_clock::time_point m_last_update = {}; Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f}; u8 player_index = -1; State m_state; std::mutex m_states_queue_mutex; RingBufferQueue m_states_queue; }; class GameControllers { std::array controllers; std::array move_controllers; static bool override_controller_color; static Colour controller_override_color; public: GameControllers() : controllers({new GameController(), new GameController(), new GameController(), new GameController()}), move_controllers({new MoveController(), new MoveController(), new MoveController(), new MoveController()}) {}; virtual ~GameControllers() = default; GameController* operator[](const size_t& i) const { if (i > 3) { UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i); } return controllers[i]; } static void TryOpenSDLControllers(GameControllers& controllers); static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id); static std::optional GetControllerIndexFromUserID(s32 user_id); static std::optional GetControllerIndexFromControllerID(s32 controller_id); static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, Libraries::Pad::OrbisFVector3& angularVelocity, float deltaTime, Libraries::Pad::OrbisFQuaternion& lastOrientation, Libraries::Pad::OrbisFQuaternion& orientation); static void SetOverrideControllerColor(bool set) { override_controller_color = set; } static void SetControllerCustomColor(u8 r, u8 g, u8 b) { controller_override_color = {r, g, b}; } static bool GetOverrideControllerColor() { return override_controller_color; } static Colour GetControllerCustomColor() { return controller_override_color; } }; } // namespace Input