Multiple controllers v0

Contains:
- replacing a whole lot of things that were just a global with a 4 long array global
- implementing sceUserServiceGetEvent to handle users connecting/disconnecting (only half done, currently you need to specify the number of players from config
- Even more shit added to remapping, this time a new field denoting the controller ID for the input or output (for inputs it only concerns gamepads, as those are the only ones that you can connect more than one of)
- reverts ngoquang's engine, as it's currently unused, and I didn't want to deal with yet another system that had to be updated
This commit is contained in:
kalaposfos13 2025-02-27 14:02:03 +01:00
parent 4bea00135d
commit 180a34808a
13 changed files with 449 additions and 491 deletions

View file

@ -75,6 +75,8 @@ static double trophyNotificationDuration = 6.0;
static bool useUnifiedInputConfig = true;
static bool overrideControllerColor = false;
static int controllerCustomColorRGB[3] = {0, 0, 255};
static int numberOfPlayers = 1;
static bool separateupdatefolder = false;
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
static std::string trophyKey;
@ -110,6 +112,14 @@ static bool showLabelsUnderIcons = true;
// Language
u32 m_language = 1; // english
void SetNumberOfPlayers(int num) {
numberOfPlayers = num;
}
int GetNumberOfPlayers() {
return numberOfPlayers;
}
bool allowHDR() {
return isHDRAllowed;
}
@ -788,6 +798,7 @@ void load(const std::filesystem::path& path) {
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1);
isMotionControlsEnabled = toml::find_or<bool>(input, "isMotionControlsEnabled", true);
useUnifiedInputConfig = toml::find_or<bool>(input, "useUnifiedInputConfig", true);
numberOfPlayers = toml::find_or<int>(input, "numberOfPlayers", 1);
}
if (data.contains("GPU")) {
@ -976,6 +987,7 @@ void save(const std::filesystem::path& path) {
data["Input"]["specialPadClass"] = specialPadClass;
data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled;
data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig;
data["Input"]["numberOfPlayers"] = numberOfPlayers;
data["GPU"]["screenWidth"] = screenWidth;
data["GPU"]["screenHeight"] = screenHeight;
data["GPU"]["nullGpu"] = isNullGpu;

View file

@ -20,6 +20,8 @@ void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path);
void saveMainWindow(const std::filesystem::path& path);
void SetNumberOfPlayers(int num);
int GetNumberOfPlayers();
std::string getTrophyKey();
void setTrophyKey(std::string key);
bool GetLoadGameSizeEnabled();

View file

@ -11,8 +11,6 @@
namespace Libraries::Pad {
using Input::GameController;
int PS4_SYSV_ABI scePadClose(s32 handle) {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
return ORBIS_OK;
@ -98,10 +96,10 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->stickInfo.deadZoneLeft = 1;
pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connectedCount = 2;
pInfo->connected = false;
pInfo->deviceClass = OrbisPadDeviceClass::Standard;
return ORBIS_OK;
return 0;
}
pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920;
@ -109,14 +107,14 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->stickInfo.deadZoneLeft = 1;
pInfo->stickInfo.deadZoneRight = 1;
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
pInfo->connectedCount = 1;
pInfo->connectedCount = 2;
pInfo->connected = true;
pInfo->deviceClass = OrbisPadDeviceClass::Standard;
if (Config::getUseSpecialPad()) {
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
}
return ORBIS_OK;
return 0;
}
int PS4_SYSV_ABI scePadGetDataInternal() {
@ -262,7 +260,9 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
}
LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
scePadResetLightBar(1);
return 1; // dummy
return userId;
// todo: using userId as handle works and simplifies some logic,
// but it's not supposed to be used this way
}
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
@ -275,7 +275,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
return 1; // dummy
return userId; // dummy
}
int PS4_SYSV_ABI scePadOpenExt2() {
@ -290,12 +290,12 @@ int PS4_SYSV_ABI scePadOutputReport() {
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
LOG_DEBUG(Lib_Pad, "handle: {}", handle);
int connected_count = 0;
bool connected = false;
Input::State states[64];
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int ret_num = controllers[handle - 1]->ReadStates(states, num, &connected, &connected_count);
if (!connected) {
ret_num = 1;
@ -315,15 +315,9 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
if (engine) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
GameController::CalculateOrientation(pData[i].acceleration,
pData[i].angularVelocity,
1.0f / gyro_poll_rate, pData[i].orientation);
}
}
Input::GameController::CalculateOrientation(pData[i].acceleration, pData[i].angularVelocity,
1.0f / controllers[handle - 1]->gyro_poll_rate,
pData[i].orientation);
pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
pData[i].touchData.touch[0].x = states[i].touchpad[0].x;
@ -363,15 +357,15 @@ int PS4_SYSV_ABI scePadReadHistory() {
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "called");
LOG_DEBUG(Lib_Pad, "handle: {}", handle);
if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int connectedCount = 0;
bool isConnected = false;
Input::State state;
controller->ReadState(&state, &isConnected, &connectedCount);
controllers[ handle - 1]->ReadState(&state, &isConnected, &connectedCount);
pData->buttons = state.buttonsState;
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)];
@ -385,14 +379,9 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->angularVelocity.x = state.angularVelocity.x;
pData->angularVelocity.y = state.angularVelocity.y;
pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
if (engine) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
1.0f / gyro_poll_rate, pData->orientation);
}
}
Input::GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
1.0f / controllers[handle - 1]->gyro_poll_rate,
pData->orientation);
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
pData->touchData.touch[0].x = state.touchpad[0].x;
@ -419,9 +408,9 @@ int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
if (handle != 1) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int* rgb = Config::GetControllerCustomColor();
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
controllers[0]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
return ORBIS_OK;
}
@ -493,7 +482,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
}
auto* controller = Common::Singleton<GameController>::Instance();
auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
return ORBIS_OK;
}
@ -561,7 +550,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP
if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
pParam->smallMotor, pParam->largeMotor);
auto* controller = Common::Singleton<GameController>::Instance();
auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
return ORBIS_OK;
}

