From ad1e58ed16d8e6bcb609a21a5db3221462f953fd Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:03:46 +0100 Subject: [PATCH] Squashed multiple controllers PR --- src/common/config.cpp | 88 +++-- src/common/config.h | 3 +- src/core/libraries/np/np_manager.cpp | 5 +- src/core/libraries/pad/pad.cpp | 101 +++--- src/core/libraries/system/userservice.cpp | 50 ++- src/core/libraries/system/userservice.h | 4 +- src/emulator.cpp | 4 +- src/emulator.h | 4 +- src/imgui/renderer/imgui_impl_sdl3.cpp | 7 +- src/input/controller.cpp | 233 +++++++++----- src/input/controller.h | 67 ++-- src/input/input_handler.cpp | 219 +++++++------ src/input/input_handler.h | 107 ++++++- src/sdl_window.cpp | 370 +++++++--------------- src/sdl_window.h | 25 +- 15 files changed, 688 insertions(+), 599 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 3831236cb..d1b75be32 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -46,7 +46,7 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if constexpr (std::is_same_v) { if (it->second.is_integer()) { - return static_cast(toml::get(it->second)); + return static_cast(toml::get(it->second)); } } else if constexpr (std::is_same_v) { if (it->second.is_integer()) { @@ -54,15 +54,19 @@ std::optional get_optional(const toml::value& v, const std::string& key) { } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { - return toml::get(it->second); + return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_string()) { - return toml::get(it->second); + return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_boolean()) { - return toml::get(it->second); + return toml::get(it->second); + } + } else if constexpr (std::is_same_v>) { + if (it->second.is_array()) { + return toml::get(it->second); } } else { static_assert([] { return false; }(), "Unsupported type in get_optional"); @@ -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 volumeSlider(100); -static ConfigEntry userName("shadPS4"); +static ConfigEntry> userNames({ + "shadPS4", + "shadps4-2", + "shadPS4-3", + "shadPS4-4", +}); // Input static ConfigEntry 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 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 )"; } diff --git a/src/common/config.h b/src/common/config.h index 8133f86cf..fa3cd5407 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -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 const getUserNames(); bool GetUseUnifiedInputConfig(); void SetUseUnifiedInputConfig(bool use); bool GetOverrideControllerColor(); diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 65acbef7f..69f8eea0e 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -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; } diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 09f404969..eb6f0ea9e 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -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::Instance(); - const auto* engine = controller->GetEngine(); + auto controllers = *Common::Singleton::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(Input::Axis::RightY)]; pData[i].analogButtons.l2 = states[i].axes[static_cast(Input::Axis::TriggerLeft)]; pData[i].analogButtons.r2 = states[i].axes[static_cast(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( @@ -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::Instance(); - const auto* engine = controller->GetEngine(); + auto controllers = *Common::Singleton::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(Input::Axis::LeftX)]; - pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; - pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; - pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; + + auto getAxisValue = [&state, &controller](Input::Axis a) { + auto i = static_cast(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(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(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::Instance(); + auto controllers = *Common::Singleton::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::Instance(); + auto* controller = Common::Singleton::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::Instance(); + auto* controller = Common::Singleton::Instance(); controller->SetVibration(pParam->smallMotor, pParam->largeMotor); return ORBIS_OK; } diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index 508b1d7e5..13f181b5f 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -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 + #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 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::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; diff --git a/src/core/libraries/system/userservice.h b/src/core/libraries/system/userservice.h index 30920e002..2a97e719c 100644 --- a/src/core/libraries/system/userservice.h +++ b/src/core/libraries/system/userservice.h @@ -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(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 3e62f6efb..21730cbec 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -259,7 +259,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, // Initialize components memory = Core::Memory::Instance(); - controller = Common::Singleton::Instance(); + controllers = Common::Singleton::Instance(); linker = Common::Singleton::Instance(); // Load renderdoc module @@ -301,7 +301,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } } window = std::make_unique( - Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title); + Config::getWindowWidth(), Config::getWindowHeight(), controllers, window_title); g_window = window.get(); diff --git a/src/emulator.h b/src/emulator.h index f4dd32c20..0db8ccb6c 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -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 window; std::chrono::steady_clock::time_point start_time; diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index fbca90efb..9be20a165 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -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::Instance(); - auto engine = controller->GetEngine(); - SDL_Gamepad* SDLGamepad = engine->m_gamepad; + auto controllers = *Common::Singleton::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) { diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 6657f4036..03740e571 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -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 #include +#include #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(axis)] = value; -} - -void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) { - touchpad[touchIndex].state = isDown; - touchpad[touchIndex].x = static_cast(x * 1920); - touchpad[touchIndex].y = static_cast(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(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(x * 1920); + state.touchpad[touchIndex].y = static_cast(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 assigned_ids; + std::array 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) { - 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 GetControllerIndexFromUserID(s32 user_id) { + if (user_id < 1 || user_id > 4) + return std::nullopt; + return static_cast(user_id - 1); +} + int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) { char GUIDbuf[33]; for (int i = 0; i < gamepadCount; i++) { diff --git a/src/input/controller.h b/src/input/controller.h index dfde521be..a843db04c 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -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 -#include #include #include +#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(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* 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(Input::Axis::AxisMax)]{0}; + int axis_smoothing_values[static_cast(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 m_engine = nullptr; + u8 player_index = -1; +}; + +class GameControllers { + std::array 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 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(); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index d38b45ddd..b3e0ca204 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -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> pressed_keys; std::list toggled_keys; static std::vector 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 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 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 { - 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(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(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::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); + } } } diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 0d95d1c4a..b63b65e92 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -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 #include #include +#include #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(type)], sdl_id); + return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id); } }; @@ -142,6 +150,8 @@ const std::map 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 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 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); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 3959588c8..268c06aa7 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -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(Axis::AxisMax); ++i) { - const auto axis = static_cast(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(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()); #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()); - 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); diff --git a/src/sdl_window.h b/src/sdl_window.h index 3a4341de5..fe18ec8e5 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -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{};