diff --git a/src/common/config.cpp b/src/common/config.cpp index 6bccd0f37..e8bca3749 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -42,7 +42,9 @@ static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static std::string logFilter; static std::string logType = "sync"; -static std::string userName = "shadPS4"; +static std::array userNames = {"shadPS4" + "shadps4-2", + "shadPS4-3", "shadPS4-4"}; static std::string updateChannel; static std::string chooseHomeTab; static std::string backButtonBehavior = "left"; @@ -236,7 +238,7 @@ std::string getLogType() { } std::string getUserName() { - return userName; + return userNames[0]; } std::string getUpdateChannel() { @@ -486,7 +488,7 @@ void setSeparateLogFilesEnabled(bool enabled) { } void setUserName(const std::string& type) { - userName = type; + userNames[0] = type; } void setUpdateChannel(const std::string& type) { @@ -762,7 +764,7 @@ void load(const std::filesystem::path& path) { enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); - userName = toml::find_or(general, "userName", "shadPS4"); + userNames[0] = toml::find_or(general, "userName", "shadPS4"); if (Common::g_is_release) { updateChannel = toml::find_or(general, "updateChannel", "Release"); } else { @@ -960,7 +962,7 @@ void save(const std::filesystem::path& path) { data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; - data["General"]["userName"] = userName; + data["General"]["userName"] = userNames[0]; data["General"]["updateChannel"] = updateChannel; data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["showSplash"] = isShowSplash; @@ -1107,7 +1109,7 @@ void setDefaultValues() { screenHeight = 720; logFilter = ""; logType = "sync"; - userName = "shadPS4"; + userNames = {"shadPS4", "shadps4-2", "shadPS4-3", "shadPS4-4"}; if (Common::g_is_release) { updateChannel = "Release"; } else { diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 5dfc68e90..218939e6f 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -11,8 +11,6 @@ namespace Libraries::Pad { -using Input::GameController; - int PS4_SYSV_ABI scePadClose(s32 handle) { LOG_ERROR(Lib_Pad, "(STUBBED) called"); return ORBIS_OK; @@ -101,7 +99,7 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->connectedCount = 1; pInfo->connected = false; pInfo->deviceClass = OrbisPadDeviceClass::Standard; - return ORBIS_OK; + return 0; } pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; @@ -116,7 +114,7 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL; pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); } - return ORBIS_OK; + return 0; } int PS4_SYSV_ABI scePadGetDataInternal() { @@ -262,7 +260,9 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP } LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index); scePadResetLightBar(1); - return 1; // dummy + return userId; + // todo: using userId as handle works and simplifies some logic, + // but it's not supposed to be used this way } int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, @@ -275,7 +275,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } - return 1; // dummy + return userId; // dummy } int PS4_SYSV_ABI scePadOpenExt2() { @@ -289,13 +289,15 @@ 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); + if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE || handle != std::clamp(handle, 1, 4)) { + 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(); - int ret_num = controller->ReadStates(states, num, &connected, &connected_count); + auto controllers = *Common::Singleton::Instance(); + int ret_num = controllers[handle - 1]->ReadStates(states, num, &connected, &connected_count); if (!connected) { ret_num = 1; @@ -315,15 +317,9 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].angularVelocity.x = states[i].angularVelocity.x; pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; - pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - if (engine) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); - if (gyro_poll_rate != 0.0f) { - GameController::CalculateOrientation(pData[i].acceleration, - pData[i].angularVelocity, - 1.0f / gyro_poll_rate, pData[i].orientation); - } - } + Input::GameController::CalculateOrientation(pData[i].acceleration, pData[i].angularVelocity, + 1.0f / controllers[handle - 1]->gyro_poll_rate, + pData[i].orientation); pData[i].touchData.touchNum = (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); pData[i].touchData.touch[0].x = states[i].touchpad[0].x; @@ -362,16 +358,15 @@ 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); + if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE || handle != std::clamp(handle, 1, 4)) { 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; - controller->ReadState(&state, &isConnected, &connectedCount); + controllers[handle - 1]->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)]; @@ -385,14 +380,9 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { pData->angularVelocity.x = state.angularVelocity.x; pData->angularVelocity.y = state.angularVelocity.y; pData->angularVelocity.z = state.angularVelocity.z; - pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - if (engine) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); - if (gyro_poll_rate != 0.0f) { - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, - 1.0f / gyro_poll_rate, pData->orientation); - } - } + Input::GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, + 1.0f / controllers[handle - 1]->gyro_poll_rate, + pData->orientation); pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); pData->touchData.touch[0].x = state.touchpad[0].x; @@ -419,9 +409,9 @@ int PS4_SYSV_ABI scePadResetLightBar(s32 handle) { if (handle != 1) { 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[0]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]); return ORBIS_OK; } @@ -493,7 +483,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; } @@ -561,7 +551,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 b4bf189ea..df6eeee14 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-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; } @@ -1078,7 +1096,12 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::si LOG_ERROR(Lib_UserService, "buffer is too short"); return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } - snprintf(user_name, size, "%s", name.c_str()); + if (user_id == 1) { + snprintf(user_name, size, "%s", name.c_str()); + } else { + snprintf(user_name, size, "%s - %d", name.c_str(), user_id); + // todo add list of names to config + } return ORBIS_OK; } diff --git a/src/core/libraries/system/userservice.h b/src/core/libraries/system/userservice.h index 66ac2b69d..2a4de2db8 100644 --- a/src/core/libraries/system/userservice.h +++ b/src/core/libraries/system/userservice.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 2ad8446ab..3d36adbec 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -154,7 +154,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector::Instance(); + controllers = Common::Singleton::Instance(); linker = Common::Singleton::Instance(); // Load renderdoc module @@ -220,7 +220,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector( - Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); + Config::getScreenWidth(), Config::getScreenHeight(), controllers, window_title); g_window = window.get(); diff --git a/src/emulator.h b/src/emulator.h index 08c2807a1..41eafda9e 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -32,7 +32,7 @@ private: void LoadSystemModules(const std::string& game_serial); Core::MemoryManager* memory; - Input::GameController* controller; + Input::GameControllers* controllers; Core::Linker* linker; std::unique_ptr window; std::chrono::steady_clock::time_point start_time; diff --git a/src/input/controller.cpp b/src/input/controller.cpp index bb8db9a7c..eae786e44 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,64 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include +#include #include "common/config.h" #include "common/logging/log.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" +#include "core/libraries/system/userservice.h" #include "input/controller.h" 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(); @@ -124,22 +78,45 @@ void GameController::AddState(const State& state) { m_states_num++; } -void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) { +void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, + bool is_pressed) { std::scoped_lock lock{m_mutex}; auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnButton(button, is_pressed); + if (is_pressed) { + state.buttonsState |= button; + } else { + state.buttonsState &= ~button; + } AddState(state); } void GameController::Axis(int id, Input::Axis axis, int value) { + using Libraries::Pad::OrbisPadButtonDataOffset; + std::scoped_lock lock{m_mutex}; auto state = GetLastState(); state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnAxis(axis, value); + int axis_id = static_cast(axis); + state.axes[axis_id] = value; + + if (axis == Input::Axis::TriggerLeft) { + if (value > 0) { + state.buttonsState |= OrbisPadButtonDataOffset::L2; + } else { + state.buttonsState &= ~OrbisPadButtonDataOffset::L2; + } + } + + if (axis == Input::Axis::TriggerRight) { + if (value > 0) { + state.buttonsState |= OrbisPadButtonDataOffset::R2; + } else { + state.buttonsState &= ~OrbisPadButtonDataOffset::R2; + } + } AddState(state); } @@ -150,7 +127,9 @@ void GameController::Gyro(int id, const float gyro[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the angular velocity (gyro data) - state.OnGyro(gyro); + state.angularVelocity.x = gyro[0]; // X-axis + state.angularVelocity.y = gyro[1]; // Y-axis + state.angularVelocity.z = gyro[2]; // Z-axis AddState(state); } @@ -160,7 +139,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the acceleration values - state.OnAccel(acceleration); + state.acceleration.x = acceleration[0]; // X-axis + state.acceleration.y = acceleration[1]; // Y-axis + state.acceleration.z = acceleration[2]; // Z-axis AddState(state); } @@ -233,48 +214,123 @@ 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); } } -void GameController::SetEngine(std::unique_ptr engine) { - std::scoped_lock _{m_mutex}; - m_engine = std::move(engine); - if (m_engine) { - m_engine->Init(); +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]) { + controllers[i]->m_sdl_gamepad = pad; + LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i, + SDL_GetGamepadID(pad)); + controllers[i]->user_id = i + 1; + slot_taken[i] = true; + controllers[i]->player_index = i; + AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1}); + + if (SDL_SetGamepadSensorEnabled(controllers[i]->m_sdl_gamepad, SDL_SENSOR_GYRO, + true)) { + controllers[i]->gyro_poll_rate = SDL_GetGamepadSensorDataRate( + controllers[i]->m_sdl_gamepad, SDL_SENSOR_GYRO); + LOG_INFO(Input, "Gyro initialized, poll rate: {}", + controllers[i]->gyro_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}", + controllers[i]->user_id); + } + if (SDL_SetGamepadSensorEnabled(controllers[i]->m_sdl_gamepad, SDL_SENSOR_ACCEL, + true)) { + controllers[i]->accel_poll_rate = SDL_GetGamepadSensorDataRate( + controllers[i]->m_sdl_gamepad, SDL_SENSOR_ACCEL); + LOG_INFO(Input, "Accel initialized, poll rate: {}", + controllers[i]->accel_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}", + controllers[i]->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}); + } } } -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; @@ -292,4 +348,10 @@ u32 GameController::Poll() { return 100; } +u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) { + s32 index = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id)); + LOG_INFO(Input, "Gamepad index: {}", index); + return index; +} + } // namespace Input diff --git a/src/input/controller.h b/src/input/controller.h index bbaed75ea..b62c9e05f 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -3,12 +3,14 @@ #pragma once -#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 { @@ -28,14 +30,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}; @@ -45,24 +40,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; -}; - inline int GetAxis(int min, int max, int value) { - return std::clamp((255 * (value - min)) / (max - min), 0, 255); + int v = (255 * (value - min)) / (max - min); + return (v < 0 ? 0 : (v > 255 ? 255 : v)); } constexpr u32 MAX_STATES = 32; class GameController { + friend class GameControllers; + public: GameController(); virtual ~GameController() = default; @@ -76,12 +63,13 @@ 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(); + float gyro_poll_rate; + float accel_poll_rate; + u32 user_id = -1; // ORBIS_USER_SERVICE_USER_ID_INVALID static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, Libraries::Pad::OrbisFVector3& angularVelocity, float deltaTime, @@ -101,7 +89,26 @@ private: std::array m_states; std::array m_private; - std::unique_ptr m_engine = nullptr; + SDL_Gamepad* m_sdl_gamepad = 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 out of bounds for GameControllers!"); + } + return controllers[i]; + } + static void TryOpenSDLControllers(GameControllers& controllers); + static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id); }; } // namespace Input diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 3e2d66a6b..c6f8120d2 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -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,42 +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), - - // Button mappings - ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle - ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle - ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross - ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 - ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right - - // Axis mappings - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY), - - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER), - - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), +std::array output_arrays = { + ControllerAllOutputs(0), + ControllerAllOutputs(1), + ControllerAllOutputs(2), + ControllerAllOutputs(3), }; void ControllerOutput::LinkJoystickAxes() { @@ -197,6 +167,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 config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id; const auto config_file = Config::GetFoolproofKbmConfigFile(config_type); @@ -254,16 +232,39 @@ void ParseInputConfig(const std::string game_id = "") { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); + s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified + + // todo: here the inputs and outputs are formatted and split, we need to extract the + // controller ID now + + // input gamepad id is only for controllers, it's discarded otherwise + std::size_t input_colon_pos = input_string.find(':'); + if (input_colon_pos != std::string::npos) { + auto temp = parseInt(input_string.substr(input_colon_pos + 1)); + if (!temp) { + LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); + } else { + input_gamepad_id = *temp; + } + input_string = input_string.substr(0, input_colon_pos); + } + + // if not provided, assume it's for all gamepads, if the input is a controller and that also + // doesn't have an ID, and for the first otherwise + std::size_t output_colon_pos = output_string.find(':'); + if (output_colon_pos != std::string::npos) { + auto temp = parseInt(output_string.substr(output_colon_pos + 1)); + if (!temp) { + LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); + } else { + output_gamepad_id = *temp; + } + output_string = output_string.substr(0, output_colon_pos); + } + std::size_t comma_pos = input_string.find(','); - auto parseInt = [](const std::string& s) -> std::optional { - try { - return std::stoi(s); - } catch (...) { - return std::nullopt; - } - }; - + // todo: make remapping work for special bindings for gamepads that are not the first if (output_string == "mouse_to_joystick") { if (input_string == "left") { SetMouseToJoystick(1); @@ -286,7 +287,7 @@ void ParseInputConfig(const std::string game_id = "") { continue; } ControllerOutput* toggle_out = - &*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE)); + &*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE)); BindingConnection toggle_connection = BindingConnection( InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]); connections.insert(connections.end(), toggle_connection); @@ -380,34 +381,60 @@ void ParseInputConfig(const std::string game_id = "") { continue; } if (button_it != string_to_cbutton_map.end()) { + // todo add new shit here connection = BindingConnection( - binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); - connections.insert(connections.end(), connection); + binding, + &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, + ControllerOutput(button_it->second))); } else if (axis_it != string_to_axis_map.end()) { + // todo add new shit here int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( binding, - &*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, - axis_it->second.axis, - axis_it->second.value >= 0)), + &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, + axis_it->second.axis, + axis_it->second.value >= 0)), value_to_set); - connections.insert(connections.end(), connection); } else { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); continue; } + // todo make the following: if the input binding contains a controller input, and gamepad ID + // isn't specified for either inputs or output (both are -1), then multiply the binding and + // add it to all 4 controllers + if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) { + for (int i = 0; i < 4; i++) { + BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1); + copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output); + connections.push_back(copy); + } + } else { + connections.push_back(connection); + } LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount); } file.close(); std::sort(connections.begin(), connections.end()); for (auto& c : connections) { + // todo add new shit here LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString()); } LOG_DEBUG(Input, "Done parsing the input config!"); } +BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) { + BindingConnection copy = *this; + for (auto& key : copy.binding.keys) { + if (key.type == InputType::Controller || key.type == InputType::Axis) { + key.gamepad_id = gamepad; + } + } + return copy; +} + u32 GetMouseWheelEvent(const SDL_Event& event) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); @@ -426,6 +453,8 @@ u32 GetMouseWheelEvent(const SDL_Event& event) { } InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { + // todo add new shit here + u8 gamepad = 1; switch (e.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: @@ -439,18 +468,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { e.type == SDL_EVENT_MOUSE_WHEEL, 0); case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: - return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0); + gamepad = 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, (u32)e.gaxis.axis, true, e.gaxis.value / 256); + 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) { @@ -492,7 +522,7 @@ void ControllerOutput::AddUpdate(InputEvent event) { *new_param = (event.active ? event.axis_value : 0) + *new_param; } } -void ControllerOutput::FinalizeUpdate() { +void ControllerOutput::FinalizeUpdate(u8 gamepad_index) { state_changed = old_button_state != new_button_state || old_param != *new_param; if (!state_changed) { return; @@ -506,8 +536,8 @@ void ControllerOutput::FinalizeUpdate() { touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f : Config::getBackButtonBehavior() == "right" ? 0.75f : 0.5f; - controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controllers[0]->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); + controllers[0]->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: leftjoystick_halfmode = new_button_state; @@ -520,7 +550,8 @@ void ControllerOutput::FinalizeUpdate() { // fine, and a toggle doesn't have to checked against every input that's bound to it, it's // enough that one is pressed default: // is a normal key (hopefully) - controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + controllers[gamepad_index]->CheckButton(0, SDLGamepadToOrbisButton(button), + new_button_state); break; } } else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) { @@ -550,18 +581,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)); } } @@ -693,22 +726,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 0178e7937..173fb341c 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -49,21 +49,24 @@ class InputID { public: InputType type; u32 sdl_id; - InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {} + u8 gamepad_id; + InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1) + : type(d), sdl_id(i), gamepad_id(g) {} bool operator==(const InputID& o) const { - return type == o.type && sdl_id == o.sdl_id; + return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id; } bool operator!=(const InputID& o) const { - return type != o.type || sdl_id != o.sdl_id; + return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id; } bool operator<=(const InputID& o) const { return type <= o.type && sdl_id <= o.sdl_id; + return std::tie(gamepad_id, type, sdl_id) <= std::tie(o.gamepad_id, o.type, o.sdl_id); } bool IsValid() const { return *this != InputID(); } std::string ToString() { - return fmt::format("({}: {:x})", input_type_names[(u8)type], sdl_id); + return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id); } }; @@ -321,14 +324,15 @@ public: static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e); }; class ControllerOutput { - static GameController* controller; + static GameControllers controllers; public: - static void SetControllerOutputController(GameController* c); + static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {} static void LinkJoystickAxes(); u32 button; u32 axis; + u8 gamepad_id; // these are only used as s8, // but I added some padding to avoid overflow if it's activated by multiple inputs // axis_plus and axis_minus pairs share a common new_param, the other outputs have their own @@ -342,6 +346,7 @@ public: new_param = new s16(0); old_param = 0; positive_axis = p; + gamepad_id = 0; } ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) { new_param = new s16(*o.new_param); @@ -367,7 +372,7 @@ public: void ResetUpdate(); void AddUpdate(InputEvent event); - void FinalizeUpdate(); + void FinalizeUpdate(u8 gamepad_index); }; class BindingConnection { public: @@ -382,6 +387,13 @@ public: output = out; toggle = t; } + BindingConnection& operator=(const BindingConnection& o) { + binding = o.binding; + output = o.output; + axis_param = o.axis_param; + toggle = o.toggle; + return *this; + } bool operator<(const BindingConnection& other) const { // a button is a higher priority than an axis, as buttons can influence axes // (e.g. joystick_halfmode) @@ -395,9 +407,64 @@ public: } return false; } + bool HasGamepadInput() { + for (auto& key : binding.keys) { + if (key.type == InputType::Controller || key.type == InputType::Axis) { + return true; + } + } + return false; + } + BindingConnection CopyWithChangedGamepadId(u8 gamepad); InputEvent ProcessBinding(); }; +class ControllerAllOutputs { +public: + std::array data = { + // Important: these have to be the first, or else they will update in the wrong order + ControllerOutput(LEFTJOYSTICK_HALFMODE), + ControllerOutput(RIGHTJOYSTICK_HALFMODE), + ControllerOutput(KEY_TOGGLE), + + // Button mappings + ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle + ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle + ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross + ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 + ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right + + // Axis mappings + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY), + + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER), + + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), + }; + ControllerAllOutputs(u8 g) { + for (int i = 0; i < 24; i++) { + data[i].gamepad_id = g; + } + } +}; + // Updates the list of pressed keys with the given input. // Returns whether the list was updated or not. bool UpdatePressedKeys(InputEvent event); diff --git a/src/qt_gui/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 3e39d4397..9812f96bc 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -61,6 +61,8 @@ A: -F12: Triggers Renderdoc capture -F9: Pauses emultor, if the debug menu is open -F8: Reparses the config file while in-game -F7: Toggles mouse capture and mouse input +-F5: Registers a new simulated controller for the game. Required if you want to use your keyboard for multiple player inputs without actually connecting a new controller. +-F4: Unregisters the last simulated controller. For the average user, this isn't really useful, and it is also possible to disconnect even the first controller with this, so only use it if you know what you're doing. If you accidentally hit this and remove all inputs, you can add back the first controller with F5. Q: How do I change between mouse and controller joystick input, and why is it even required? A: You can switch between them with F7, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back to the center), even if other input devices would update it. @@ -100,6 +102,12 @@ axis_left_y_minus = w; You can make a comment line by putting # as the first character. Whitespace doesn't matter, =; is just as valid as = ; ';' at the ends of lines is also optional. + +You can bind inputs to an output that's not the first controller with: + : = +For example: +cross : 2 = o +binds 'o' to the second controller's cross button. See the FAQ on how to enable a second controller without actually connecting one. )"; } QString bindings() { diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index e369240c6..3f4723da9 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -13,6 +13,7 @@ #include "core/debug_state.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" +#include "core/libraries/system/userservice.h" #include "imgui/renderer/imgui_core.h" #include "input/controller.h" #include "input/input_handler.h" @@ -24,239 +25,55 @@ #include "SDL3/SDL_metal.h" #endif -namespace Input { - -using Libraries::Pad::OrbisPadButtonDataOffset; - -static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { - using OPBDO = OrbisPadButtonDataOffset; - - switch (button) { - case SDL_GAMEPAD_BUTTON_DPAD_DOWN: - return OPBDO::Down; - case SDL_GAMEPAD_BUTTON_DPAD_UP: - return OPBDO::Up; - case SDL_GAMEPAD_BUTTON_DPAD_LEFT: - return OPBDO::Left; - case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: - return OPBDO::Right; - case SDL_GAMEPAD_BUTTON_SOUTH: - return OPBDO::Cross; - case SDL_GAMEPAD_BUTTON_NORTH: - return OPBDO::Triangle; - case SDL_GAMEPAD_BUTTON_WEST: - return OPBDO::Square; - case SDL_GAMEPAD_BUTTON_EAST: - return OPBDO::Circle; - case SDL_GAMEPAD_BUTTON_START: - return OPBDO::Options; - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - return OPBDO::TouchPad; - case SDL_GAMEPAD_BUTTON_BACK: - return OPBDO::TouchPad; - case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: - return OPBDO::L1; - case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: - return OPBDO::R1; - case SDL_GAMEPAD_BUTTON_LEFT_STICK: - return OPBDO::L3; - case SDL_GAMEPAD_BUTTON_RIGHT_STICK: - return OPBDO::R3; - default: - return OPBDO::None; - } -} - -static SDL_GamepadAxis InputAxisToSDL(Axis axis) { - switch (axis) { - case Axis::LeftX: - return SDL_GAMEPAD_AXIS_LEFTX; - case Axis::LeftY: - return SDL_GAMEPAD_AXIS_LEFTY; - case Axis::RightX: - return SDL_GAMEPAD_AXIS_RIGHTX; - case Axis::RightY: - return SDL_GAMEPAD_AXIS_RIGHTY; - case Axis::TriggerLeft: - return SDL_GAMEPAD_AXIS_LEFT_TRIGGER; - case Axis::TriggerRight: - return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; - default: - UNREACHABLE(); - } -} - -SDLInputEngine::~SDLInputEngine() { - if (m_gamepad) { - SDL_CloseGamepad(m_gamepad); - } -} - -void SDLInputEngine::Init() { - if (m_gamepad) { - SDL_CloseGamepad(m_gamepad); - m_gamepad = nullptr; - } - - int gamepad_count; - SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); - if (!gamepads) { - LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); - return; - } - if (gamepad_count == 0) { - LOG_INFO(Input, "No gamepad found!"); - SDL_free(gamepads); - return; - } - - LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); - m_gamepad = SDL_OpenGamepad(gamepads[0]); - if (!m_gamepad) { - LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); - SDL_free(gamepads); - return; - } - - SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad); - Uint16 vendor = SDL_GetJoystickVendor(joystick); - Uint16 product = SDL_GetJoystickProduct(joystick); - - bool isDualSense = (vendor == 0x054C && product == 0x0CE6); - - LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product); - if (isDualSense) { - LOG_INFO(Input, "Detected DualSense Controller"); - } - - if (Config::getIsMotionControlsEnabled()) { - if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) { - m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO); - LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}", - SDL_GetError()); - SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false); - } - if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) { - m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL); - LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}", - SDL_GetError()); - SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false); - } - } - - SDL_free(gamepads); - - int* rgb = Config::GetControllerCustomColor(); - - if (isDualSense) { - if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) { - LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]); - } else { - LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError()); - } - } else { - SetLightBarRGB(rgb[0], rgb[1], rgb[2]); - } -} - -void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) { - if (m_gamepad) { - SDL_SetGamepadLED(m_gamepad, r, g, b); - } -} - -void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) { - if (m_gamepad) { - const auto low_freq = (smallMotor / 255.0f) * 0xFFFF; - const auto high_freq = (largeMotor / 255.0f) * 0xFFFF; - SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1); - } -} - -State SDLInputEngine::ReadState() { - State state{}; - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Buttons - for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { - auto orbisButton = SDLGamepadToOrbisButton(i); - if (orbisButton == OrbisPadButtonDataOffset::None) { - continue; - } - state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i)); - } - - // Axes - for (int i = 0; i < static_cast(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()); } @@ -300,7 +117,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_SetWindowFullscreen(window, Config::getIsFullscreen()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); - controller->SetEngine(std::make_unique()); #if defined(SDL_PLATFORM_WIN32) window_info.type = WindowSystemType::Windows; @@ -325,9 +141,10 @@ 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); + using namespace Libraries::UserService; } WindowSDL::~WindowSDL() = default; @@ -365,34 +182,29 @@ 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: 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; @@ -421,8 +233,8 @@ void WindowSDL::WaitEvent() { } void WindowSDL::InitTimers() { - SDL_AddTimer(100, &PollController, controller); - SDL_AddTimer(33, Input::MousePolling, (void*)controller); + SDL_AddTimer(100, &PollController, controllers[0]); + SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]); } void WindowSDL::RequestKeyboard() { @@ -468,6 +280,7 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { // Handle window controls outside of the input maps if (event->type == SDL_EVENT_KEY_DOWN) { + using namespace Libraries::UserService; u32 input_id = input_event.input.sdl_id; // Reparse kbm inputs if (input_id == SDLK_F8) { @@ -493,6 +306,28 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { VideoCore::TriggerCapture(); return; } + // test controller connect/disconnect + else if (input_id == SDLK_F4) { + auto controllers = *Common::Singleton::Instance(); + for (int i = 3; i >= 0; i--) { + if (controllers[i]->user_id != -1) { + AddUserServiceEvent( + {OrbisUserServiceEventType::Logout, (s32)controllers[i]->user_id}); + controllers[i]->user_id = -1; + break; + } + } + } else if (input_id == SDLK_F5) { + auto controllers = *Common::Singleton::Instance(); + for (int i = 0; i < 4; i++) { + if (controllers[i]->user_id == -1) { + controllers[i]->user_id = i + 1; + AddUserServiceEvent( + {OrbisUserServiceEventType::Login, (s32)controllers[i]->user_id}); + break; + } + } + } } // if it's a wheel event, make a timer that turns it off after a set time @@ -520,10 +355,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 48a9be58c..754fa4834 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -15,25 +15,8 @@ struct SDL_Gamepad; union SDL_Event; namespace Input { - -class SDLInputEngine : public Engine { -public: - ~SDLInputEngine() override; - void Init() override; - void SetLightBarRGB(u8 r, u8 g, u8 b) override; - void SetVibration(u8 smallMotor, u8 largeMotor) override; - float GetGyroPollRate() const override; - float GetAccelPollRate() const override; - State ReadState() override; - -private: - SDL_Gamepad* m_gamepad = nullptr; - - float m_gyro_poll_rate = 0.0f; - float m_accel_poll_rate = 0.0f; -}; - -} // namespace Input +class GameController; +} namespace Frontend { @@ -65,7 +48,7 @@ class WindowSDL { int keyboard_grab = 0; public: - explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, + explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers, std::string_view window_title); ~WindowSDL(); @@ -103,7 +86,7 @@ private: private: s32 width; s32 height; - Input::GameController* controller; + Input::GameControllers controllers{}; WindowSystemInfo window_info{}; SDL_Window* window{}; bool is_shown{};