View file

@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <queue>
#include "common/config.h"
#include "common/logging/log.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/system/userservice_error.h"
#include "core/tls.h"
namespace Libraries::UserService {
@ -105,19 +108,41 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
return ORBIS_OK;
}
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
user_service_event_queue.push(e);
}
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
LOG_TRACE(Lib_UserService, "(DUMMY) called");
// fake a loggin event
static bool logged_in = false;
if (!logged_in) {
logged_in = true;
event->event = OrbisUserServiceEventType::Login;
event->userId = 1;
if (!user_service_event_queue.empty()) {
OrbisUserServiceEvent& temp = user_service_event_queue.front();
event->event = temp.event;
event->userId = temp.userId;
user_service_event_queue.pop();
return ORBIS_OK;
}
return ORBIS_USER_SERVICE_ERROR_NO_EVENT;
// // fake a login event
// static bool logged_in_1 = false;
// static bool logged_in_2 = false;
// if (!logged_in_1) {
// logged_in_1 = true;
// event->event = OrbisUserServiceEventType::Login;
// event->userId = 1;
// return ORBIS_OK;
// }
// if (!logged_in_2) {
// logged_in_2 = true;
// event->event = OrbisUserServiceEventType::Login;
// event->userId = 2;
// return ORBIS_OK;
// }
}
int PS4_SYSV_ABI sceUserServiceGetEventCalendarType() {
@ -574,9 +599,9 @@ s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdLis
}
// TODO only first user, do the others as well
userIdList->user_id[0] = 1;
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[1] = 2;
userIdList->user_id[2] = 3;
userIdList->user_id[3] = 4;
return ORBIS_OK;
}
@ -1078,7 +1103,12 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::si
LOG_ERROR(Lib_UserService, "buffer is too short");
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
}
snprintf(user_name, size, "%s", name.c_str());
if (user_id == 1) {
snprintf(user_name, size, "%s", name.c_str());
} else {
snprintf(user_name, size, "%s - %d", name.c_str(), user_id);
// todo add list of names to config
}
return ORBIS_OK;
}

View file

@ -57,6 +57,8 @@ struct OrbisUserServiceEvent {
OrbisUserServiceUserId userId;
};
void AddUserServiceEvent(const OrbisUserServiceEvent e);
int PS4_SYSV_ABI sceUserServiceInitializeForShellCore();
int PS4_SYSV_ABI sceUserServiceTerminateForShellCore();
int PS4_SYSV_ABI sceUserServiceDestroyUser();

View file

