Squashed multiple controllers PR

This commit is contained in:
kalaposfos13
2025-11-29 11:03:46 +01:00
parent d1e9b47fc3
commit ad1e58ed16
15 changed files with 688 additions and 599 deletions

View File

@@ -46,7 +46,7 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
if constexpr (std::is_same_v<T, int>) {
if (it->second.is_integer()) {
return static_cast<int>(toml::get<int>(it->second));
return static_cast<s32>(toml::get<int>(it->second));
}
} else if constexpr (std::is_same_v<T, unsigned int>) {
if (it->second.is_integer()) {
@@ -54,15 +54,19 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
}
} else if constexpr (std::is_same_v<T, double>) {
if (it->second.is_floating()) {
return toml::get<double>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, std::string>) {
if (it->second.is_string()) {
return toml::get<std::string>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, bool>) {
if (it->second.is_boolean()) {
return toml::get<bool>(it->second);
return toml::get<T>(it->second);
}
} else if constexpr (std::is_same_v<T, std::array<string, 4>>) {
if (it->second.is_array()) {
return toml::get<T>(it->second);
}
} else {
static_assert([] { return false; }(), "Unsupported type in get_optional<T>");
@@ -114,6 +118,9 @@ public:
void set(const T value, bool is_game_specific = false) {
is_game_specific ? game_specific_value = value : base_value = value;
}
void setDefault(bool is_game_specific = false) {
is_game_specific ? game_specific_value = default_value : base_value = default_value;
}
void setTomlValue(toml::ordered_value& data, const std::string& header, const std::string& key,
bool is_game_specific = false) {
if (is_game_specific) {
@@ -130,7 +137,12 @@ public:
// General
static ConfigEntry<int> volumeSlider(100);
static ConfigEntry<string> userName("shadPS4");
static ConfigEntry<std::array<std::string, 4>> userNames({
"shadPS4",
"shadps4-2",
"shadPS4-3",
"shadPS4-4",
});
// Input
static ConfigEntry<int> cursorState(HideCursorState::Idle);
@@ -296,8 +308,18 @@ u32 getInternalScreenHeight() {
return internalScreenHeight.get();
}
string getUserName() {
return userName.get();
void setUserName(int id, string name) {
auto temp = userNames.get();
temp[id] = name;
userNames.set(temp);
}
std::array<string, 4> const getUserNames() {
return userNames.get();
}
std::string getUserName(int id) {
return userNames.get()[id];
}
bool getUseSpecialPad() {
@@ -479,10 +501,6 @@ void setLanguage(u32 language, bool is_game_specific) {
m_language.set(language, is_game_specific);
}
void setUserName(const string& name, bool is_game_specific) {
userName.set(name, is_game_specific);
}
void setUseSpecialPad(bool use) {
useSpecialPad.base_value = use;
}
@@ -584,7 +602,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
const toml::value& general = data.at("General");
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
userName.setFromToml(general, "userName", is_game_specific);
userNames.setFromToml(general, "userNames", is_game_specific);
defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific);
}
@@ -746,7 +764,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
}
// Entries saved by the game-specific settings GUI
volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific);
userName.setTomlValue(data, "General", "userName", is_game_specific);
userNames.setTomlValue(data, "General", "userNames", is_game_specific);
cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific);
cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific);
@@ -845,36 +863,36 @@ void setDefaultValues(bool is_game_specific) {
// Entries with game-specific settings that are in the game-specific setings GUI but not in
// the global settings GUI
if (is_game_specific) {
readbacksEnabled.set(false, is_game_specific);
readbackLinearImagesEnabled.set(false, is_game_specific);
directMemoryAccessEnabled.set(false, is_game_specific);
readbacksEnabled.setDefault(is_game_specific);
readbackLinearImagesEnabled.setDefault(is_game_specific);
directMemoryAccessEnabled.setDefault(is_game_specific);
}
// Entries with game-specific settings that are in both the game-specific and global GUI
// GS - General
volumeSlider.set(100, is_game_specific);
userName.set("shadPS4", is_game_specific);
volumeSlider.setDefault(is_game_specific);
userNames.setDefault(is_game_specific);
// GS - Input
cursorState.set(HideCursorState::Idle, is_game_specific);
cursorHideTimeout.set(5, is_game_specific);
isMotionControlsEnabled.set(true, is_game_specific);
backgroundControllerInput.set(false, is_game_specific);
usbDeviceBackend.set(UsbBackendType::Real, is_game_specific);
cursorState.setDefault(is_game_specific);
cursorHideTimeout.setDefault(is_game_specific);
isMotionControlsEnabled.setDefault(is_game_specific);
backgroundControllerInput.setDefault(is_game_specific);
usbDeviceBackend.setDefault(is_game_specific);
// GS - Audio
micDevice.set("Default Device", is_game_specific);
micDevice.setDefault(is_game_specific);
// GS - GPU
windowWidth.set(1280, is_game_specific);
windowHeight.set(720, is_game_specific);
shouldCopyGPUBuffers.set(false, is_game_specific);
shouldDumpShaders.set(false, is_game_specific);
vblankFrequency.set(60, is_game_specific);
isHDRAllowed.set(false, is_game_specific);
fsrEnabled.set(true, is_game_specific);
rcasEnabled.set(true, is_game_specific);
rcasAttenuation.set(250, is_game_specific);
windowWidth.setDefault(is_game_specific);
windowHeight.setDefault(is_game_specific);
shouldCopyGPUBuffers.setDefault(is_game_specific);
shouldDumpShaders.setDefault(is_game_specific);
vblankFrequency.setDefault(is_game_specific);
isHDRAllowed.setDefault(is_game_specific);
fsrEnabled.setDefault(is_game_specific);
rcasEnabled.setDefault(is_game_specific);
rcasAttenuation.setDefault(is_game_specific);
// GS - Vulkan
vkValidation.set(false, is_game_specific);
@@ -887,7 +905,7 @@ void setDefaultValues(bool is_game_specific) {
rdocEnable.set(false, is_game_specific);
// GS - Settings
m_language.set(1, is_game_specific);
m_language.setDefault(is_game_specific);
// All other entries
if (!is_game_specific) {
@@ -924,6 +942,8 @@ 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_quit = lctrl, lshift, end
)";
}

View File

@@ -111,7 +111,8 @@ void setUsbDeviceBackend(int value, bool is_game_specific = false);
// TODO
std::filesystem::path GetSaveDataPath();
std::string getUserName();
std::string getUserName(int id);
std::array<std::string, 4> const getUserNames();
bool GetUseUnifiedInputConfig();
void SetUseUnifiedInputConfig(bool use);
bool GetOverrideControllerColor();

View File

@@ -627,7 +627,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use
return ORBIS_NP_ERROR_SIGNED_OUT;
}
memset(np_id, 0, sizeof(OrbisNpId));
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
strncpy(np_id->handle.data, Config::getUserName(user_id - 1).c_str(),
sizeof(np_id->handle.data));
return ORBIS_OK;
}
@@ -641,7 +642,7 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId
return ORBIS_NP_ERROR_SIGNED_OUT;
}
memset(online_id, 0, sizeof(OrbisNpOnlineId));
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
strncpy(online_id->data, Config::getUserName(user_id - 1).c_str(), sizeof(online_id->data));
return ORBIS_OK;
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
@@ -274,7 +274,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
g_opened = true;
scePadResetLightBar(userId);
scePadResetOrientation(userId);
return 1; // dummy
return userId; // TODO: userId shouldn't be used as the handle too
}
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
@@ -287,7 +287,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() {
@@ -301,12 +301,16 @@ int PS4_SYSV_ABI scePadOutputReport() {
}
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
LOG_TRACE(Lib_Pad, "called");
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
int connected_count = 0;
bool connected = false;
Input::State states[64];
auto* controller = Common::Singleton<GameController>::Instance();
const auto* engine = controller->GetEngine();
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
auto const& controller = controllers[*controller_id];
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
if (!connected) {
@@ -321,9 +325,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].rightStick.y = states[i].axes[static_cast<int>(Input::Axis::RightY)];
pData[i].analogButtons.l2 = states[i].axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData[i].analogButtons.r2 = states[i].axes[static_cast<int>(Input::Axis::TriggerRight)];
pData[i].acceleration.x = states[i].acceleration.x;
pData[i].acceleration.y = states[i].acceleration.y;
pData[i].acceleration.z = states[i].acceleration.z;
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
@@ -335,8 +336,8 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (handle == 1) {
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>(
@@ -431,24 +432,34 @@ int PS4_SYSV_ABI scePadReadHistory() {
}
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "called");
if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) {
LOG_TRACE(Lib_Pad, "handle: {}", handle);
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(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<Input::GameControllers>::Instance();
int connectedCount = 0;
bool isConnected = false;
Input::State state;
auto const& controller = controllers[*controller_id];
controller->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)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.y = state.axes[static_cast<int>(Input::Axis::RightY)];
pData->analogButtons.l2 = state.axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData->analogButtons.r2 = state.axes[static_cast<int>(Input::Axis::TriggerRight)];
auto getAxisValue = [&state, &controller](Input::Axis a) {
auto i = static_cast<int>(a);
if (controller->axis_smoothing_ticks[i] > 0) {
--controller->axis_smoothing_ticks[i];
return (state.axes[i] + controller->axis_smoothing_values[i]) / 2;
}
return state.axes[i];
};
pData->leftStick.x = getAxisValue(Input::Axis::LeftX);
pData->leftStick.y = getAxisValue(Input::Axis::LeftY);
pData->rightStick.x = getAxisValue(Input::Axis::RightX);
pData->rightStick.y = getAxisValue(Input::Axis::RightY);
pData->analogButtons.l2 = getAxisValue(Input::Axis::TriggerLeft);
pData->analogButtons.r2 = getAxisValue(Input::Axis::TriggerRight);
pData->acceleration.x = state.acceleration.x * 0.098;
pData->acceleration.y = state.acceleration.y * 0.098;
pData->acceleration.z = state.acceleration.z * 0.098;
@@ -457,21 +468,19 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
// Only do this on handle 1 for now
if (engine && handle == 1) {
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->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
}
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->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
@@ -536,12 +545,13 @@ int PS4_SYSV_ABI scePadReadStateExt() {
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
LOG_INFO(Lib_Pad, "(DUMMY) called");
if (handle != 1) {
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
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[*controller_id]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
return ORBIS_OK;
}
@@ -558,7 +568,8 @@ 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 = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
@@ -614,6 +625,10 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
if (Config::GetOverrideControllerColor()) {
return ORBIS_OK;
}
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
if (!controller_id) {
return ORBIS_PAD_ERROR_INVALID_HANDLE;
}
if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "called handle = {} rgb = {} {} {}", handle, pParam->r, pParam->g,
pParam->b);
@@ -623,7 +638,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;
}
@@ -691,7 +706,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,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <queue>
#include "common/config.h"
#include "common/logging/log.h"
#include "common/singleton.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 {
@@ -105,15 +110,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
return ORBIS_OK;
}
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId);
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();
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
temp.userId);
return ORBIS_OK;
}
@@ -567,17 +580,22 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
}
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
LOG_DEBUG(Lib_UserService, "called");
// LOG_DEBUG(Lib_UserService, "called");
if (userIdList == nullptr) {
LOG_ERROR(Lib_UserService, "user_id 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;
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int li = 0;
for (int ci = 0; ci < 4; ci++) {
if (controllers[ci]->user_id != -1) {
userIdList->user_id[li++] = controllers[ci]->user_id;
}
}
for (; li < 4; li++) {
userIdList->user_id[li] = -1;
}
return ORBIS_OK;
}
@@ -1048,7 +1066,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)(user_id - 1);
return ORBIS_OK;
}
@@ -1068,12 +1086,12 @@ 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 = Config::getUserName(user_id - 1);
if (size < name.length()) {
LOG_ERROR(Lib_UserService, "buffer is too short");
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// reference :
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/user.h
@@ -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

@@ -259,7 +259,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
@@ -301,7 +301,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
window = std::make_unique<Frontend::WindowSDL>(
Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
Config::getWindowWidth(), Config::getWindowHeight(), controllers, window_title);
g_window = window.get();

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 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

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// Based on imgui_impl_sdl3.cpp from Dear ImGui repository
@@ -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,66 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_set>
#include <SDL3/SDL.h>
#include <common/singleton.h>
#include "common/config.h"
#include "common/logging/log.h"
#include "controller.h"
#include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "input/controller.h"
static std::string SelectedGamepad = "";
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();
@@ -110,7 +65,8 @@ State GameController::GetLastState() const {
return m_last_state;
}
const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES;
return m_states[last];
auto copy = m_states[last];
return copy;
}
void GameController::AddState(const State& state) {
@@ -126,22 +82,50 @@ 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);
if (std::abs(state.axes[axis_id] - value) > 120) {
LOG_DEBUG(Input, "Keyboard axis change detected");
axis_smoothing_ticks[axis_id] = GameController::max_smoothing_ticks;
axis_smoothing_values[axis_id] = state.axes[axis_id];
}
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);
}
@@ -152,7 +136,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);
}
@@ -162,7 +148,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);
}
@@ -203,33 +191,116 @@ 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);
}
}
bool is_first_check = true;
void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
using namespace Libraries::UserService;
int controller_count;
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
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;
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) {
AddUserServiceEvent({OrbisUserServiceEventType::Logout, i + 1});
SDL_CloseGamepad(pad);
controllers[i]->m_sdl_gamepad = nullptr;
controllers[i]->user_id = -1;
slot_taken[i] = false;
} else {
controllers[i]->player_index = i;
}
}
}
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* c = controllers[i];
c->m_sdl_gamepad = pad;
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
SDL_GetGamepadID(pad));
c->user_id = i + 1;
slot_taken[i] = true;
c->player_index = i;
AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1});
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 == 0) {
controllers[0]->user_id = 1;
AddUserServiceEvent({OrbisUserServiceEventType::Login, 1});
}
}
}
u8 GameController::GetTouchCount() {
std::scoped_lock lock{m_mutex};
return m_touch_count;
@@ -293,21 +364,9 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
m_last_update = lastUpdate;
}
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
std::scoped_lock _{m_mutex};
m_engine = std::move(engine);
if (m_engine) {
m_engine->Init();
}
}
Engine* GameController::GetEngine() {
return m_engine.get();
}
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;
@@ -325,6 +384,12 @@ u32 GameController::Poll() {
return 100;
}
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
s32 index = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id));
LOG_TRACE(Input, "Gamepad index: {}", index);
return index;
}
} // namespace Input
namespace GamepadSelect {
@@ -343,6 +408,12 @@ int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
return -1;
}
std::optional<u8> GetControllerIndexFromUserID(s32 user_id) {
if (user_id < 1 || user_id > 4)
return std::nullopt;
return static_cast<u8>(user_id - 1);
}
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
char GUIDbuf[33];
for (int i = 0; i < gamepadCount; i++) {

View File

@@ -1,15 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <memory>
#include <mutex>
#include <SDL3/SDL_gamepad.h>
#include "SDL3/SDL_joystick.h"
#include "common/assert.h"
#include "common/types.h"
#include "core/libraries/pad/pad.h"
struct SDL_Gamepad;
namespace Input {
enum class Axis {
@@ -30,14 +32,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};
@@ -47,25 +42,16 @@ 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;
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));
}
constexpr u32 MAX_STATES = 32;
class GameController {
friend class GameControllers;
public:
GameController();
virtual ~GameController() = default;
@@ -79,10 +65,8 @@ 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();
u8 GetTouchCount();
@@ -104,6 +88,14 @@ public:
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation);
float gyro_poll_rate;
float accel_poll_rate;
u32 user_id = -1; // ORBIS_USER_SERVICE_USER_ID_INVALID
SDL_Gamepad* m_sdl_gamepad = nullptr;
static constexpr int max_smoothing_ticks = 2;
int axis_smoothing_ticks[static_cast<int>(Input::Axis::AxisMax)]{0};
int axis_smoothing_values[static_cast<int>(Input::Axis::AxisMax)]{0};
private:
struct StateInternal {
bool obtained = false;
@@ -125,13 +117,32 @@ private:
std::chrono::steady_clock::time_point m_last_update = {};
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
std::unique_ptr<Engine> m_engine = nullptr;
u8 player_index = -1;
};
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 {} is out of bounds for GameControllers!", i);
}
return controllers[i];
}
static void TryOpenSDLControllers(GameControllers& controllers);
static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
};
} // namespace Input
namespace GamepadSelect {
std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
std::string GetSelectedGamepad();

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "input_handler.h"
@@ -22,6 +22,7 @@
#include "common/elf_info.h"
#include "common/io_file.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "input/controller.h"
#include "input/input_mouse.h"
@@ -61,54 +62,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),
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(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_RENDERDOC),
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() {
@@ -215,6 +173,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 game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default);
@@ -270,21 +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);
// 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
// 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);
@@ -307,7 +291,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);
@@ -400,40 +384,67 @@ void ParseInputConfig(const std::string game_id = "") {
return;
}
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);
return;
}
// 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);
};
while (std::getline(global_config_stream, line)) {
ProcessLine();
}
lineCount = 0;
while (std::getline(config_stream, line)) {
ProcessLine();
}
config_stream.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!");
@@ -452,6 +463,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:
@@ -466,20 +479,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, static_cast<u32>(e.gbutton.button), e.gbutton.down,
0); // clang made me do it
gamepad = GameControllers::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 = GameControllers::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;
}
GameControllers ControllerOutput::controllers =
*Common::Singleton<Input::GameControllers>::Instance();
void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) {
@@ -526,7 +538,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;
@@ -542,6 +554,7 @@ void ControllerOutput::FinalizeUpdate() {
old_button_state = new_button_state;
old_param = *new_param;
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
auto controller = controllers[gamepad_index];
switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
@@ -582,6 +595,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_QUIT:
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
break;
@@ -622,18 +641,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, 0x7f, *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, 0x7f, *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, 0x7f, *new_param * multiplier));
}
}
@@ -765,22 +786,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

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -7,6 +7,7 @@
#include <map>
#include <string>
#include <unordered_set>
#include <vector>
#include "SDL3/SDL_events.h"
#include "SDL3/SDL_timer.h"
@@ -37,6 +38,8 @@
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
#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 LEFTJOYSTICK_HALFMODE 0x00010000
#define RIGHTJOYSTICK_HALFMODE 0x00020000
@@ -53,6 +56,8 @@
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
#define HOTKEY_RENDERDOC 0xf0000008
#define HOTKEY_ADD_VIRTUAL_USER 0xf0000009
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000a
#define SDL_UNMAPPED UINT32_MAX - 1
@@ -73,21 +78,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;
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[static_cast<u8>(type)], sdl_id);
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
}
};
@@ -142,6 +150,8 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
};
const std::map<std::string, AxisMapping> string_to_axis_map = {
@@ -392,7 +402,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());
@@ -411,14 +421,15 @@ public:
};
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
@@ -432,6 +443,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);
@@ -457,7 +469,7 @@ public:
void ResetUpdate();
void AddUpdate(InputEvent event);
void FinalizeUpdate();
void FinalizeUpdate(u8 gamepad_index);
};
class BindingConnection {
public:
@@ -472,6 +484,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)
@@ -485,9 +504,79 @@ 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 = 37;
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(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_RENDERDOC),
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
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

