shadPS4/src/input/controller.h
2026-03-02 19:15:45 +01:00

216 lines
6.6 KiB
C++

// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <utility>
#include <vector>
#include <SDL3/SDL_gamepad.h>
#include "SDL3/SDL_joystick.h"
#include "common/assert.h"
#include "common/types.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
struct SDL_Gamepad;
namespace Input {
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 <typename T>
using AxisArray = std::array<T, std::to_underlying(Axis::AxisMax)>;
static constexpr AxisArray<s32> axis_defaults{128, 128, 128, 128, 0, 0};
static constexpr u64 axis_smoothing_time{33000};
AxisArray<bool> axis_smoothing_flags{true};
AxisArray<u64> axis_smoothing_start_times{0};
AxisArray<int> axis_smoothing_start_values{axis_defaults};
AxisArray<int> 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<s32> 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 T>
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<T> 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<T> 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);
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<State> m_states_queue;
};
class GameControllers {
std::array<GameController*, 4> controllers;
static bool override_controller_color;
static Colour controller_override_color;
public:
GameControllers()
: controllers({new GameController(), new GameController(), new GameController(),
new GameController()}) {};
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 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
namespace GamepadSelect {
std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
std::string GetSelectedGamepad();
void SetSelectedGamepad(std::string GUID);
} // namespace GamepadSelect