This commit is contained in:
kalaposfos13 2025-06-01 09:56:00 -03:00 committed by GitHub
commit 38b7e99a00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 558 additions and 510 deletions

View File

@ -42,7 +42,9 @@ static u32 screenHeight = 720;
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
static std::string logFilter; static std::string logFilter;
static std::string logType = "sync"; static std::string logType = "sync";
static std::string userName = "shadPS4"; static std::array<std::string, 4> userNames = {"shadPS4"
"shadps4-2",
"shadPS4-3", "shadPS4-4"};
static std::string updateChannel; static std::string updateChannel;
static std::string chooseHomeTab; static std::string chooseHomeTab;
static std::string backButtonBehavior = "left"; static std::string backButtonBehavior = "left";
@ -236,7 +238,7 @@ std::string getLogType() {
} }
std::string getUserName() { std::string getUserName() {
return userName; return userNames[0];
} }
std::string getUpdateChannel() { std::string getUpdateChannel() {
@ -486,7 +488,7 @@ void setSeparateLogFilesEnabled(bool enabled) {
} }
void setUserName(const std::string& type) { void setUserName(const std::string& type) {
userName = type; userNames[0] = type;
} }
void setUpdateChannel(const std::string& type) { void setUpdateChannel(const std::string& type) {
@ -762,7 +764,7 @@ void load(const std::filesystem::path& path) {
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true); enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
logFilter = toml::find_or<std::string>(general, "logFilter", ""); logFilter = toml::find_or<std::string>(general, "logFilter", "");
logType = toml::find_or<std::string>(general, "logType", "sync"); logType = toml::find_or<std::string>(general, "logType", "sync");
userName = toml::find_or<std::string>(general, "userName", "shadPS4"); userNames[0] = toml::find_or<std::string>(general, "userName", "shadPS4");
if (Common::g_is_release) { if (Common::g_is_release) {
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release"); updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release");
} else { } else {
@ -960,7 +962,7 @@ void save(const std::filesystem::path& path) {
data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["logFilter"] = logFilter; data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType; data["General"]["logType"] = logType;
data["General"]["userName"] = userName; data["General"]["userName"] = userNames[0];
data["General"]["updateChannel"] = updateChannel; data["General"]["updateChannel"] = updateChannel;
data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["chooseHomeTab"] = chooseHomeTab;
data["General"]["showSplash"] = isShowSplash; data["General"]["showSplash"] = isShowSplash;
@ -1107,7 +1109,7 @@ void setDefaultValues() {
screenHeight = 720; screenHeight = 720;
logFilter = ""; logFilter = "";
logType = "sync"; logType = "sync";
userName = "shadPS4"; userNames = {"shadPS4", "shadps4-2", "shadPS4-3", "shadPS4-4"};
if (Common::g_is_release) { if (Common::g_is_release) {
updateChannel = "Release"; updateChannel = "Release";
} else { } else {

View File

@ -11,8 +11,6 @@
namespace Libraries::Pad { namespace Libraries::Pad {
using Input::GameController;
int PS4_SYSV_ABI scePadClose(s32 handle) { int PS4_SYSV_ABI scePadClose(s32 handle) {
LOG_ERROR(Lib_Pad, "(STUBBED) called"); LOG_ERROR(Lib_Pad, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
@ -101,7 +99,7 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
pInfo->connectedCount = 1; pInfo->connectedCount = 1;
pInfo->connected = false; pInfo->connected = false;
pInfo->deviceClass = OrbisPadDeviceClass::Standard; pInfo->deviceClass = OrbisPadDeviceClass::Standard;
return ORBIS_OK; return 0;
} }
pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.pixelDensity = 1;
pInfo->touchPadInfo.resolution.x = 1920; 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->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass(); pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
} }
return ORBIS_OK; return 0;
} }
int PS4_SYSV_ABI scePadGetDataInternal() { 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); LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
scePadResetLightBar(1); 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, 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) if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
} }
return 1; // dummy return userId; // dummy
} }
int PS4_SYSV_ABI scePadOpenExt2() { 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) { 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; int connected_count = 0;
bool connected = false; bool connected = false;
Input::State states[64]; Input::State states[64];
auto* controller = Common::Singleton<GameController>::Instance(); auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
const auto* engine = controller->GetEngine(); int ret_num = controllers[handle - 1]->ReadStates(states, num, &connected, &connected_count);
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
if (!connected) { if (!connected) {
ret_num = 1; 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.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z; pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; Input::GameController::CalculateOrientation(pData[i].acceleration, pData[i].angularVelocity,
if (engine) { 1.0f / controllers[handle - 1]->gyro_poll_rate,
const auto gyro_poll_rate = engine->GetAccelPollRate(); pData[i].orientation);
if (gyro_poll_rate != 0.0f) {
GameController::CalculateOrientation(pData[i].acceleration,
pData[i].angularVelocity,
1.0f / gyro_poll_rate, pData[i].orientation);
}
}
pData[i].touchData.touchNum = pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); (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; 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) { int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
LOG_TRACE(Lib_Pad, "called"); LOG_TRACE(Lib_Pad, "handle: {}", handle);
if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) { if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE || handle != std::clamp(handle, 1, 4)) {
return ORBIS_PAD_ERROR_INVALID_HANDLE; return ORBIS_PAD_ERROR_INVALID_HANDLE;
} }
auto* controller = Common::Singleton<GameController>::Instance(); auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
const auto* engine = controller->GetEngine();
int connectedCount = 0; int connectedCount = 0;
bool isConnected = false; bool isConnected = false;
Input::State state; Input::State state;
controller->ReadState(&state, &isConnected, &connectedCount); controllers[handle - 1]->ReadState(&state, &isConnected, &connectedCount);
pData->buttons = state.buttonsState; pData->buttons = state.buttonsState;
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)]; pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)]; pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)];
@ -385,14 +380,9 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->angularVelocity.x = state.angularVelocity.x; pData->angularVelocity.x = state.angularVelocity.x;
pData->angularVelocity.y = state.angularVelocity.y; pData->angularVelocity.y = state.angularVelocity.y;
pData->angularVelocity.z = state.angularVelocity.z; pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; Input::GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
if (engine) { 1.0f / controllers[handle - 1]->gyro_poll_rate,
const auto gyro_poll_rate = engine->GetAccelPollRate(); pData->orientation);
if (gyro_poll_rate != 0.0f) {
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
1.0f / gyro_poll_rate, pData->orientation);
}
}
pData->touchData.touchNum = pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
pData->touchData.touch[0].x = state.touchpad[0].x; pData->touchData.touch[0].x = state.touchpad[0].x;
@ -419,9 +409,9 @@ int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
if (handle != 1) { if (handle != 1) {
return ORBIS_PAD_ERROR_INVALID_HANDLE; return ORBIS_PAD_ERROR_INVALID_HANDLE;
} }
auto* controller = Common::Singleton<GameController>::Instance(); auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
int* rgb = Config::GetControllerCustomColor(); int* rgb = Config::GetControllerCustomColor();
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]); controllers[0]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
return ORBIS_OK; return ORBIS_OK;
} }
@ -493,7 +483,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING; return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
} }
auto* controller = Common::Singleton<GameController>::Instance(); auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b); controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
return ORBIS_OK; return ORBIS_OK;
} }
@ -561,7 +551,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP
if (pParam != nullptr) { if (pParam != nullptr) {
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
pParam->smallMotor, pParam->largeMotor); pParam->smallMotor, pParam->largeMotor);
auto* controller = Common::Singleton<GameController>::Instance(); auto* controller = Common::Singleton<Input::GameController>::Instance();
controller->SetVibration(pParam->smallMotor, pParam->largeMotor); controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -1,12 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <queue>
#include "common/config.h" #include "common/config.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
#include "core/libraries/system/userservice.h" #include "core/libraries/system/userservice.h"
#include "core/libraries/system/userservice_error.h" #include "core/libraries/system/userservice_error.h"
#include "core/tls.h"
#include "input/controller.h"
namespace Libraries::UserService { namespace Libraries::UserService {
@ -105,15 +110,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
return ORBIS_OK; return ORBIS_OK;
} }
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId);
user_service_event_queue.push(e);
}
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) { s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
LOG_TRACE(Lib_UserService, "(DUMMY) called"); LOG_TRACE(Lib_UserService, "(DUMMY) called");
// fake a loggin event
static bool logged_in = false;
if (!logged_in) { if (!user_service_event_queue.empty()) {
logged_in = true; OrbisUserServiceEvent& temp = user_service_event_queue.front();
event->event = OrbisUserServiceEventType::Login; event->event = temp.event;
event->userId = 1; 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; return ORBIS_OK;
} }
@ -567,17 +580,22 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
} }
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) { s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
LOG_DEBUG(Lib_UserService, "called"); // LOG_DEBUG(Lib_UserService, "called");
if (userIdList == nullptr) { if (userIdList == nullptr) {
LOG_ERROR(Lib_UserService, "user_id is null"); LOG_ERROR(Lib_UserService, "user_id is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
} }
// TODO only first user, do the others as well // TODO only first user, do the others as well
userIdList->user_id[0] = 1; auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID; int li = 0;
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID; for (int ci = 0; ci < 4; ci++) {
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID; 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; return ORBIS_OK;
} }
@ -1048,7 +1066,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
LOG_ERROR(Lib_UserService, "color is null"); LOG_ERROR(Lib_UserService, "color is null");
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
} }
*color = OrbisUserServiceUserColor::Blue; *color = (OrbisUserServiceUserColor)(user_id - 1);
return ORBIS_OK; 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"); LOG_ERROR(Lib_UserService, "buffer is too short");
return ORBIS_USER_SERVICE_ERROR_BUFFER_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; return ORBIS_OK;
} }

