Local multiplayer support (#4169)

* multiple controllers v2

* the c language formatter tool

* review comments (the easy ones)

* c++ copy semantics

* correct error return for remotes without the system user id

* update pad handle handling logic

* controller override colour
This commit is contained in:
kalaposfos13 2026-03-27 12:37:25 +01:00 committed by GitHub
parent e0a86dc8f9
commit 8a1500d7ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1037 additions and 696 deletions

View File

@ -211,13 +211,6 @@ void IPC::InputLoop() {
} else if (cmd == "RELOAD_INPUTS") {
std::string config = next_str();
Input::ParseInputConfig(config);
} else if (cmd == "SET_ACTIVE_CONTROLLER") {
std::string active_controller = next_str();
GamepadSelect::SetSelectedGamepad(active_controller);
SDL_Event checkGamepad;
SDL_memset(&checkGamepad, 0, sizeof(checkGamepad));
checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER;
SDL_PushEvent(&checkGamepad);
} else {
std::cerr << ";UNKNOWN CMD: " << cmd << std::endl;
}

View File

@ -1,20 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/libraries/libs.h"
#include "core/libraries/pad/pad_errors.h"
#include "core/user_settings.h"
#include "input/controller.h"
#include "pad.h"
namespace Libraries::Pad {
using Input::GameController;
using Input::GameControllers;
using namespace Libraries::UserService;
static bool g_initialized = false;
static bool g_opened = false;
static std::unordered_map<OrbisUserServiceUserId, s32> user_id_pad_handle_map{};
static constexpr s32 tv_remote_handle = 5;
int PS4_SYSV_ABI scePadClose(s32 handle) {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
@ -30,8 +34,8 @@ int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation(
s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation));
if (Config::getUseSpecialPad()) {
pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
if (EmulatorSettings.IsUsingSpecialPad()) {
pExtInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
}
return ORBIS_OK;
}
@ -107,9 +111,9 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
return ORBIS_OK;
}
pInfo->connected = true;
if (Config::getUseSpecialPad()) {
if (EmulatorSettings.IsUsingSpecialPad()) {
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
pInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
}
return ORBIS_OK;
}
@ -156,11 +160,16 @@ int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId
if (!g_initialized) {
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
}
if (userId == -1 || !g_opened) {
if (userId == -1) {
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
}
LOG_DEBUG(Lib_Pad, "(DUMMY) called");
return 1;
auto it = user_id_pad_handle_map.find(userId);
if (it == user_id_pad_handle_map.end()) {
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
}
s32 pad_handle = it->second;
LOG_DEBUG(Lib_Pad, "called, userid: {}, out pad handle: {}", userId, pad_handle);
return pad_handle;
}
int PS4_SYSV_ABI scePadGetIdleCount() {
@ -168,8 +177,19 @@ int PS4_SYSV_ABI scePadGetIdleCount() {
return ORBIS_OK;
}
int PS4_SYSV_ABI scePadGetInfo() {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
int PS4_SYSV_ABI scePadGetInfo(u32* data) {
LOG_WARNING(Lib_Pad, "(DUMMY) called");
if (!data) {
return ORBIS_PAD_ERROR_INVALID_ARG;
}
data[0] = 0x1; // index but starting from one?
data[1] = 0x0; // index?
data[2] = 1; // pad handle
data[3] = 0x0101; // ???
data[4] = 0x0; // ?
data[5] = 0x0; // ?
data[6] = 0x00ff0000; // colour(?)
data[7] = 0x0; // ?
return ORBIS_OK;
}
@ -254,34 +274,61 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI
if (!g_initialized) {
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
}
if (userId == -1) {
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
if (userId < 0) {
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
}
if (Config::getUseSpecialPad()) {
if (userId == ORBIS_USER_SERVICE_USER_ID_SYSTEM) {
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
LOG_INFO(Lib_Pad, "Opened a TV remote device");
user_id_pad_handle_map[ORBIS_USER_SERVICE_USER_ID_SYSTEM] = tv_remote_handle;
return tv_remote_handle;
}
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
}
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
return ORBIS_PAD_ERROR_INVALID_ARG;
}
if (EmulatorSettings.IsUsingSpecialPad()) {
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
} else {
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
g_opened = true;
scePadResetLightBar(userId);
scePadResetOrientation(userId);
return 1; // dummy
auto u = UserManagement.GetUserByID(userId);
if (!u) {
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
}
s32 pad_handle = u->player_index;
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
index, pad_handle);
scePadResetLightBar(pad_handle);
scePadResetOrientation(pad_handle);
user_id_pad_handle_map[userId] = pad_handle;
return pad_handle;
}
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index, const OrbisPadOpenExtParam* pParam) {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
if (Config::getUseSpecialPad()) {
if (EmulatorSettings.IsUsingSpecialPad()) {
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
} else {
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
}
return 1; // dummy
auto u = UserManagement.GetUserByID(userId);
if (!u) {
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
}
s32 pad_handle = u->player_index;
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
index, pad_handle);
scePadResetLightBar(pad_handle);
scePadResetOrientation(pad_handle);
user_id_pad_handle_map[userId] = pad_handle;
return pad_handle;
}
int PS4_SYSV_ABI scePadOpenExt2() {
@ -294,8 +341,8 @@ int PS4_SYSV_ABI scePadOutputReport() {
return ORBIS_OK;
}
int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected,
u32 connected_count) {
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller,
Input::State* states, s32 num, bool connected, u32 connected_count) {
if (!connected) {
pData[0] = {};
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
@ -319,61 +366,57 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
deltaTime, lastOrientation, outputOrientation);
pData[i].orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
}
const auto gyro_poll_rate = controller.accel_poll_rate;
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
now - controller.GetLastUpdate())
.count() /
1000000.0f;
controller.SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameControllers::CalculateOrientation(pData->acceleration, pData->angularVelocity,
deltaTime, lastOrientation, outputOrientation);
pData[i].orientation = outputOrientation;
controller.SetLastOrientation(outputOrientation);
}
pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
if (handle == 1) {
if (controller->GetTouchCount() >= 127) {
controller->SetTouchCount(0);
if (controller.GetTouchCount() >= 127) {
controller.SetTouchCount(0);
}
if (controller->GetSecondaryTouchCount() >= 127) {
controller->SetSecondaryTouchCount(0);
if (controller.GetSecondaryTouchCount() >= 127) {
controller.SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
controller->SetTouchCount(controller->GetTouchCount() + 1);
controller->SetSecondaryTouchCount(controller->GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
controller.SetTouchCount(controller.GetTouchCount() + 1);
controller.SetSecondaryTouchCount(controller.GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
} else {
if (controller->WasSecondaryTouchReset()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
controller->UnsetSecondaryTouchResetBool();
if (controller.WasSecondaryTouchReset()) {
controller.SetTouchCount(controller.GetSecondaryTouchCount());
controller.UnsetSecondaryTouchResetBool();
}
}
}
controller->SetPreviousTouchNum(pData->touchData.touchNum);
controller.SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
states[i].touchpad[0].ID = controller.GetTouchCount();
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
}
} else {
states[i].touchpad[0].ID = 1;
@ -397,16 +440,18 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
if (handle < 1) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
int connected_count = 0;
bool connected = false;
std::vector<Input::State> states(64);
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count);
return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count);
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto& controllers = *Common::Singleton<GameControllers>::Instance();
auto& controller = *controllers[*controller_id];
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected,
connected_count);
}
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
@ -430,17 +475,18 @@ int PS4_SYSV_ABI scePadReadHistory() {
}
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "called");
if (handle < 1) {
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
auto& controllers = *Common::Singleton<GameControllers>::Instance();
auto& controller = *controllers[*controller_id];
int connected_count = 0;
bool connected = false;
Input::State state;
controller->ReadState(&state, &connected, &connected_count);
ProcessStates(handle, pData, &state, 1, connected, connected_count);
controller.ReadState(&state, &connected, &connected_count);
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
return ORBIS_OK;
}
@ -450,13 +496,30 @@ int PS4_SYSV_ABI scePadReadStateExt() {
}
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
LOG_INFO(Lib_Pad, "(DUMMY) called");
if (handle != 1) {
LOG_DEBUG(Lib_Pad, "called, handle: {}", handle);
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
int* rgb = Config::GetControllerCustomColor();
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
auto& controllers = *Common::Singleton<GameControllers>::Instance();
s32 colour_index = UserManagement.GetUserByPlayerIndex(handle)->user_color - 1;
Input::Colour colour{255, 0, 0};
if (colour_index >= 0 && colour_index <= 3) {
static constexpr Input::Colour colours[4]{
{0, 0, 255}, // blue
{255, 0, 0}, // red
{0, 255, 0}, // green
{255, 0, 255}, // pink
};
colour = colours[colour_index];
} else {
LOG_ERROR(Lib_Pad, "Invalid user colour value {} for controller {}, falling back to blue",
colour_index, handle);
}
if (auto oc = GameControllers::GetControllerCustomColor(*controller_id)) {
colour = *oc;
}
controllers[*controller_id]->SetLightBarRGB(colour.r, colour.g, colour.b);
return ORBIS_OK;
}
@ -473,14 +536,15 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
if (handle != 1) {
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto* controller = Common::Singleton<GameController>::Instance();
auto& controllers = *Common::Singleton<GameControllers>::Instance();
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
controller->SetLastOrientation(defaultOrientation);
controller->SetLastUpdate(std::chrono::steady_clock::now());
controllers[*controller_id]->SetLastOrientation(defaultOrientation);
controllers[*controller_id]->SetLastUpdate(std::chrono::steady_clock::now());
return ORBIS_OK;
}
@ -526,7 +590,11 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() {
}
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
if (Config::GetOverrideControllerColor()) {
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
if (GameControllers::GetControllerCustomColor(*controller_id)) {
return ORBIS_OK;
}
if (pParam != nullptr) {
@ -538,8 +606,8 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
}
auto* controller = Common::Singleton<GameController>::Instance();
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
auto& controllers = *Common::Singleton<GameControllers>::Instance();
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
return ORBIS_OK;
}
return ORBIS_PAD_ERROR_INVALID_ARG;
@ -555,8 +623,14 @@ int PS4_SYSV_ABI scePadSetLightBarBlinking() {
return ORBIS_OK;
}
int PS4_SYSV_ABI scePadSetLightBarForTracker() {
LOG_ERROR(Lib_Pad, "(STUBBED) called");
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam) {
LOG_INFO(Lib_Pad, "called, r: {} g: {} b: {}", pParam->r, pParam->g, pParam->b);
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
auto& controllers = *Common::Singleton<GameControllers>::Instance();
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
return ORBIS_OK;
}
@ -603,11 +677,15 @@ int PS4_SYSV_ABI scePadSetUserColor() {
}
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
pParam->smallMotor, pParam->largeMotor);
auto* controller = Common::Singleton<GameController>::Instance();
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
auto& controllers = *Common::Singleton<GameControllers>::Instance();
controllers[*controller_id]->SetVibration(pParam->smallMotor, pParam->largeMotor);
return ORBIS_OK;
}
return ORBIS_PAD_ERROR_INVALID_ARG;