@ -53,7 +53,7 @@ Emulator::Emulator() {
// Defer until after logging is initialized.
memory = Core::Memory::Instance();
controller = Common::Singleton<Input::GameController>::Instance();
controllers = Common::Singleton<Input::GameControllers>::Instance();
linker = Common::Singleton<Core::Linker>::Instance();
// Load renderdoc module.
@ -215,7 +215,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
}
}
window = std::make_unique<Frontend::WindowSDL>(
Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title);
Config::getScreenWidth(), Config::getScreenHeight(), controllers, window_title);
g_window = window.get();

View file

@ -32,7 +32,7 @@ private:
void LoadSystemModules(const std::string& game_serial);
Core::MemoryManager* memory;
Input::GameController* controller;
Input::GameControllers* controllers;
Core::Linker* linker;
std::unique_ptr<Frontend::WindowSDL> window;
std::chrono::steady_clock::time_point start_time;

View file

@ -10,55 +10,6 @@
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
}
void State::OnAxis(Axis axis, int value) {
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
axes[static_cast<int>(axis)] = value;
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
touchpad[touchIndex].state = isDown;
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
touchpad[touchIndex].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
GameController::GameController() {
m_states_num = 0;
m_last_state = State();
@ -124,22 +75,45 @@ void GameController::AddState(const State& state) {
m_states_num++;
}
void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button,
bool is_pressed) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnButton(button, is_pressed);
if (is_pressed) {
state.buttonsState |= button;
} else {
state.buttonsState &= ~button;
}
AddState(state);
}
void GameController::Axis(int id, Input::Axis axis, int value) {
using Libraries::Pad::OrbisPadButtonDataOffset;
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnAxis(axis, value);
int axis_id = static_cast<int>(axis);
state.axes[axis_id] = value;
if (axis == Input::Axis::TriggerLeft) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::L2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::L2;
}
}
if (axis == Input::Axis::TriggerRight) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::R2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::R2;
}
}
AddState(state);
}
@ -150,7 +124,9 @@ void GameController::Gyro(int id, const float gyro[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the angular velocity (gyro data)
state.OnGyro(gyro);
state.angularVelocity.x = gyro[0]; // X-axis
state.angularVelocity.y = gyro[1]; // Y-axis
state.angularVelocity.z = gyro[2]; // Z-axis
AddState(state);
}
@ -160,7 +136,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the acceleration values
state.OnAccel(acceleration);
state.acceleration.x = acceleration[0]; // X-axis
state.acceleration.y = acceleration[1]; // Y-axis
state.acceleration.z = acceleration[2]; // Z-axis
AddState(state);
}
@ -233,48 +211,72 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
}
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (!m_engine) {
return;
if (m_sdl_gamepad != nullptr) {
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
}
std::scoped_lock _{m_mutex};
m_engine->SetLightBarRGB(r, g, b);
}
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (!m_engine) {
return;
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_sdl_gamepad != nullptr) {
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
(largeMotor / 255.0f) * 0xFFFF, -1);
}
std::scoped_lock _{m_mutex};
m_engine->SetVibration(smallMotor, largeMotor);
return true;
}
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
std::scoped_lock lock{m_mutex};
auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnTouchpad(touchIndex, touchDown, x, y);
state.touchpad[touchIndex].state = touchDown;
state.touchpad[touchIndex].x = static_cast<u16>(x * 1920);
state.touchpad[touchIndex].y = static_cast<u16>(y * 941);
AddState(state);
}
}
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
std::scoped_lock _{m_mutex};
m_engine = std::move(engine);
if (m_engine) {
m_engine->Init();
void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
int controller_count;
SDL_JoystickID* joysticks = SDL_GetGamepads(&controller_count);
for (int i = 0; i < 4; i++) {
if (i < controller_count) {
controllers[i]->m_sdl_gamepad = SDL_OpenGamepad(joysticks[i]);
} else {
controllers[i]->m_sdl_gamepad = nullptr;
}
}
}
Engine* GameController::GetEngine() {
return m_engine.get();
// if (m_sdl_gamepad == nullptr || !SDL_GamepadConnected(m_sdl_gamepad)) {
// int gamepad_count;
// SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
// m_sdl_gamepad = gamepad_count > 0 ? SDL_OpenGamepad(gamepads[0]) : nullptr;
// if (Config::getIsMotionControlsEnabled()) {
// if (SDL_SetGamepadSensorEnabled(m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
// gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_sdl_gamepad, SDL_SENSOR_GYRO);
// LOG_INFO(Input, "Gyro initialized, poll rate: {}", gyro_poll_rate);
// } else {
// LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad");
// }
// if (SDL_SetGamepadSensorEnabled(m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
// accel_poll_rate = SDL_GetGamepadSensorDataRate(m_sdl_gamepad, SDL_SENSOR_ACCEL);
// LOG_INFO(Input, "Accel initialized, poll rate: {}", accel_poll_rate);
// } else {
// LOG_ERROR(Input, "Failed to initialize accel controls for gamepad");
// }
// }
// SDL_free(gamepads);
// SetLightBarRGB(0, 0, 255);
// }
}
u32 GameController::Poll() {
std::scoped_lock lock{m_mutex};
if (m_connected) {
std::scoped_lock lock{m_mutex};
auto time = Libraries::Kernel::sceKernelGetProcessTime();
if (m_states_num == 0) {
auto diff = (time - m_last_state.time) / 1000;

View file

@ -3,12 +3,13 @@
#pragma once
#include <algorithm>
#include <memory>
#include <mutex>
#include "common/assert.h"
#include "common/types.h"
#include "core/libraries/pad/pad.h"
struct SDL_Gamepad;
namespace Input {
enum class Axis {
@ -28,14 +29,7 @@ struct TouchpadEntry {
u16 y{};
};
class State {
public:
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
void OnAxis(Axis, int);
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
void OnGyro(const float[3]);
void OnAccel(const float[3]);
struct State {
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
u64 time = 0;
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
@ -45,24 +39,15 @@ public:
Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f};
};
class Engine {
public:
virtual ~Engine() = default;
virtual void Init() = 0;
virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0;
virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0;
virtual State ReadState() = 0;
virtual float GetAccelPollRate() const = 0;
virtual float GetGyroPollRate() const = 0;
};
inline int GetAxis(int min, int max, int value) {
return std::clamp((255 * (value - min)) / (max - min), 0, 255);
int v = (255 * (value - min)) / (max - min);
return (v < 0 ? 0 : (v > 255 ? 255 : v));
}
constexpr u32 MAX_STATES = 32;
class GameController {
friend class GameControllers;
public:
GameController();
virtual ~GameController() = default;
@ -76,12 +61,12 @@ public:
void Gyro(int id, const float gyro[3]);
void Acceleration(int id, const float acceleration[3]);
void SetLightBarRGB(u8 r, u8 g, u8 b);
void SetVibration(u8 smallMotor, u8 largeMotor);
bool SetVibration(u8 smallMotor, u8 largeMotor);
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
void SetEngine(std::unique_ptr<Engine>);
Engine* GetEngine();
u32 Poll();
float gyro_poll_rate;
float accel_poll_rate;
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
@ -101,7 +86,21 @@ private:
std::array<State, MAX_STATES> m_states;
std::array<StateInternal, MAX_STATES> m_private;
std::unique_ptr<Engine> m_engine = nullptr;
SDL_Gamepad* m_sdl_gamepad = nullptr;
};
class GameControllers {
std::array<GameController*, 4> controllers;
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 out of bounds for GameControllers!");
}
return controllers[i];
}
static void TryOpenSDLControllers(GameControllers& controllers);
};
} // namespace Input

View file

@ -22,6 +22,11 @@
#include "common/elf_info.h"
#include "common/io_file.h"
#include "common/path_util.h"
<<<<<<< HEAD
=======
#include "common/singleton.h"
#include "common/version.h"
>>>>>>> ac515a2bb (Multiple controllers v0)
#include "input/controller.h"
#include "input/input_mouse.h"
@ -61,42 +66,11 @@ std::list<std::pair<InputEvent, bool>> pressed_keys;
std::list<InputID> toggled_keys;
static std::vector<BindingConnection> connections;
auto output_array = std::array{
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE),
// Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
std::array<ControllerAllOutputs, 4> output_arrays = {
ControllerAllOutputs(0),
ControllerAllOutputs(1),
ControllerAllOutputs(2),
ControllerAllOutputs(3),
};
void ControllerOutput::LinkJoystickAxes() {
@ -197,6 +171,14 @@ InputBinding GetBindingFromString(std::string& line) {
return InputBinding(keys[0], keys[1], keys[2]);
}
std::optional<int> parseInt(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
void ParseInputConfig(const std::string game_id = "") {
std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofKbmConfigFile(config_type);
@ -254,16 +236,39 @@ void ParseInputConfig(const std::string game_id = "") {
std::string output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 1);
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
// todo: here the inputs and outputs are formatted and split, we need to extract the
// controller ID now
// input gamepad id is only for controllers, it's discarded otherwise
std::size_t input_colon_pos = input_string.find(':');
if (input_colon_pos != std::string::npos) {
auto temp = parseInt(input_string.substr(input_colon_pos + 1));
if (!temp) {
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
} else {
input_gamepad_id = *temp;
}
input_string = input_string.substr(0, input_colon_pos);
}
// if not provided, assume it's for all gamepads, if the input is a controller and that also
// doesn't have an ID, and for the first otherwise
std::size_t output_colon_pos = output_string.find(':');
if (output_colon_pos != std::string::npos) {
auto temp = parseInt(output_string.substr(output_colon_pos + 1));
if (!temp) {
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
} else {
output_gamepad_id = *temp;
}
output_string = output_string.substr(0, output_colon_pos);
}
std::size_t comma_pos = input_string.find(',');
auto parseInt = [](const std::string& s) -> std::optional<int> {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
// todo: make remapping work for special bindings for gamepads that are not the first
if (output_string == "mouse_to_joystick") {
if (input_string == "left") {
SetMouseToJoystick(1);
@ -286,7 +291,7 @@ void ParseInputConfig(const std::string game_id = "") {
continue;
}
ControllerOutput* toggle_out =
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
&*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE));
BindingConnection toggle_connection = BindingConnection(
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
connections.insert(connections.end(), toggle_connection);
@ -380,34 +385,60 @@ void ParseInputConfig(const std::string game_id = "") {
continue;
}
if (button_it != string_to_cbutton_map.end()) {
// todo add new shit here
connection = BindingConnection(
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
connections.insert(connections.end(), connection);
binding,
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(button_it->second)));
} else if (axis_it != string_to_axis_map.end()) {
// todo add new shit here
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
connection = BindingConnection(
binding,
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
axis_it->second.axis,
axis_it->second.value >= 0)),
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
axis_it->second.axis,
axis_it->second.value >= 0)),
value_to_set);
connections.insert(connections.end(), connection);
} else {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line);
continue;
}
// todo make the following: if the input binding contains a controller input, and gamepad ID
// isn't specified for either inputs or output (both are -1), then multiply the binding and
// add it to all 4 controllers
if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) {
for (int i = 0; i < 4; i++) {
BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1);
copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output);
connections.push_back(copy);
}
} else {
connections.push_back(connection);
}
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
}
file.close();
std::sort(connections.begin(), connections.end());
for (auto& c : connections) {
// todo add new shit here
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
}
LOG_DEBUG(Input, "Done parsing the input config!");
}
BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) {
BindingConnection copy = *this;
for (auto& key : copy.binding.keys) {
if (key.type == InputType::Controller || key.type == InputType::Axis) {
key.gamepad_id = gamepad;
}
}
return copy;
}
u32 GetMouseWheelEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
@ -426,6 +457,8 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
}
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
// todo add new shit here
u8 gamepad = 1;
switch (e.type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
@ -439,18 +472,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0);
gamepad = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(e.gbutton.which));
return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down,
0);
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
return InputEvent(InputType::Axis, (u32)e.gaxis.axis, true, e.gaxis.value / 256);
gamepad = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(e.gaxis.which));
return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256);
default:
return InputEvent();
}
}
GameController* ControllerOutput::controller = nullptr;
void ControllerOutput::SetControllerOutputController(GameController* c) {
ControllerOutput::controller = c;
}
GameControllers ControllerOutput::controllers =
*Common::Singleton<Input::GameControllers>::Instance();
void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) {
@ -492,7 +526,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
*new_param = (event.active ? event.axis_value : 0) + *new_param;
}
}
void ControllerOutput::FinalizeUpdate() {
void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
state_changed = old_button_state != new_button_state || old_param != *new_param;
if (!state_changed) {
return;
@ -506,8 +540,8 @@ void ControllerOutput::FinalizeUpdate() {
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
: Config::getBackButtonBehavior() == "right" ? 0.75f
: 0.5f;
controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controllers[0]->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f);
controllers[0]->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
case LEFTJOYSTICK_HALFMODE:
leftjoystick_halfmode = new_button_state;
@ -519,8 +553,13 @@ void ControllerOutput::FinalizeUpdate() {
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed
case SDL_GAMEPAD_BUTTON_START:
controllers[gamepad_index]->CheckButton(0, SDLGamepadToOrbisButton(button),
new_button_state);
break;
default: // is a normal key (hopefully)
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
controllers[gamepad_index]->CheckButton(0, SDLGamepadToOrbisButton(button),
new_button_state);
break;
}
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
@ -550,18 +589,20 @@ void ControllerOutput::FinalizeUpdate() {
break;
case Axis::TriggerLeft:
ApplyDeadzone(new_param, lefttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2,
*new_param > 0x20);
return;
case Axis::TriggerRight:
ApplyDeadzone(new_param, righttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x80, *new_param));
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2,
*new_param > 0x20);
return;
default:
break;
}
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(-0x80, 0x80, *new_param * multiplier));
}
}
@ -693,22 +734,30 @@ InputEvent BindingConnection::ProcessBinding() {
}
void ActivateOutputsFromInputs() {
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_array) {
it.ResetUpdate();
}
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
it.output->AddUpdate(it.ProcessBinding());
}
// todo find a better solution
for (int i = 0; i < 4; i++) {
// Update all outputs
for (auto& it : output_array) {
it.FinalizeUpdate();
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_arrays[i].data) {
it.ResetUpdate();
}
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
// only update this when it's the correct pass
if (it.output->gamepad_id == i) {
it.output->AddUpdate(it.ProcessBinding());
}
}
// Update all outputs
for (auto& it : output_arrays[i].data) {
it.FinalizeUpdate(i);
}
}
}