View File

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

View File

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

View File

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

View File

@ -1,64 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <unordered_set>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <common/singleton.h>
#include "common/config.h" #include "common/config.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/kernel/time.h" #include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h" #include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "input/controller.h" #include "input/controller.h"
namespace Input { namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
if (isPressed) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
}
void State::OnAxis(Axis axis, int value) {
const auto toggle = [&](const auto button) {
if (value > 0) {
buttonsState |= button;
} else {
buttonsState &= ~button;
}
};
switch (axis) {
case Axis::TriggerLeft:
toggle(OrbisPadButtonDataOffset::L2);
break;
case Axis::TriggerRight:
toggle(OrbisPadButtonDataOffset::R2);
break;
default:
break;
}
axes[static_cast<int>(axis)] = value;
}
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
touchpad[touchIndex].state = isDown;
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
touchpad[touchIndex].y = static_cast<u16>(y * 941);
}
void State::OnGyro(const float gyro[3]) {
angularVelocity.x = gyro[0];
angularVelocity.y = gyro[1];
angularVelocity.z = gyro[2];
}
void State::OnAccel(const float accel[3]) {
acceleration.x = accel[0];
acceleration.y = accel[1];
acceleration.z = accel[2];
}
GameController::GameController() { GameController::GameController() {
m_states_num = 0; m_states_num = 0;
m_last_state = State(); m_last_state = State();
@ -124,22 +78,45 @@ void GameController::AddState(const State& state) {
m_states_num++; 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}; std::scoped_lock lock{m_mutex};
auto state = GetLastState(); auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime(); state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnButton(button, is_pressed); if (is_pressed) {
state.buttonsState |= button;
} else {
state.buttonsState &= ~button;
}
AddState(state); AddState(state);
} }
void GameController::Axis(int id, Input::Axis axis, int value) { void GameController::Axis(int id, Input::Axis axis, int value) {
using Libraries::Pad::OrbisPadButtonDataOffset;
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
auto state = GetLastState(); auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime(); state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnAxis(axis, value); int axis_id = static_cast<int>(axis);
state.axes[axis_id] = value;
if (axis == Input::Axis::TriggerLeft) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::L2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::L2;
}
}
if (axis == Input::Axis::TriggerRight) {
if (value > 0) {
state.buttonsState |= OrbisPadButtonDataOffset::R2;
} else {
state.buttonsState &= ~OrbisPadButtonDataOffset::R2;
}
}
AddState(state); AddState(state);
} }
@ -150,7 +127,9 @@ void GameController::Gyro(int id, const float gyro[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime(); state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the angular velocity (gyro data) // 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); AddState(state);
} }
@ -160,7 +139,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
state.time = Libraries::Kernel::sceKernelGetProcessTime(); state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Update the acceleration values // 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); AddState(state);
} }
@ -233,48 +214,123 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
} }
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (!m_engine) { if (m_sdl_gamepad != nullptr) {
return; 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) { bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
if (!m_engine) { if (m_sdl_gamepad != nullptr) {
return; return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
(largeMotor / 255.0f) * 0xFFFF, -1);
} }
std::scoped_lock _{m_mutex}; return true;
m_engine->SetVibration(smallMotor, largeMotor);
} }
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) { void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
if (touchIndex < 2) { if (touchIndex < 2) {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
auto state = GetLastState(); auto state = GetLastState();
state.time = Libraries::Kernel::sceKernelGetProcessTime(); state.time = Libraries::Kernel::sceKernelGetProcessTime();
state.OnTouchpad(touchIndex, touchDown, x, y);
state.touchpad[touchIndex].state = touchDown;
state.touchpad[touchIndex].x = static_cast<u16>(x * 1920);
state.touchpad[touchIndex].y = static_cast<u16>(y * 941);
AddState(state); AddState(state);
} }
} }
void GameController::SetEngine(std::unique_ptr<Engine> engine) { bool is_first_check = true;
std::scoped_lock _{m_mutex};
m_engine = std::move(engine); void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
if (m_engine) { using namespace Libraries::UserService;
m_engine->Init(); int controller_count;
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
std::unordered_set<SDL_JoystickID> assigned_ids;
std::array<bool, 4> slot_taken{false, false, false, false};
for (int i = 0; i < 4; i++) {
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
if (pad) {
SDL_JoystickID id = SDL_GetGamepadID(pad);
bool still_connected = false;
for (int j = 0; j < controller_count; j++) {
if (new_joysticks[j] == id) {
still_connected = true;
assigned_ids.insert(id);
slot_taken[i] = true;
break;
}
}
if (!still_connected) {
AddUserServiceEvent({OrbisUserServiceEventType::Logout, i + 1});
SDL_CloseGamepad(pad);
controllers[i]->m_sdl_gamepad = nullptr;
controllers[i]->user_id = -1;
slot_taken[i] = false;
} else {
controllers[i]->player_index = i;
}
}
}
for (int j = 0; j < controller_count; j++) {
SDL_JoystickID id = new_joysticks[j];
if (assigned_ids.contains(id))
continue;
SDL_Gamepad* pad = SDL_OpenGamepad(id);
if (!pad)
continue;
for (int i = 0; i < 4; i++) {
if (!slot_taken[i]) {
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() { u32 GameController::Poll() {
std::scoped_lock lock{m_mutex};
if (m_connected) { if (m_connected) {
std::scoped_lock lock{m_mutex};
auto time = Libraries::Kernel::sceKernelGetProcessTime(); auto time = Libraries::Kernel::sceKernelGetProcessTime();
if (m_states_num == 0) { if (m_states_num == 0) {
auto diff = (time - m_last_state.time) / 1000; auto diff = (time - m_last_state.time) / 1000;
@ -292,4 +348,10 @@ u32 GameController::Poll() {
return 100; 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 } // namespace Input

View File

@ -3,12 +3,14 @@
#pragma once #pragma once
#include <algorithm>
#include <memory>
#include <mutex> #include <mutex>
#include "SDL3/SDL_joystick.h"
#include "common/assert.h"
#include "common/types.h" #include "common/types.h"
#include "core/libraries/pad/pad.h" #include "core/libraries/pad/pad.h"
struct SDL_Gamepad;
namespace Input { namespace Input {
enum class Axis { enum class Axis {
@ -28,14 +30,7 @@ struct TouchpadEntry {
u16 y{}; u16 y{};
}; };
class State { struct 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]);
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{}; Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
u64 time = 0; u64 time = 0;
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0}; int axes[static_cast<int>(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}; 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) { 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; constexpr u32 MAX_STATES = 32;
class GameController { class GameController {
friend class GameControllers;
public: public:
GameController(); GameController();
virtual ~GameController() = default; virtual ~GameController() = default;
@ -76,12 +63,13 @@ public:
void Gyro(int id, const float gyro[3]); void Gyro(int id, const float gyro[3]);
void Acceleration(int id, const float acceleration[3]); void Acceleration(int id, const float acceleration[3]);
void SetLightBarRGB(u8 r, u8 g, u8 b); 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 SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
void SetEngine(std::unique_ptr<Engine>);
Engine* GetEngine();
u32 Poll(); 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, static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity, Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime, float deltaTime,
@ -101,7 +89,26 @@ private:
std::array<State, MAX_STATES> m_states; std::array<State, MAX_STATES> m_states;
std::array<StateInternal, MAX_STATES> m_private; std::array<StateInternal, MAX_STATES> m_private;
std::unique_ptr<Engine> m_engine = nullptr; SDL_Gamepad* m_sdl_gamepad = nullptr;
u8 player_index = -1;
};
class GameControllers {
std::array<GameController*, 4> controllers;
public:
GameControllers()
: controllers({new GameController(), new GameController(), new GameController(),
new GameController()}) {};
virtual ~GameControllers() = default;
GameController* operator[](const size_t& i) const {
if (i > 3) {
UNREACHABLE_MSG("Index out of bounds for GameControllers!");
}
return controllers[i];
}
static void TryOpenSDLControllers(GameControllers& controllers);
static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
}; };
} // namespace Input } // namespace Input

View File

@ -22,6 +22,7 @@
#include "common/elf_info.h" #include "common/elf_info.h"
#include "common/io_file.h" #include "common/io_file.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "common/singleton.h"
#include "input/controller.h" #include "input/controller.h"
#include "input/input_mouse.h" #include "input/input_mouse.h"
@ -61,42 +62,11 @@ std::list<std::pair<InputEvent, bool>> pressed_keys;
std::list<InputID> toggled_keys; std::list<InputID> toggled_keys;
static std::vector<BindingConnection> connections; static std::vector<BindingConnection> connections;
auto output_array = std::array{ std::array<ControllerAllOutputs, 4> output_arrays = {
// Important: these have to be the first, or else they will update in the wrong order ControllerAllOutputs(0),
ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerAllOutputs(1),
ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerAllOutputs(2),
ControllerOutput(KEY_TOGGLE), ControllerAllOutputs(3),
// 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),
}; };
void ControllerOutput::LinkJoystickAxes() { void ControllerOutput::LinkJoystickAxes() {
@ -197,6 +167,14 @@ InputBinding GetBindingFromString(std::string& line) {
return InputBinding(keys[0], keys[1], keys[2]); return InputBinding(keys[0], keys[1], keys[2]);
} }
std::optional<int> parseInt(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
void ParseInputConfig(const std::string game_id = "") { void ParseInputConfig(const std::string game_id = "") {
std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id; std::string config_type = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
const auto config_file = Config::GetFoolproofKbmConfigFile(config_type); 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 output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 1); 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(','); std::size_t comma_pos = input_string.find(',');
auto parseInt = [](const std::string& s) -> std::optional<int> { // todo: make remapping work for special bindings for gamepads that are not the first
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
};
if (output_string == "mouse_to_joystick") { if (output_string == "mouse_to_joystick") {
if (input_string == "left") { if (input_string == "left") {
SetMouseToJoystick(1); SetMouseToJoystick(1);
@ -286,7 +287,7 @@ void ParseInputConfig(const std::string game_id = "") {
continue; continue;
} }
ControllerOutput* toggle_out = 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( BindingConnection toggle_connection = BindingConnection(
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]); InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
connections.insert(connections.end(), toggle_connection); connections.insert(connections.end(), toggle_connection);
@ -380,34 +381,60 @@ void ParseInputConfig(const std::string game_id = "") {
continue; continue;
} }
if (button_it != string_to_cbutton_map.end()) { if (button_it != string_to_cbutton_map.end()) {
// todo add new shit here
connection = BindingConnection( connection = BindingConnection(
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); binding,
connections.insert(connections.end(), connection); &*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()) { } 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; int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
connection = BindingConnection( connection = BindingConnection(
binding, binding,
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
axis_it->second.axis, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
axis_it->second.value >= 0)), axis_it->second.axis,
axis_it->second.value >= 0)),
value_to_set); value_to_set);
connections.insert(connections.end(), connection);
} else { } else {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line); lineCount, line);
continue; 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); LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
} }
file.close(); file.close();
std::sort(connections.begin(), connections.end()); std::sort(connections.begin(), connections.end());
for (auto& c : connections) { for (auto& c : connections) {
// todo add new shit here
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString()); LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
} }
LOG_DEBUG(Input, "Done parsing the input config!"); 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) { u32 GetMouseWheelEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); 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) { InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
// todo add new shit here
u8 gamepad = 1;
switch (e.type) { switch (e.type) {
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP: case SDL_EVENT_KEY_UP:
@ -439,18 +468,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
e.type == SDL_EVENT_MOUSE_WHEEL, 0); e.type == SDL_EVENT_MOUSE_WHEEL, 0);
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP: 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: 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: default:
return InputEvent(); return InputEvent();
} }
} }
GameController* ControllerOutput::controller = nullptr; GameControllers ControllerOutput::controllers =
void ControllerOutput::SetControllerOutputController(GameController* c) { *Common::Singleton<Input::GameControllers>::Instance();
ControllerOutput::controller = c;
}
void ToggleKeyInList(InputID input) { void ToggleKeyInList(InputID input) {
if (input.type == InputType::Axis) { if (input.type == InputType::Axis) {
@ -492,7 +522,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
*new_param = (event.active ? event.axis_value : 0) + *new_param; *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; state_changed = old_button_state != new_button_state || old_param != *new_param;
if (!state_changed) { if (!state_changed) {
return; return;
@ -506,8 +536,8 @@ void ControllerOutput::FinalizeUpdate() {
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
: Config::getBackButtonBehavior() == "right" ? 0.75f : Config::getBackButtonBehavior() == "right" ? 0.75f
: 0.5f; : 0.5f;
controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); controllers[0]->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controllers[0]->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;
case LEFTJOYSTICK_HALFMODE: case LEFTJOYSTICK_HALFMODE:
leftjoystick_halfmode = new_button_state; 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 // fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed // enough that one is pressed
default: // is a normal key (hopefully) 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; break;
} }
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) { } else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
@ -550,18 +581,20 @@ void ControllerOutput::FinalizeUpdate() {
break; break;
case Axis::TriggerLeft: case Axis::TriggerLeft:
ApplyDeadzone(new_param, lefttrigger_deadzone); ApplyDeadzone(new_param, lefttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2,
*new_param > 0x20);
return; return;
case Axis::TriggerRight: case Axis::TriggerRight:
ApplyDeadzone(new_param, righttrigger_deadzone); ApplyDeadzone(new_param, righttrigger_deadzone);
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2,
*new_param > 0x20);
return; return;
default: default:
break; 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() { 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 // todo find a better solution
for (auto& it : connections) { for (int i = 0; i < 4; i++) {
it.output->AddUpdate(it.ProcessBinding());
}
// Update all outputs // Reset values and flags
for (auto& it : output_array) { for (auto& it : pressed_keys) {
it.FinalizeUpdate(); it.second = false;
}
for (auto& it : output_arrays[i].data) {
it.ResetUpdate();
}
// Iterate over all inputs, and update their respecive outputs accordingly
for (auto& it : connections) {
// only update this when it's the correct pass
if (it.output->gamepad_id == i) {
it.output->AddUpdate(it.ProcessBinding());
}
}
// Update all outputs
for (auto& it : output_arrays[i].data) {
it.FinalizeUpdate(i);
}
} }
} }

View File

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

View File

@ -61,6 +61,8 @@ A: -F12: Triggers Renderdoc capture
-F9: Pauses emultor, if the debug menu is open -F9: Pauses emultor, if the debug menu is open
-F8: Reparses the config file while in-game -F8: Reparses the config file while in-game
-F7: Toggles mouse capture and mouse input -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? 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. 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. You can make a comment line by putting # as the first character.
Whitespace doesn't matter, <output>=<input>; is just as valid as <output> = <input>; Whitespace doesn't matter, <output>=<input>; is just as valid as <output> = <input>;
';' at the ends of lines is also optional. ';' at the ends of lines is also optional.
You can bind inputs to an output that's not the first controller with:
<controller_output> : <index_of_output> = <input>
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() { QString bindings() {

View File

@ -13,6 +13,7 @@
#include "core/debug_state.h" #include "core/debug_state.h"
#include "core/libraries/kernel/time.h" #include "core/libraries/kernel/time.h"
#include "core/libraries/pad/pad.h" #include "core/libraries/pad/pad.h"
#include "core/libraries/system/userservice.h"
#include "imgui/renderer/imgui_core.h" #include "imgui/renderer/imgui_core.h"
#include "input/controller.h" #include "input/controller.h"
#include "input/input_handler.h" #include "input/input_handler.h"
@ -24,239 +25,55 @@
#include "SDL3/SDL_metal.h" #include "SDL3/SDL_metal.h"
#endif #endif
namespace Input {
using Libraries::Pad::OrbisPadButtonDataOffset;
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
using OPBDO = OrbisPadButtonDataOffset;
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return OPBDO::Down;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return OPBDO::Up;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return OPBDO::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return OPBDO::Right;
case SDL_GAMEPAD_BUTTON_SOUTH:
return OPBDO::Cross;
case SDL_GAMEPAD_BUTTON_NORTH:
return OPBDO::Triangle;
case SDL_GAMEPAD_BUTTON_WEST:
return OPBDO::Square;
case SDL_GAMEPAD_BUTTON_EAST:
return OPBDO::Circle;
case SDL_GAMEPAD_BUTTON_START:
return OPBDO::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return OPBDO::L1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return OPBDO::R1;
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
return OPBDO::L3;
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
return OPBDO::R3;
default:
return OPBDO::None;
}
}
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
switch (axis) {
case Axis::LeftX:
return SDL_GAMEPAD_AXIS_LEFTX;
case Axis::LeftY:
return SDL_GAMEPAD_AXIS_LEFTY;
case Axis::RightX:
return SDL_GAMEPAD_AXIS_RIGHTX;
case Axis::RightY:
return SDL_GAMEPAD_AXIS_RIGHTY;
case Axis::TriggerLeft:
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
case Axis::TriggerRight:
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
default:
UNREACHABLE();
}
}
SDLInputEngine::~SDLInputEngine() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
}
}
void SDLInputEngine::Init() {
if (m_gamepad) {
SDL_CloseGamepad(m_gamepad);
m_gamepad = nullptr;
}
int gamepad_count;
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
if (!gamepads) {
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
return;
}
if (gamepad_count == 0) {
LOG_INFO(Input, "No gamepad found!");
SDL_free(gamepads);
return;
}
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
m_gamepad = SDL_OpenGamepad(gamepads[0]);
if (!m_gamepad) {
LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError());
SDL_free(gamepads);
return;
}
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
Uint16 vendor = SDL_GetJoystickVendor(joystick);
Uint16 product = SDL_GetJoystickProduct(joystick);
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
if (isDualSense) {
LOG_INFO(Input, "Detected DualSense Controller");
}
if (Config::getIsMotionControlsEnabled()) {
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
}
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
} else {
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
SDL_GetError());
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
}
}
SDL_free(gamepads);
int* rgb = Config::GetControllerCustomColor();
if (isDualSense) {
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
} else {
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
}
} else {
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
}
}
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
if (m_gamepad) {
SDL_SetGamepadLED(m_gamepad, r, g, b);
}
}
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
if (m_gamepad) {
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
}
}
State SDLInputEngine::ReadState() {
State state{};
state.time = Libraries::Kernel::sceKernelGetProcessTime();
// Buttons
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
auto orbisButton = SDLGamepadToOrbisButton(i);
if (orbisButton == OrbisPadButtonDataOffset::None) {
continue;
}
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
}
// Axes
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
const auto axis = static_cast<Axis>(i);
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
switch (axis) {
case Axis::TriggerLeft:
case Axis::TriggerRight:
state.OnAxis(axis, GetAxis(0, 0x8000, value));
break;
default:
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
break;
}
}
// Touchpad
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
for (int finger = 0; finger < 2; ++finger) {
bool down;
float x, y;
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
state.OnTouchpad(finger, down, x, y);
}
}
}
// Gyro
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
float gyro[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
state.OnGyro(gyro);
}
}
// Accel
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
float accel[3];
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
state.OnAccel(accel);
}
}
return state;
}
float SDLInputEngine::GetGyroPollRate() const {
return m_gyro_poll_rate;
}
float SDLInputEngine::GetAccelPollRate() const {
return m_accel_poll_rate;
}
} // namespace Input
namespace Frontend { namespace Frontend {
using namespace Libraries::Pad; 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) { static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
auto* controller = reinterpret_cast<Input::GameController*>(userdata); auto* controller = reinterpret_cast<Input::GameController*>(userdata);
return controller->Poll(); 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) 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")) { if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError()); 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_SetWindowFullscreen(window, Config::getIsFullscreen());
SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_InitSubSystem(SDL_INIT_GAMEPAD);
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
#if defined(SDL_PLATFORM_WIN32) #if defined(SDL_PLATFORM_WIN32)
window_info.type = WindowSystemType::Windows; 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)); window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
#endif #endif
// input handler init-s // input handler init-s
Input::ControllerOutput::SetControllerOutputController(controller);
Input::ControllerOutput::LinkJoystickAxes(); Input::ControllerOutput::LinkJoystickAxes();
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
Input::GameControllers::TryOpenSDLControllers(controllers);
using namespace Libraries::UserService;
} }
WindowSDL::~WindowSDL() = default; WindowSDL::~WindowSDL() = default;
@ -365,34 +182,29 @@ void WindowSDL::WaitEvent() {
break; break;
case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED: case SDL_EVENT_GAMEPAD_REMOVED:
controller->SetEngine(std::make_unique<Input::SDLInputEngine>()); // todo handle userserviceevents here
break; Input::GameControllers::TryOpenSDLControllers(controllers);
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);
break; break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_BUTTON_UP:
case SDL_EVENT_GAMEPAD_AXIS_MOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:
OnGamepadEvent(&event); OnGamepadEvent(&event);
break; break;
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
// AND IT DOESN'T EVEN USE PROPER ENUMS int controller_id =
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which);
switch ((SDL_SensorType)event.gsensor.sensor) { switch ((SDL_SensorType)event.gsensor.sensor) {
case SDL_SENSOR_GYRO: case SDL_SENSOR_GYRO:
controller->Gyro(0, event.gsensor.data); controllers[controller_id]->Gyro(0, event.gsensor.data);
break; break;
case SDL_SENSOR_ACCEL: case SDL_SENSOR_ACCEL:
controller->Acceleration(0, event.gsensor.data); controllers[controller_id]->Acceleration(0, event.gsensor.data);
break; break;
default: default:
break; break;
} }
break; break;
}
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
is_open = false; is_open = false;
break; break;
@ -421,8 +233,8 @@ void WindowSDL::WaitEvent() {
} }
void WindowSDL::InitTimers() { void WindowSDL::InitTimers() {
SDL_AddTimer(100, &PollController, controller); SDL_AddTimer(100, &PollController, controllers[0]);
SDL_AddTimer(33, Input::MousePolling, (void*)controller); SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
} }
void WindowSDL::RequestKeyboard() { void WindowSDL::RequestKeyboard() {
@ -468,6 +280,7 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
// Handle window controls outside of the input maps // Handle window controls outside of the input maps
if (event->type == SDL_EVENT_KEY_DOWN) { if (event->type == SDL_EVENT_KEY_DOWN) {
using namespace Libraries::UserService;
u32 input_id = input_event.input.sdl_id; u32 input_id = input_event.input.sdl_id;
// Reparse kbm inputs // Reparse kbm inputs
if (input_id == SDLK_F8) { if (input_id == SDLK_F8) {
@ -493,6 +306,28 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
VideoCore::TriggerCapture(); VideoCore::TriggerCapture();
return; return;
} }
// test controller connect/disconnect
else if (input_id == SDLK_F4) {
auto controllers = *Common::Singleton<Input::GameControllers>::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<Input::GameControllers>::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 // 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 // as it would break the entire touchpad handling
// You can still bind other things to it though // You can still bind other things to it though
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) { 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; 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 // add/remove it from the list
bool inputs_changed = Input::UpdatePressedKeys(input_event); bool inputs_changed = Input::UpdatePressedKeys(input_event);

View File

@ -15,25 +15,8 @@ struct SDL_Gamepad;
union SDL_Event; union SDL_Event;
namespace Input { namespace Input {
class GameController;
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
namespace Frontend { namespace Frontend {
@ -65,7 +48,7 @@ class WindowSDL {
int keyboard_grab = 0; int keyboard_grab = 0;
public: public:
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers,
std::string_view window_title); std::string_view window_title);
~WindowSDL(); ~WindowSDL();
@ -103,7 +86,7 @@ private:
private: private:
s32 width; s32 width;
s32 height; s32 height;
Input::GameController* controller; Input::GameControllers controllers{};
WindowSystemInfo window_info{}; WindowSystemInfo window_info{};
SDL_Window* window{}; SDL_Window* window{};
bool is_shown{}; bool is_shown{};