View File

@ -280,7 +280,7 @@ int PS4_SYSV_ABI scePadGetFeatureReport();
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
s32 index);
int PS4_SYSV_ABI scePadGetIdleCount();
int PS4_SYSV_ABI scePadGetInfo();
int PS4_SYSV_ABI scePadGetInfo(u32* data);
int PS4_SYSV_ABI scePadGetInfoByPortType();
int PS4_SYSV_ABI scePadGetLicenseControllerInformation();
int PS4_SYSV_ABI scePadGetMotionSensorPosition();
@ -324,7 +324,7 @@ int PS4_SYSV_ABI scePadSetForceIntercepted();
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam);
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness();
int PS4_SYSV_ABI scePadSetLightBarBlinking();
int PS4_SYSV_ABI scePadSetLightBarForTracker();
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam);
int PS4_SYSV_ABI scePadSetLoginUserNumber();
int PS4_SYSV_ABI scePadSetMotionSensorState(s32 handle, bool bEnable);
int PS4_SYSV_ABI scePadSetProcessFocus();

View File

@ -1,13 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include <queue>
#include "common/logging/log.h"
#include <core/user_settings.h>
#include <queue>
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/system/userservice_error.h"
#include "core/tls.h"
#include "input/controller.h"
namespace Libraries::UserService {
@ -114,14 +120,15 @@ void AddUserServiceEvent(const OrbisUserServiceEvent e) {
}
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
LOG_TRACE(Lib_UserService, "(DUMMY) called");
// fake a loggin event
static bool logged_in = false;
LOG_TRACE(Lib_UserService, "called");
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();
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
temp.userId);
return ORBIS_OK;
}
@ -504,8 +511,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
LOG_ERROR(Lib_UserService, "user_id is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
// select first user (TODO add more)
*user_id = 1;
*user_id = UserManagement.GetDefaultUser().user_id;
return ORBIS_OK;
}
@ -575,20 +581,29 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
}
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
LOG_DEBUG(Lib_UserService, "called");
if (userIdList == nullptr) {
LOG_ERROR(Lib_UserService, "user_id is null");
LOG_ERROR(Lib_UserService, "userIdList is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
// 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;
// Initialize all slots to invalid (-1)
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
userIdList->user_id[i] = ORBIS_USER_SERVICE_USER_ID_INVALID;
}
auto& user_manager = UserManagement;
auto logged_in_users = user_manager.GetLoggedInUsers();
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
s32 id =
logged_in_users[i] ? logged_in_users[i]->user_id : ORBIS_USER_SERVICE_USER_ID_INVALID;
userIdList->user_id[i] = id;
LOG_DEBUG(Lib_UserService, "Slot {}: User ID {} (port {})", i, id,
logged_in_users[i] ? logged_in_users[i]->player_index : -1);
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceUserServiceGetMicLevel() {
LOG_ERROR(Lib_UserService, "(STUBBED) called");
return ORBIS_OK;
@ -1056,7 +1071,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
LOG_ERROR(Lib_UserService, "color is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
*color = OrbisUserServiceUserColor::Blue;
*color = (OrbisUserServiceUserColor)UserManagement.GetUserByID(user_id)->user_color;
return ORBIS_OK;
}
@ -1076,12 +1091,18 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
}
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
if (user_name == nullptr) {
LOG_ERROR(Lib_UserService, "user_name is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
std::string name = Config::getUserName();
std::string name = "shadPS4";
auto const* u = UserManagement.GetUserByID(user_id);
if (u != nullptr) {
name = u->user_name;
} else {
LOG_ERROR(Lib_UserService, "No user found");
}
if (size < name.length()) {
LOG_ERROR(Lib_UserService, "buffer is too short");
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;

View File

@ -289,7 +289,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
// Initialize components
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
@ -331,7 +331,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
window = std::make_unique<Frontend::WindowSDL>(EmulatorSettings.GetWindowWidth(),
EmulatorSettings.GetWindowHeight(), controller,
EmulatorSettings.GetWindowHeight(), controllers,
window_title);
g_window = window.get();

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -43,7 +43,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

@ -737,9 +737,8 @@ static void UpdateGamepads() {
ImGuiIO& io = ImGui::GetIO();
SdlData* bd = GetBackendData();
auto controller = Common::Singleton<Input::GameController>::Instance();
auto engine = controller->GetEngine();
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
auto& controllers = *Common::Singleton<Input::GameControllers>::Instance();
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
// Update list of gamepads to use
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
if (SDLGamepad) {

View File

@ -1,15 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <sstream>
#include <unordered_set>
#include <SDL3/SDL.h>
#include "common/config.h"
#include <common/elf_info.h>
#include <common/singleton.h>
#include "common/logging/log.h"
#include "controller.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "core/user_settings.h"
#include "input/controller.h"
static std::string SelectedGamepad = "";
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
@ -22,7 +27,15 @@ void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
}
}
void State::OnAxis(Axis axis, int value) {
void State::OnAxis(Axis axis, int value, bool smooth) {
auto const i = std::to_underlying(axis);
// forcibly finish the previous smoothing task by jumping to the end
axes[i] = axis_smoothing_end_values[i];
axis_smoothing_start_times[i] = time;
axis_smoothing_start_values[i] = axes[i];
axis_smoothing_end_values[i] = value;
axis_smoothing_flags[i] = smooth;
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
@ -40,7 +53,6 @@ void State::OnAxis(Axis axis, int value) {
default:
break;
}
axes[static_cast<int>(axis)] = value;
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
@ -61,6 +73,22 @@ void State::OnAccel(const float accel[3]) {
acceleration.z = accel[2];
}
void State::UpdateAxisSmoothing() {
for (int i = 0; i < std::to_underlying(Axis::AxisMax); i++) {
// if it's not to be smoothed or close enough, just jump to the end
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
if (axes[i] != axis_smoothing_end_values[i]) {
axes[i] = axis_smoothing_end_values[i];
}
continue;
}
auto now = Libraries::Kernel::sceKernelGetProcessTime();
f32 t =
std::clamp((now - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
}
}
GameController::GameController() : m_states_queue(64) {}
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
@ -88,31 +116,82 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
return ret_num;
}
void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
m_state.OnButton(button, is_pressed);
PushState();
}
void GameController::Axis(int id, Input::Axis axis, int value) {
m_state.OnAxis(axis, value);
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
m_state.OnAxis(axis, value, smooth);
PushState();
}
void GameController::Gyro(int id, const float gyro[3]) {
m_state.OnGyro(gyro);
void GameController::Gyro(int id) {
m_state.OnGyro(gyro_buf);
PushState();
}
void GameController::Acceleration(int id, const float acceleration[3]) {
m_state.OnAccel(acceleration);
void GameController::Acceleration(int id) {
m_state.OnAccel(accel_buf);
PushState();
}
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation) {
void GameController::UpdateGyro(const float gyro[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
}
void GameController::UpdateAcceleration(const float acceleration[3]) {
std::scoped_lock l(m_states_queue_mutex);
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
}
void GameController::UpdateAxisSmoothing() {
m_state.UpdateAxisSmoothing();
}
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
colour = {r, g, b};
if (m_sdl_gamepad != nullptr) {
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
}
}
void GameController::PollLightColour() {
if (m_sdl_gamepad != nullptr) {
SDL_SetGamepadLED(m_sdl_gamepad, colour.r, colour.g, colour.b);
}
}
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);
}
return true;
}
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
m_state.OnTouchpad(touchIndex, touchDown, x, y);
PushState();
}
}
std::array<std::optional<Colour>, 4> GameControllers::controller_override_colors{
std::nullopt, std::nullopt, std::nullopt, std::nullopt};
void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation) {
// avoid wildly off values coming from elapsed time between two samples
// being too high, such as on the first time the controller is polled
if (deltaTime > 1.0f) {
orientation = lastOrientation;
return;
}
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
0.0f};
@ -143,27 +222,100 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
orientation.y, orientation.z, orientation.w);
}
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (!m_engine) {
return;
}
m_engine->SetLightBarRGB(r, g, b);
}
bool is_first_check = true;
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (!m_engine) {
return;
}
m_engine->SetVibration(smallMotor, largeMotor);
}
void GameControllers::TryOpenSDLControllers() {
using namespace Libraries::UserService;
int controller_count;
s32 move_count = 0;
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
LOG_INFO(Input, "{} controllers are currently connected", controller_count);
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) {
m_state.OnTouchpad(touchIndex, touchDown, x, y);
PushState();
}
}
std::unordered_set<SDL_JoystickID> assigned_ids;
std::array<bool, 4> slot_taken{false, false, false, false};
for (int i = 0; i < 4; i++) {
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
if (pad) {
SDL_JoystickID id = SDL_GetGamepadID(pad);
bool still_connected = false;
ControllerType type = ControllerType::Standard;
for (int j = 0; j < controller_count; j++) {
if (new_joysticks[j] == id) {
still_connected = true;
assigned_ids.insert(id);
slot_taken[i] = true;
break;
}
}
if (!still_connected) {
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
UserManagement.LogoutUser(u);
SDL_CloseGamepad(pad);
controllers[i]->m_sdl_gamepad = nullptr;
controllers[i]->user_id = -1;
slot_taken[i] = false;
}
}
}
for (int j = 0; j < controller_count; j++) {
SDL_JoystickID id = new_joysticks[j];
if (assigned_ids.contains(id))
continue;
SDL_Gamepad* pad = SDL_OpenGamepad(id);
if (!pad) {
continue;
}
for (int i = 0; i < 4; i++) {
if (!slot_taken[i]) {
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
if (!u) {
LOG_INFO(Input, "User {} not found", i + 1);
continue; // for now, if you don't specify who Player N is in the config,
// Player N won't be registered at all
}
auto* c = controllers[i];
c->m_sdl_gamepad = pad;
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
SDL_GetGamepadID(pad));
c->user_id = u->user_id;
slot_taken[i] = true;
UserManagement.LoginUser(u, i + 1);
if (EmulatorSettings.IsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
c->gyro_poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
c->user_id);
}
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
c->accel_poll_rate =
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
c->user_id);
}
}
break;
}
}
}
if (is_first_check) [[unlikely]] {
is_first_check = false;
if (controller_count - move_count == 0) {
auto u = UserManagement.GetUserByPlayerIndex(1);
controllers[0]->user_id = u->user_id;
UserManagement.LoginUser(u, 1);
}
}
SDL_free(new_joysticks);
}
u8 GameController::GetTouchCount() {
return m_touch_count;
}
@ -215,73 +367,37 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
m_last_update = lastUpdate;
}
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
m_engine = std::move(engine);
if (m_engine) {
m_engine->Init();
}
}
Engine* GameController::GetEngine() {
return m_engine.get();
}
void GameController::PushState() {
std::lock_guard lg(m_states_queue_mutex);
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
m_states_queue.Push(m_state);
}
u32 GameController::Poll() {
if (m_connected) {
PushState();
}
return 33;
}
} // namespace Input
namespace GamepadSelect {
int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
char GUIDbuf[33];
if (Config::getDefaultControllerID() != "") {
for (int i = 0; i < gamepadCount; i++) {
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
std::string currentGUID = std::string(GUIDbuf);
if (currentGUID == Config::getDefaultControllerID()) {
return i;
}
}
}
return -1;
}
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
char GUIDbuf[33];
for (int i = 0; i < gamepadCount; i++) {
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
std::string currentGUID = std::string(GUIDbuf);
if (currentGUID == GUID) {
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
auto g = SDL_GetGamepadFromID(id);
ASSERT(g != nullptr);
for (int i = 0; i < 5; i++) {
if (controllers[i]->m_sdl_gamepad == g) {
return i;
}
}
// LOG_TRACE(Input, "Gamepad index: {}", index);
return -1;
}
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index) {
char GUIDbuf[33];
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[index]), GUIDbuf, 33);
std::string GUID = std::string(GUIDbuf);
return GUID;
std::optional<u8> GameControllers::GetControllerIndexFromUserID(s32 user_id) {
auto const u = UserManagement.GetUserByID(user_id);
if (!u) {
return std::nullopt;
}
return u->player_index - 1;
}
std::string GetSelectedGamepad() {
return SelectedGamepad;
std::optional<u8> GameControllers::GetControllerIndexFromControllerID(s32 controller_id) {
if (controller_id < 1 || controller_id > 5) {
return std::nullopt;
}
return controller_id - 1;
}
void SetSelectedGamepad(std::string GUID) {
SelectedGamepad = GUID;
}
} // namespace GamepadSelect
} // namespace Input