View file

@ -49,21 +49,24 @@ class InputID {
public:
InputType type;
u32 sdl_id;
InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {}
u8 gamepad_id;
InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1)
: type(d), sdl_id(i), gamepad_id(g) {}
bool operator==(const InputID& o) const {
return type == o.type && sdl_id == o.sdl_id;
return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id;
}
bool operator!=(const InputID& o) const {
return type != o.type || sdl_id != o.sdl_id;
return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id;
}
bool operator<=(const InputID& o) const {
return type <= o.type && sdl_id <= o.sdl_id;
return std::tie(gamepad_id, type, sdl_id) <= std::tie(o.gamepad_id, o.type, o.sdl_id);
}
bool IsValid() const {
return *this != InputID();
}
std::string ToString() {
return fmt::format("({}: {:x})", input_type_names[(u8)type], sdl_id);
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
}
};
@ -321,14 +324,15 @@ public:
static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e);
};
class ControllerOutput {
static GameController* controller;
static GameControllers controllers;
public:
static void SetControllerOutputController(GameController* c);
static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {}
static void LinkJoystickAxes();
u32 button;
u32 axis;
u8 gamepad_id;
// these are only used as s8,
// but I added some padding to avoid overflow if it's activated by multiple inputs
// axis_plus and axis_minus pairs share a common new_param, the other outputs have their own
@ -342,6 +346,7 @@ public:
new_param = new s16(0);
old_param = 0;
positive_axis = p;
gamepad_id = 0;
}
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {
new_param = new s16(*o.new_param);
@ -367,7 +372,7 @@ public:
void ResetUpdate();
void AddUpdate(InputEvent event);
void FinalizeUpdate();
void FinalizeUpdate(u8 gamepad_index);
};
class BindingConnection {
public:
@ -382,6 +387,13 @@ public:
output = out;
toggle = t;
}
BindingConnection& operator=(const BindingConnection& o) {
binding = o.binding;
output = o.output;
axis_param = o.axis_param;
toggle = o.toggle;
return *this;
}
bool operator<(const BindingConnection& other) const {
// a button is a higher priority than an axis, as buttons can influence axes
// (e.g. joystick_halfmode)
@ -395,9 +407,64 @@ public:
}
return false;
}
bool HasGamepadInput() {
for (auto& key : binding.keys) {
if (key.type == InputType::Controller || key.type == InputType::Axis) {
return true;
}
}
return false;
}
BindingConnection CopyWithChangedGamepadId(u8 gamepad);
InputEvent ProcessBinding();
};
class ControllerAllOutputs {
public:
std::array<ControllerOutput, 24> data = {
// Important: these have to be the first, or else they will update in the wrong order
ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE),
// Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
};
ControllerAllOutputs(u8 g) {
for (int i = 0; i < 24; i++) {
data[i].gamepad_id = g;
}
}
};
// Updates the list of pressed keys with the given input.
// Returns whether the list was updated or not.
bool UpdatePressedKeys(InputEvent event);