@@ -15,6 +15,7 @@
#include "core/emulator_settings.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"
@@ -26,258 +27,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;
}
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;
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());
}
@@ -323,7 +121,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
#if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows;
@@ -348,9 +145,9 @@ 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()));
Input::GameControllers::TryOpenSDLControllers(controllers);
if (Config::getBackgroundControllerInput()) {
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
@@ -392,34 +189,32 @@ 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);
// todo handle userserviceevents here
Input::GameControllers::TryOpenSDLControllers(controllers);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
case SDL_EVENT_GAMEPAD_TOUCHPAD_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_SENSOR_UPDATE:
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
int controller_id =
Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which);
switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO:
controller->Gyro(0, event.gsensor.data);
controllers[controller_id]->Gyro(0, event.gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controller->Acceleration(0, event.gsensor.data);
controllers[controller_id]->Acceleration(0, event.gsensor.data);
break;
default:
break;
}
break;
}
case SDL_EVENT_QUIT:
is_open = false;
break;
@@ -444,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();
@@ -460,6 +255,29 @@ void WindowSDL::WaitEvent() {
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
break;
case SDL_EVENT_ADD_VIRTUAL_USER:
for (int i = 0; i < 4; i++) {
if (controllers[i]->user_id == -1) {
controllers[i]->user_id = i + 1;
Libraries::UserService::AddUserServiceEvent(
{Libraries::UserService::OrbisUserServiceEventType::Login,
(s32)controllers[i]->user_id});
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) {
Libraries::UserService::AddUserServiceEvent(
{Libraries::UserService::OrbisUserServiceEventType::Logout,
(s32)controllers[i]->user_id});
controllers[i]->user_id = -1;
break;
}
}
break;
case SDL_EVENT_RDOC_CAPTURE:
VideoCore::TriggerCapture();
break;
@@ -469,8 +287,10 @@ void WindowSDL::WaitEvent() {
}
void WindowSDL::InitTimers() {
SDL_AddTimer(100, &PollController, controller);
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
for (int i = 0; i < 4; ++i) {
SDL_AddTimer(250, &PollController, controllers[i]);
}
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
}
void WindowSDL::RequestKeyboard() {
@@ -538,10 +358,38 @@ 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[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gbutton.which)]
->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
return;
}
switch (event->type) {
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
switch ((SDL_SensorType)event->gsensor.sensor) {
case SDL_SENSOR_GYRO:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Gyro(0, event->gsensor.data);
break;
case SDL_SENSOR_ACCEL:
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
->Acceleration(0, 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[Input::GameControllers::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 2025 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{};