View File

@ -3,18 +3,25 @@
#pragma once
#include <algorithm>
#include <memory>
#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 ControllerType {
Standard,
};
enum class Axis {
LeftX = 0,
LeftY = 1,
@ -33,37 +40,41 @@ struct TouchpadEntry {
u16 y{};
};
class State {
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);
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;
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
AxisArray<s32> axes{axis_defaults};
TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}};
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, 0.0f, 0.0f};
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};
};
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;
SDL_Gamepad* m_gamepad;
};
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));
}
template <class T>
@ -98,6 +109,8 @@ private:
};
class GameController {
friend class GameControllers;
public:
GameController();
virtual ~GameController() = default;
@ -105,16 +118,17 @@ public:
void ReadState(State* state, bool* isConnected, int* connectedCount);
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
void Axis(int id, Input::Axis axis, int value);
void Gyro(int id, const float gyro[3]);
void Acceleration(int id, const float acceleration[3]);
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);
void SetVibration(u8 smallMotor, u8 largeMotor);
void PollLightColour();
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();
u8 GetTouchCount();
void SetTouchCount(u8 touchCount);
@ -129,11 +143,12 @@ public:
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};
s32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
SDL_Gamepad* m_sdl_gamepad = nullptr;
private:
void PushState();
@ -146,22 +161,46 @@ private:
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};
Colour colour;
State m_state;
std::mutex m_states_queue_mutex;
RingBufferQueue<State> m_states_queue;
};
std::unique_ptr<Engine> m_engine = nullptr;
class GameControllers {
std::array<GameController*, 5> controllers;
static std::array<std::optional<Colour>, 4> controller_override_colors;
public:
GameControllers()
: controllers({new GameController(), new GameController(), new GameController(),
new GameController(), new GameController()}) {};
virtual ~GameControllers() = default;
GameController* operator[](const size_t& i) const {
if (i > 4) {
UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i);
}
return controllers[i];
}
void TryOpenSDLControllers();
u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
static std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
static std::optional<u8> 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 SetControllerCustomColor(s32 i, u8 r, u8 g, u8 b) {
controller_override_colors[i] = {r, g, b};
}
static std::optional<Colour> GetControllerCustomColor(s32 i) {
return controller_override_colors[i];
}
};
} // namespace Input
namespace GamepadSelect {
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

View File

@ -18,10 +18,10 @@
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
#include "common/config.h"
#include "common/elf_info.h"
#include "common/io_file.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "core/devtools/layer.h"
#include "core/emulator_settings.h"
#include "core/emulator_state.h"
@ -43,78 +43,185 @@ What structs are needed?
InputBinding(key1, key2, key3)
ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is
always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element))
Things to always test before pushing like a dumbass:
Button outputs
Axis outputs
Input hierarchy
Multi key inputs
Mouse to joystick
Key toggle
Joystick halfmode
Don't be an idiot and test only the changed part expecting everything else to not be broken
*/
constexpr std::string_view GetDefaultGlobalConfig() {
return R"(# Anything put here will be loaded for all games,
# alongside the game's config or default.ini depending on your preference.
)";
}
constexpr std::string_view GetDefaultInputConfig() {
return R"(#Feeling lost? Check out the Help section!
# Keyboard bindings
triangle = kp8
circle = kp6
cross = kp2
square = kp4
# Alternatives for users without a keypad
triangle = c
circle = b
cross = n
square = v
l1 = q
r1 = u
l2 = e
r2 = o
l3 = x
r3 = m
options = enter
touchpad_center = space
pad_up = up
pad_down = down
pad_left = left
pad_right = right
axis_left_x_minus = a
axis_left_x_plus = d
axis_left_y_minus = w
axis_left_y_plus = s
axis_right_x_minus = j
axis_right_x_plus = l
axis_right_y_minus = i
axis_right_y_plus = k
# Controller bindings
triangle = triangle
cross = cross
square = square
circle = circle
l1 = l1
l2 = l2
l3 = l3
r1 = r1
r2 = r2
r3 = r3
options = options
touchpad_center = back
pad_up = pad_up
pad_down = pad_down
pad_left = pad_left
pad_right = pad_right
axis_left_x = axis_left_x
axis_left_y = axis_left_y
axis_right_x = axis_right_x
axis_right_y = axis_right_y
# Range of deadzones: 1 (almost none) to 127 (max)
analog_deadzone = leftjoystick, 2, 127
analog_deadzone = rightjoystick, 2, 127
override_controller_color = false, 0, 0, 255
)";
}
std::filesystem::path GetInputConfigFile(const std::string& game_id) {
// Read configuration file of the game, and if it doesn't exist, generate it from default
// If that doesn't exist either, generate that from getDefaultConfig() and try again
// If even the folder is missing, we start with that.
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "input_config";
const auto config_file = config_dir / (game_id + ".ini");
const auto default_config_file = config_dir / "default.ini";
// Ensure the config directory exists
if (!std::filesystem::exists(config_dir)) {
std::filesystem::create_directories(config_dir);
}
// Check if the default config exists
if (!std::filesystem::exists(default_config_file)) {
// If the default config is also missing, create it from getDefaultConfig()
const auto default_config = GetDefaultInputConfig();
std::ofstream default_config_stream(default_config_file);
if (default_config_stream) {
default_config_stream << default_config;
}
}
// if empty, we only need to execute the function up until this point
if (game_id.empty()) {
return default_config_file;
}
// Create global config if it doesn't exist yet
if (game_id == "global" && !std::filesystem::exists(config_file)) {
if (!std::filesystem::exists(config_file)) {
const auto global_config = GetDefaultGlobalConfig();
std::ofstream global_config_stream(config_file);
if (global_config_stream) {
global_config_stream << global_config;
}
}
}
if (game_id == "global") {
std::map<std::string, std::string> default_bindings_to_add = {
{"hotkey_renderdoc_capture", "f12"},
{"hotkey_fullscreen", "f11"},
{"hotkey_show_fps", "f10"},
{"hotkey_pause", "f9"},
{"hotkey_reload_inputs", "f8"},
{"hotkey_toggle_mouse_to_joystick", "f7"},
{"hotkey_toggle_mouse_to_gyro", "f6"},
{"hotkey_add_virtual_user", "f5"},
{"hotkey_remove_virtual_user", "f4"},
{"hotkey_toggle_mouse_to_touchpad", "delete"},
{"hotkey_quit", "lctrl, lshift, end"},
{"hotkey_volume_up", "kpplus"},
{"hotkey_volume_down", "kpminus"},
};
std::ifstream global_in(config_file);
std::string line;
while (std::getline(global_in, line)) {
line.erase(std::remove_if(line.begin(), line.end(),
[](unsigned char c) { return std::isspace(c); }),
line.end());
std::size_t equal_pos = line.find('=');
if (equal_pos == std::string::npos) {
continue;
}
std::string output_string = line.substr(0, equal_pos);
default_bindings_to_add.erase(output_string);
}
global_in.close();
std::ofstream global_out(config_file, std::ios::app);
for (auto const& b : default_bindings_to_add) {
global_out << b.first << " = " << b.second << "\n";
}
}
// If game-specific config doesn't exist, create it from the default config
if (!std::filesystem::exists(config_file)) {
std::filesystem::copy(default_config_file, config_file);
}
return config_file;
}
bool leftjoystick_halfmode = false, rightjoystick_halfmode = false;
std::pair<int, int> leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone,
righttrigger_deadzone;
std::array<std::pair<int, int>, 4> leftjoystick_deadzone, rightjoystick_deadzone,
lefttrigger_deadzone, righttrigger_deadzone;
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),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
GameControllers ControllerOutput::controllers =
*Common::Singleton<Input::GameControllers>::Instance();
// 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_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // 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(HOTKEY_FULLSCREEN),
ControllerOutput(HOTKEY_PAUSE),
ControllerOutput(HOTKEY_SIMPLE_FPS),
ControllerOutput(HOTKEY_QUIT),
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(HOTKEY_VOLUME_UP),
ControllerOutput(HOTKEY_VOLUME_DOWN),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
std::array<ControllerAllOutputs, 9> output_arrays = {
ControllerAllOutputs(0), ControllerAllOutputs(1), ControllerAllOutputs(2),
ControllerAllOutputs(3), ControllerAllOutputs(4), ControllerAllOutputs(5),
ControllerAllOutputs(6), ControllerAllOutputs(7), ControllerAllOutputs(8),
};
void ControllerOutput::LinkJoystickAxes() {
@ -158,6 +265,8 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OPBDO::L1;
case SDL_GAMEPAD_BUTTON_MISC1: // Move
return OPBDO::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OPBDO::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
@ -223,10 +332,19 @@ 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 game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetInputConfigFile(game_id_or_default);
const auto global_config_file = Config::GetInputConfigFile("global");
std::string game_id_or_default =
EmulatorSettings.IsUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = GetInputConfigFile(game_id_or_default);
const auto global_config_file = GetInputConfigFile("global");
// we reset these here so in case the user fucks up or doesn't include some of these,
// we can fall back to default
@ -235,13 +353,14 @@ void ParseInputConfig(const std::string game_id = "") {
float mouse_speed = 1;
float mouse_speed_offset = 0.125;
leftjoystick_deadzone = {1, 127};
rightjoystick_deadzone = {1, 127};
lefttrigger_deadzone = {1, 127};
righttrigger_deadzone = {1, 127};
// me when I'm in a type deduction tournament and my opponent is clang
constexpr std::array<std::pair<int, int>, 4> default_deadzone = {
std::pair{1, 127}, {1, 127}, {1, 127}, {1, 127}};
Config::SetOverrideControllerColor(false);
Config::SetControllerCustomColor(0, 0, 255);
leftjoystick_deadzone = default_deadzone;
rightjoystick_deadzone = default_deadzone;
lefttrigger_deadzone = default_deadzone;
righttrigger_deadzone = default_deadzone;
int lineCount = 0;
@ -278,21 +397,37 @@ 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);
// Remove trailing semicolon from input_string
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
input_string != ";") {
line = line.substr(0, line.length() - 1);
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
// 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 override_controller_color and analog_deadzone be controller specific
// instead of global
if (output_string == "mouse_to_joystick") {
if (input_string == "left") {
SetMouseToJoystick(1);
@ -315,7 +450,7 @@ void ParseInputConfig(const std::string game_id = "") {
return;
}
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);
@ -356,15 +491,17 @@ void ParseInputConfig(const std::string game_id = "") {
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
static std::unordered_map<std::string, std::pair<int, int>&> deadzone_map = {
{"leftjoystick", leftjoystick_deadzone},
{"rightjoystick", rightjoystick_deadzone},
{"l2", lefttrigger_deadzone},
{"r2", righttrigger_deadzone},
};
static std::unordered_map<std::string, std::array<std::pair<int, int>, 4>&>
deadzone_map = {
{"leftjoystick", leftjoystick_deadzone},
{"rightjoystick", rightjoystick_deadzone},
{"l2", lefttrigger_deadzone},
{"r2", righttrigger_deadzone},
};
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
if (auto it = deadzone_map.find(device); it != deadzone_map.end()) {
it->second = deadzone;
it->second[output_gamepad_id - 1] = deadzone;
LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str,
outer_deadzone_str);
} else {
@ -390,10 +527,12 @@ void ParseInputConfig(const std::string game_id = "") {
lineCount, line);
return;
}
Config::SetOverrideControllerColor(enable == "true");
Config::SetControllerCustomColor(*r, *g, *b);
LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}",
enable == "true" ? "override" : "no override", *r, *b, *g);
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
if (enable == "true") {
GameControllers::SetControllerCustomColor(output_gamepad_id - 1, *r, *g, *b);
}
LOG_DEBUG(Input, "Parsed color settings: {} {} - {} {} {}",
enable == "true" ? "override" : "no override", output_gamepad_id, *r, *b, *g);
return;
}
@ -410,31 +549,46 @@ void ParseInputConfig(const std::string game_id = "") {
auto axis_it = string_to_axis_map.find(output_string);
if (button_it != string_to_cbutton_map.end()) {
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 (hotkey_it != string_to_hotkey_map.end()) {
connection = BindingConnection(
binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second)));
connections.insert(connections.end(), connection);
binding,
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
ControllerOutput(hotkey_it->second)));
} else if (axis_it != string_to_axis_map.end()) {
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);
return;
}
// 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 < output_arrays.size(); 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);
};
while (std::getline(global_config_stream, line)) {
ProcessLine();
}
lineCount = 0;
while (std::getline(config_stream, line)) {
ProcessLine();
}
@ -446,6 +600,16 @@ void ParseInputConfig(const std::string game_id = "") {
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!");
@ -464,6 +628,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
}
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
u8 gamepad = 1;
switch (e.type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
@ -478,21 +643,17 @@ 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, static_cast<u32>(e.gbutton.button), e.gbutton.down,
0); // clang made me do it
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gbutton.which) + 1;
return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down,
0);
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
return InputEvent(InputType::Axis, static_cast<u32>(e.gaxis.axis), true,
e.gaxis.value / 256); // this too
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gaxis.which) + 1;
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;
}
void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) {
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
@ -538,7 +699,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) {
auto PushSDLEvent = [&](u32 event_type) {
if (new_button_state) {
SDL_Event e;
@ -553,20 +714,24 @@ void ControllerOutput::FinalizeUpdate() {
}
old_button_state = new_button_state;
old_param = *new_param;
bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed();
GameController* controller;
if (gamepad_index < 5)
controller = controllers[gamepad_index];
else
UNREACHABLE();
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
break;
case LEFTJOYSTICK_HALFMODE:
leftjoystick_halfmode = new_button_state;
@ -598,6 +763,12 @@ void ControllerOutput::FinalizeUpdate() {
case HOTKEY_RENDERDOC:
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
break;
case HOTKEY_ADD_VIRTUAL_USER:
PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER);
break;
case HOTKEY_REMOVE_VIRTUAL_USER:
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
break;
case HOTKEY_VOLUME_UP:
EmulatorSettings.SetVolumeSlider(
std::clamp(EmulatorSettings.GetVolumeSlider() + 10, 0, 500));
@ -618,7 +789,7 @@ void ControllerOutput::FinalizeUpdate() {
SetMouseGyroRollMode(new_button_state);
break;
default: // is a normal key (hopefully)
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
break;
}
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
@ -638,28 +809,28 @@ void ControllerOutput::FinalizeUpdate() {
switch (c_axis) {
case Axis::LeftX:
case Axis::LeftY:
ApplyDeadzone(new_param, leftjoystick_deadzone);
ApplyDeadzone(new_param, leftjoystick_deadzone[gamepad_index]);
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
break;
case Axis::RightX:
case Axis::RightY:
ApplyDeadzone(new_param, rightjoystick_deadzone);
ApplyDeadzone(new_param, rightjoystick_deadzone[gamepad_index]);
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
break;
case Axis::TriggerLeft:
ApplyDeadzone(new_param, lefttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->Button(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
ApplyDeadzone(new_param, lefttrigger_deadzone[gamepad_index]);
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->Button(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->Button(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
ApplyDeadzone(new_param, righttrigger_deadzone[gamepad_index]);
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->Button(OrbisPadButtonDataOffset::R2, *new_param > 0x20);
return;
default:
break;
}
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
controller->Axis(c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
}
}
@ -674,11 +845,9 @@ bool UpdatePressedKeys(InputEvent event) {
if (input.type == InputType::Axis) {
// analog input, it gets added when it first sends an event,
// and from there, it only changes the parameter
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
[](const std::pair<InputEvent, bool>& e, InputID i) {
return std::tie(e.first.input.type, e.first.input.sdl_id) <
std::tie(i.type, i.sdl_id);
});
auto it = std::lower_bound(
pressed_keys.begin(), pressed_keys.end(), input,
[](const std::pair<InputEvent, bool>& e, InputID i) { return e.first.input < i; });
if (it == pressed_keys.end() || it->first.input != input) {
pressed_keys.insert(it, {event, false});
LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id);
@ -791,25 +960,33 @@ InputEvent BindingConnection::ProcessBinding() {
}
void ActivateOutputsFromInputs() {
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_array) {
it.ResetUpdate();
}
// Check for input blockers
ApplyMouseInputBlockers();
// todo find a better solution
for (int i = 0; i < output_arrays.size(); i++) {
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
it.output->AddUpdate(it.ProcessBinding());
}
// Reset values and flags
for (auto& it : pressed_keys) {
it.second = false;
}
for (auto& it : output_arrays[i].data) {
it.ResetUpdate();
}
// Update all outputs
for (auto& it : output_array) {
it.FinalizeUpdate();
// Check for input blockers
ApplyMouseInputBlockers();
// 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

@ -7,6 +7,7 @@
#include <map>
#include <string>
#include <unordered_set>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
@ -35,9 +36,11 @@
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11
#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 13
#define LEFTJOYSTICK_HALFMODE 0x00010000
#define RIGHTJOYSTICK_HALFMODE 0x00020000
@ -57,6 +60,8 @@
#define HOTKEY_RENDERDOC 0xf0000009
#define HOTKEY_VOLUME_UP 0xf000000a
#define HOTKEY_VOLUME_DOWN 0xf000000b
#define HOTKEY_ADD_VIRTUAL_USER 0xf000000c
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d
#define SDL_UNMAPPED UINT32_MAX - 1
@ -77,21 +82,24 @@ class InputID {
public:
InputType type;
u32 sdl_id;
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : 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;
auto operator<=>(const InputID& o) const {
return std::tie(gamepad_id, type, sdl_id, gamepad_id) <=>
std::tie(o.gamepad_id, o.type, o.sdl_id, o.gamepad_id);
}
bool IsValid() const {
return *this != InputID();
}
std::string ToString() {
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
}
};
@ -149,6 +157,8 @@ const std::map<std::string, u32> string_to_hotkey_map = {
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
};
@ -401,7 +411,7 @@ public:
inline bool IsEmpty() {
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
}
std::string ToString() { // todo add device type
std::string ToString() {
switch (KeyCount()) {
case 1:
return fmt::format("({})", keys[0].ToString());
@ -420,14 +430,14 @@ public:
};
class ControllerOutput {
static GameController* controller;
public:
static void SetControllerOutputController(GameController* c);
static GameControllers controllers;
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
@ -441,6 +451,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);
@ -466,7 +477,7 @@ public:
void ResetUpdate();
void AddUpdate(InputEvent event);
void FinalizeUpdate();
void FinalizeUpdate(u8 gamepad_index);
};
class BindingConnection {
public:
@ -481,6 +492,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)
@ -494,9 +512,81 @@ 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:
static constexpr u64 output_count = 39;
std::array<ControllerOutput, output_count> 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),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// 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_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // 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(HOTKEY_FULLSCREEN),
ControllerOutput(HOTKEY_PAUSE),
ControllerOutput(HOTKEY_SIMPLE_FPS),
ControllerOutput(HOTKEY_QUIT),
ControllerOutput(HOTKEY_RELOAD_INPUTS),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
ControllerOutput(HOTKEY_RENDERDOC),
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
ControllerOutput(HOTKEY_VOLUME_UP),
ControllerOutput(HOTKEY_VOLUME_DOWN),
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
};
ControllerAllOutputs(u8 g) {
for (int i = 0; i < output_count; 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

@ -77,11 +77,11 @@ void EmulateJoystick(GameController* controller, u32 interval) {
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
if (d_x != 0 || d_y != 0) {
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, a_x));
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, a_y));
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, a_x), false);
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, a_y), false);
} else {
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0));
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0));
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, 0), false);
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, 0), false);
}
}
@ -89,13 +89,13 @@ constexpr float constant_down_accel[3] = {0.0f, 9.81f, 0.0f};
void EmulateGyro(GameController* controller, u32 interval) {
float d_x = 0, d_y = 0;
SDL_GetRelativeMouseState(&d_x, &d_y);
controller->Acceleration(1, constant_down_accel);
controller->UpdateAcceleration(constant_down_accel);
float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f};
if (mouse_gyro_roll_mode) {
gyro_from_mouse[1] = 0.0f;
gyro_from_mouse[2] = -d_x / 100;
}
controller->Gyro(1, gyro_from_mouse);
controller->UpdateGyro(gyro_from_mouse);
}
void EmulateTouchpad(GameController* controller, u32 interval) {
@ -104,7 +104,7 @@ void EmulateTouchpad(GameController* controller, u32 interval) {
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
controller->Button(Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
}

View File

@ -8,12 +8,14 @@
#include "SDL3/SDL_timer.h"
#include "SDL3/SDL_video.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/elf_info.h"
#include "core/debug_state.h"
#include "core/devtools/layer.h"
#include "core/emulator_settings.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "core/user_settings.h"
#include "imgui/renderer/imgui_core.h"
#include "input/controller.h"
#include "input/input_handler.h"
@ -26,9 +28,9 @@
#endif
#include <core/emulator_settings.h>
namespace Input {
namespace Frontend {
using Libraries::Pad::OrbisPadButtonDataOffset;
using namespace Libraries::Pad;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
using OPBDO = OrbisPadButtonDataOffset;
@ -69,220 +71,24 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
}
}
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;
}
int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count,
GamepadSelect::GetSelectedGamepad());
int defaultIndex =
GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID());
// If user selects a gamepad in the GUI, use that, otherwise try the default
if (!m_gamepad) {
if (selectedIndex != -1) {
m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]);
LOG_INFO(Input, "Opening gamepad selected in GUI.");
} else if (defaultIndex != -1) {
m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]);
LOG_INFO(Input, "Opening default gamepad.");
} else {
m_gamepad = SDL_OpenGamepad(gamepads[0]);
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
}
}
if (!m_gamepad) {
if (!m_gamepad) {
LOG_ERROR(Input, "Failed to open gamepad: {}", 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;
std::mutex motion_control_mutex;
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
static Uint32 SDLCALL PollGyroAndAccel(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
std::scoped_lock l{motion_control_mutex};
controller->Gyro(0, gyro_buf);
controller->Acceleration(0, accel_buf);
return 4;
controller->UpdateAxisSmoothing();
controller->Gyro(0);
controller->Acceleration(0);
return interval;
}
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
static Uint32 SDLCALL PollControllerLightColour(void* userdata, SDL_TimerID timer_id,
Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
controller->PollLightColour();
return interval;
}
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());
}
@ -330,7 +136,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
SDL_SyncWindow(window);
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
#if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows;
@ -355,11 +160,11 @@ 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()));
controllers.TryOpenSDLControllers();
if (Config::getBackgroundControllerInput()) {
if (EmulatorSettings.IsBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
}
}
@ -399,37 +204,16 @@ void WindowSDL::WaitEvent() {
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
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.TryOpenSDLControllers();
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
OnGamepadEvent(&event);
break;
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS
// AND IT DOESN'T EVEN USE PROPER ENUMS
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO: {
std::scoped_lock l{motion_control_mutex};
memcpy(gyro_buf, event.gsensor.data, sizeof(gyro_buf));
break;
}
case SDL_SENSOR_ACCEL: {
std::scoped_lock l{motion_control_mutex};
memcpy(accel_buf, event.gsensor.data, sizeof(accel_buf));
break;
}
default:
break;
}
OnGamepadEvent(&event);
break;
case SDL_EVENT_QUIT:
is_open = false;
@ -455,7 +239,7 @@ void WindowSDL::WaitEvent() {
}
break;
case SDL_EVENT_CHANGE_CONTROLLER:
controller->GetEngine()->Init();
UNREACHABLE_MSG("todo");
break;
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
Overlay::ToggleSimpleFps();
@ -476,6 +260,29 @@ void WindowSDL::WaitEvent() {
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
break;
case SDL_EVENT_ADD_VIRTUAL_USER:
for (int i = 0; i < 4; i++) {
if (controllers[i]->user_id == -1) {
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
if (!u) {
break;
}
controllers[i]->user_id = u->user_id;
UserManagement.LoginUser(u, i + 1);
break;
}
}
break;
case SDL_EVENT_REMOVE_VIRTUAL_USER:
LOG_INFO(Input, "Remove user");
for (int i = 3; i >= 0; i--) {
if (controllers[i]->user_id != -1) {
UserManagement.LogoutUser(UserManagement.GetUserByID(controllers[i]->user_id));
controllers[i]->user_id = -1;
break;
}
}
break;
case SDL_EVENT_RDOC_CAPTURE:
VideoCore::TriggerCapture();
break;
@ -485,8 +292,10 @@ void WindowSDL::WaitEvent() {
}
void WindowSDL::InitTimers() {
SDL_AddTimer(4, &PollGyroAndAccel, controller);
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
for (int i = 0; i < 4; ++i) {
SDL_AddTimer(4, &PollController, controllers[i]);
}
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
}
void WindowSDL::RequestKeyboard() {
@ -554,10 +363,44 @@ 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->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down);
controllers[controllers.GetGamepadIndexFromJoystickId(event->gbutton.which)]->Button(
OrbisPadButtonDataOffset::TouchPad, input_down);
return;
}
u8 gamepad;
switch (event->type) {
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
switch ((SDL_SensorType)event->gsensor.sensor) {
case SDL_SENSOR_GYRO:
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
if (gamepad < 5) {
controllers[gamepad]->UpdateGyro(event->gsensor.data);
}
break;
case SDL_SENSOR_ACCEL:
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
if (gamepad < 5) {
controllers[gamepad]->UpdateAcceleration(event->gsensor.data);
}
break;
default:
break;
}
return;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
controllers[controllers.GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
->SetTouchpadState(event->gtouchpad.finger,
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
event->gtouchpad.y);
return;
default:
break;
}
// add/remove it from the list
bool inputs_changed = Input::UpdatePressedKeys(input_event);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -14,23 +14,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:
float m_gyro_poll_rate = 0.0f;
float m_accel_poll_rate = 0.0f;
};
} // namespace Input
class GameController;
}
namespace Frontend {
@ -62,7 +47,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();
@ -100,7 +85,7 @@ private:
private:
s32 width;
s32 height;
Input::GameController* controller;
Input::GameControllers controllers{};
WindowSystemInfo window_info{};
SDL_Window* window{};
bool is_shown{};