View file

@ -13,6 +13,7 @@
#include "core/debug_state.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "imgui/renderer/imgui_core.h"
#include "input/controller.h"
#include "input/input_handler.h"
@ -24,239 +25,55 @@
#include "SDL3/SDL_metal.h"
#endif
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
using OPBDO = OrbisPadButtonDataOffset;
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return OPBDO::Down;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return OPBDO::Up;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return OPBDO::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return OPBDO::Right;
case SDL_GAMEPAD_BUTTON_SOUTH:
return OPBDO::Cross;
case SDL_GAMEPAD_BUTTON_NORTH:
return OPBDO::Triangle;
case SDL_GAMEPAD_BUTTON_WEST:
return OPBDO::Square;
case SDL_GAMEPAD_BUTTON_EAST:
return OPBDO::Circle;
case SDL_GAMEPAD_BUTTON_START:
return OPBDO::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OPBDO::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OPBDO::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
return OPBDO::L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
return OPBDO::R3;
default:
return OPBDO::None;
}
}
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
switch (axis) {
case Axis::LeftX:
return SDL_GAMEPAD_AXIS_LEFTX;
case Axis::LeftY:
return SDL_GAMEPAD_AXIS_LEFTY;
case Axis::RightX:
return SDL_GAMEPAD_AXIS_RIGHTX;
case Axis::RightY:
return SDL_GAMEPAD_AXIS_RIGHTY;
case Axis::TriggerLeft:
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
case Axis::TriggerRight:
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
default:
UNREACHABLE();
}
}
SDLInputEngine::~SDLInputEngine() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
}
}
void SDLInputEngine::Init() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
m_gamepad = nullptr;
}
int gamepad_count;
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
if (!gamepads) {
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
return;
}
if (gamepad_count == 0) {
LOG_INFO(Input, "No gamepad found!");
SDL_free(gamepads);
return;
}
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
m_gamepad = SDL_OpenGamepad(gamepads[0]);
if (!m_gamepad) {
LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError());
SDL_free(gamepads);
return;
}
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
Uint16 vendor = SDL_GetJoystickVendor(joystick);
Uint16 product = SDL_GetJoystickProduct(joystick);
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
if (isDualSense) {
LOG_INFO(Input, "Detected DualSense Controller");
}
if (Config::getIsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
}
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
}
}
SDL_free(gamepads);
int* rgb = Config::GetControllerCustomColor();
if (isDualSense) {
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
} else {
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
}
} else {
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
}
}
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (m_gamepad) {
SDL_SetGamepadLED(m_gamepad, r, g, b);
}
}
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_gamepad) {
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
}
}
State SDLInputEngine::ReadState() {
State state{};
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Buttons
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
auto orbisButton = SDLGamepadToOrbisButton(i);
if (orbisButton == OrbisPadButtonDataOffset::None) {
continue;
}
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
}
// Axes
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
const auto axis = static_cast<Axis>(i);
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
switch (axis) {
case Axis::TriggerLeft:
case Axis::TriggerRight:
state.OnAxis(axis, GetAxis(0, 0x8000, value));
break;
default:
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
break;
}
}
// Touchpad
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
for (int finger = 0; finger < 2; ++finger) {
bool down;
float x, y;
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
state.OnTouchpad(finger, down, x, y);
}
}
}
// Gyro
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
float gyro[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
state.OnGyro(gyro);
}
}
// Accel
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
float accel[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
state.OnAccel(accel);
}
}
return state;
}
float SDLInputEngine::GetGyroPollRate() const {
return m_gyro_poll_rate;
}
float SDLInputEngine::GetAccelPollRate() const {
return m_accel_poll_rate;
}
} // namespace Input
namespace Frontend {
using namespace Libraries::Pad;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return OrbisPadButtonDataOffset::Down;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return OrbisPadButtonDataOffset::Up;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return OrbisPadButtonDataOffset::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return OrbisPadButtonDataOffset::Right;
case SDL_GAMEPAD_BUTTON_SOUTH:
return OrbisPadButtonDataOffset::Cross;
case SDL_GAMEPAD_BUTTON_NORTH:
return OrbisPadButtonDataOffset::Triangle;
case SDL_GAMEPAD_BUTTON_WEST:
return OrbisPadButtonDataOffset::Square;
case SDL_GAMEPAD_BUTTON_EAST:
return OrbisPadButtonDataOffset::Circle;
case SDL_GAMEPAD_BUTTON_START:
return OrbisPadButtonDataOffset::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OrbisPadButtonDataOffset::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OrbisPadButtonDataOffset::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OrbisPadButtonDataOffset::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OrbisPadButtonDataOffset::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
return OrbisPadButtonDataOffset::L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
return OrbisPadButtonDataOffset::R3;
default:
return OrbisPadButtonDataOffset::None;
}
}
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
return controller->Poll();
}
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
std::string_view window_title)
: width{width_}, height{height_}, controller{controller_} {
: width{width_}, height{height_}, controllers{*controllers_} {
if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError());
}
@ -300,7 +117,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
SDL_SetWindowFullscreen(window, Config::getIsFullscreen());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
Input::GameControllers::TryOpenSDLControllers(controllers);
#if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows;
@ -325,9 +142,14 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
#endif
// input handler init-s
Input::ControllerOutput::SetControllerOutputController(controller);
Input::ControllerOutput::LinkJoystickAxes();
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
// default login
using namespace Libraries::UserService;
int player_count = Config::GetNumberOfPlayers();
for (int i = 0; i < player_count; i++) {
AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1});
}
}
WindowSDL::~WindowSDL() = default;
@ -365,14 +187,15 @@ void WindowSDL::WaitEvent() {
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
// todo handle userserviceevents here
Input::GameControllers::TryOpenSDLControllers(controllers);
break;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
controller->SetTouchpadState(event.gtouchpad.finger,
event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event.gtouchpad.x,
event.gtouchpad.y);
controllers[0]->SetTouchpadState(event.gtouchpad.finger,
event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
event.gtouchpad.x, event.gtouchpad.y);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
@ -384,10 +207,10 @@ void WindowSDL::WaitEvent() {
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO:
controller->Gyro(0, event.gsensor.data);
controllers[0]->Gyro(0, event.gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controller->Acceleration(0, event.gsensor.data);
controllers[0]->Acceleration(0, event.gsensor.data);
break;
default:
break;
@ -421,8 +244,8 @@ void WindowSDL::WaitEvent() {
}
void WindowSDL::InitTimers() {
SDL_AddTimer(100, &PollController, controller);
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
SDL_AddTimer(100, &PollController, controllers[0]);
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
}
void WindowSDL::RequestKeyboard() {
@ -520,7 +343,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
// as it would break the entire touchpad handling
// You can still bind other things to it though
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
controllers[0]->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
return;
}

View file

@ -15,25 +15,8 @@ struct SDL_Gamepad;
union SDL_Event;
namespace Input {
class SDLInputEngine : public Engine {
public:
~SDLInputEngine() override;
void Init() override;
void SetLightBarRGB(u8 r, u8 g, u8 b) override;
void SetVibration(u8 smallMotor, u8 largeMotor) override;
float GetGyroPollRate() const override;
float GetAccelPollRate() const override;
State ReadState() override;
private:
SDL_Gamepad* m_gamepad = nullptr;
float m_gyro_poll_rate = 0.0f;
float m_accel_poll_rate = 0.0f;
};
} // namespace Input
class GameController;
}
namespace Frontend {
@ -65,7 +48,7 @@ class WindowSDL {
int keyboard_grab = 0;
public:
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller,
explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers,
std::string_view window_title);
~WindowSDL();
@ -103,7 +86,7 @@ private:
private:
s32 width;
s32 height;
Input::GameController* controller;
Input::GameControllers controllers{};
WindowSystemInfo window_info{};
SDL_Window* window{};
bool is_shown{};