mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-06-04 06:55:03 -06:00
Local multiplayer support (#4169)
* multiple controllers v2 * the c language formatter tool * review comments (the easy ones) * c++ copy semantics * correct error return for remotes without the system user id * update pad handle handling logic * controller override colour
This commit is contained in:
parent
e0a86dc8f9
commit
8a1500d7ad
@ -211,13 +211,6 @@ void IPC::InputLoop() {
|
|||||||
} else if (cmd == "RELOAD_INPUTS") {
|
} else if (cmd == "RELOAD_INPUTS") {
|
||||||
std::string config = next_str();
|
std::string config = next_str();
|
||||||
Input::ParseInputConfig(config);
|
Input::ParseInputConfig(config);
|
||||||
} else if (cmd == "SET_ACTIVE_CONTROLLER") {
|
|
||||||
std::string active_controller = next_str();
|
|
||||||
GamepadSelect::SetSelectedGamepad(active_controller);
|
|
||||||
SDL_Event checkGamepad;
|
|
||||||
SDL_memset(&checkGamepad, 0, sizeof(checkGamepad));
|
|
||||||
checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER;
|
|
||||||
SDL_PushEvent(&checkGamepad);
|
|
||||||
} else {
|
} else {
|
||||||
std::cerr << ";UNKNOWN CMD: " << cmd << std::endl;
|
std::cerr << ";UNKNOWN CMD: " << cmd << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,24 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/config.h"
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
|
#include "core/emulator_settings.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "core/libraries/pad/pad_errors.h"
|
#include "core/libraries/pad/pad_errors.h"
|
||||||
|
#include "core/user_settings.h"
|
||||||
#include "input/controller.h"
|
#include "input/controller.h"
|
||||||
#include "pad.h"
|
#include "pad.h"
|
||||||
|
|
||||||
namespace Libraries::Pad {
|
namespace Libraries::Pad {
|
||||||
|
|
||||||
using Input::GameController;
|
using Input::GameController;
|
||||||
|
using Input::GameControllers;
|
||||||
|
using namespace Libraries::UserService;
|
||||||
|
|
||||||
static bool g_initialized = false;
|
static bool g_initialized = false;
|
||||||
static bool g_opened = false;
|
static std::unordered_map<OrbisUserServiceUserId, s32> user_id_pad_handle_map{};
|
||||||
|
static constexpr s32 tv_remote_handle = 5;
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadClose(s32 handle) {
|
int PS4_SYSV_ABI scePadClose(s32 handle) {
|
||||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||||
@ -30,8 +34,8 @@ int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation(
|
|||||||
s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) {
|
s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) {
|
||||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||||
std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation));
|
std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation));
|
||||||
if (Config::getUseSpecialPad()) {
|
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||||
pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
pExtInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||||
}
|
}
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
@ -107,9 +111,9 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
pInfo->connected = true;
|
pInfo->connected = true;
|
||||||
if (Config::getUseSpecialPad()) {
|
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||||
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
|
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
|
||||||
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
pInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||||
}
|
}
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
@ -156,11 +160,16 @@ int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId
|
|||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||||
}
|
}
|
||||||
if (userId == -1 || !g_opened) {
|
if (userId == -1) {
|
||||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Lib_Pad, "(DUMMY) called");
|
auto it = user_id_pad_handle_map.find(userId);
|
||||||
return 1;
|
if (it == user_id_pad_handle_map.end()) {
|
||||||
|
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||||
|
}
|
||||||
|
s32 pad_handle = it->second;
|
||||||
|
LOG_DEBUG(Lib_Pad, "called, userid: {}, out pad handle: {}", userId, pad_handle);
|
||||||
|
return pad_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadGetIdleCount() {
|
int PS4_SYSV_ABI scePadGetIdleCount() {
|
||||||
@ -168,8 +177,19 @@ int PS4_SYSV_ABI scePadGetIdleCount() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadGetInfo() {
|
int PS4_SYSV_ABI scePadGetInfo(u32* data) {
|
||||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
LOG_WARNING(Lib_Pad, "(DUMMY) called");
|
||||||
|
if (!data) {
|
||||||
|
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
data[0] = 0x1; // index but starting from one?
|
||||||
|
data[1] = 0x0; // index?
|
||||||
|
data[2] = 1; // pad handle
|
||||||
|
data[3] = 0x0101; // ???
|
||||||
|
data[4] = 0x0; // ?
|
||||||
|
data[5] = 0x0; // ?
|
||||||
|
data[6] = 0x00ff0000; // colour(?)
|
||||||
|
data[7] = 0x0; // ?
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,34 +274,61 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI
|
|||||||
if (!g_initialized) {
|
if (!g_initialized) {
|
||||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||||
}
|
}
|
||||||
if (userId == -1) {
|
if (userId < 0) {
|
||||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||||
}
|
}
|
||||||
if (Config::getUseSpecialPad()) {
|
if (userId == ORBIS_USER_SERVICE_USER_ID_SYSTEM) {
|
||||||
|
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||||
|
LOG_INFO(Lib_Pad, "Opened a TV remote device");
|
||||||
|
user_id_pad_handle_map[ORBIS_USER_SERVICE_USER_ID_SYSTEM] = tv_remote_handle;
|
||||||
|
return tv_remote_handle;
|
||||||
|
}
|
||||||
|
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||||
|
}
|
||||||
|
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||||
|
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
} else {
|
} else {
|
||||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
|
||||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
|
auto u = UserManagement.GetUserByID(userId);
|
||||||
g_opened = true;
|
if (!u) {
|
||||||
scePadResetLightBar(userId);
|
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||||
scePadResetOrientation(userId);
|
}
|
||||||
return 1; // dummy
|
s32 pad_handle = u->player_index;
|
||||||
|
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||||
|
index, pad_handle);
|
||||||
|
scePadResetLightBar(pad_handle);
|
||||||
|
scePadResetOrientation(pad_handle);
|
||||||
|
user_id_pad_handle_map[userId] = pad_handle;
|
||||||
|
return pad_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||||
s32 index, const OrbisPadOpenExtParam* pParam) {
|
s32 index, const OrbisPadOpenExtParam* pParam) {
|
||||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||||
if (Config::getUseSpecialPad()) {
|
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
} else {
|
} else {
|
||||||
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
|
auto u = UserManagement.GetUserByID(userId);
|
||||||
|
if (!u) {
|
||||||
|
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||||
|
}
|
||||||
|
s32 pad_handle = u->player_index;
|
||||||
|
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||||
|
index, pad_handle);
|
||||||
|
scePadResetLightBar(pad_handle);
|
||||||
|
scePadResetOrientation(pad_handle);
|
||||||
|
user_id_pad_handle_map[userId] = pad_handle;
|
||||||
|
return pad_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadOpenExt2() {
|
int PS4_SYSV_ABI scePadOpenExt2() {
|
||||||
@ -294,8 +341,8 @@ int PS4_SYSV_ABI scePadOutputReport() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected,
|
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller,
|
||||||
u32 connected_count) {
|
Input::State* states, s32 num, bool connected, u32 connected_count) {
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
pData[0] = {};
|
pData[0] = {};
|
||||||
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
@ -319,61 +366,57 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
|||||||
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
||||||
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
|
||||||
auto* controller = Common::Singleton<GameController>::Instance();
|
const auto gyro_poll_rate = controller.accel_poll_rate;
|
||||||
const auto* engine = controller->GetEngine();
|
if (gyro_poll_rate != 0.0f) {
|
||||||
if (engine && handle == 1) {
|
auto now = std::chrono::steady_clock::now();
|
||||||
const auto gyro_poll_rate = engine->GetAccelPollRate();
|
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
if (gyro_poll_rate != 0.0f) {
|
now - controller.GetLastUpdate())
|
||||||
auto now = std::chrono::steady_clock::now();
|
.count() /
|
||||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
1000000.0f;
|
||||||
now - controller->GetLastUpdate())
|
controller.SetLastUpdate(now);
|
||||||
.count() /
|
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
|
||||||
1000000.0f;
|
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
controller->SetLastUpdate(now);
|
GameControllers::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
||||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
deltaTime, lastOrientation, outputOrientation);
|
||||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
pData[i].orientation = outputOrientation;
|
||||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
controller.SetLastOrientation(outputOrientation);
|
||||||
deltaTime, lastOrientation, outputOrientation);
|
|
||||||
pData[i].orientation = outputOrientation;
|
|
||||||
controller->SetLastOrientation(outputOrientation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
|
||||||
if (handle == 1) {
|
if (handle == 1) {
|
||||||
if (controller->GetTouchCount() >= 127) {
|
if (controller.GetTouchCount() >= 127) {
|
||||||
controller->SetTouchCount(0);
|
controller.SetTouchCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller->GetSecondaryTouchCount() >= 127) {
|
if (controller.GetSecondaryTouchCount() >= 127) {
|
||||||
controller->SetSecondaryTouchCount(0);
|
controller.SetSecondaryTouchCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
|
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
|
||||||
controller->SetTouchCount(controller->GetTouchCount() + 1);
|
controller.SetTouchCount(controller.GetTouchCount() + 1);
|
||||||
controller->SetSecondaryTouchCount(controller->GetTouchCount());
|
controller.SetSecondaryTouchCount(controller.GetTouchCount());
|
||||||
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
|
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
|
||||||
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
|
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
|
||||||
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
|
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
|
||||||
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
|
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
|
||||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||||
} else {
|
} else {
|
||||||
if (controller->WasSecondaryTouchReset()) {
|
if (controller.WasSecondaryTouchReset()) {
|
||||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||||
controller->UnsetSecondaryTouchResetBool();
|
controller.UnsetSecondaryTouchResetBool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controller->SetPreviousTouchNum(pData->touchData.touchNum);
|
controller.SetPreviousTouchNum(pData->touchData.touchNum);
|
||||||
|
|
||||||
if (pData->touchData.touchNum == 1) {
|
if (pData->touchData.touchNum == 1) {
|
||||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||||
states[i].touchpad[1].ID = 0;
|
states[i].touchpad[1].ID = 0;
|
||||||
} else if (pData->touchData.touchNum == 2) {
|
} else if (pData->touchData.touchNum == 2) {
|
||||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||||
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
|
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
states[i].touchpad[0].ID = 1;
|
states[i].touchpad[0].ID = 1;
|
||||||
@ -397,16 +440,18 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
|||||||
|
|
||||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||||
LOG_TRACE(Lib_Pad, "called");
|
LOG_TRACE(Lib_Pad, "called");
|
||||||
if (handle < 1) {
|
|
||||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
|
||||||
}
|
|
||||||
int connected_count = 0;
|
int connected_count = 0;
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
std::vector<Input::State> states(64);
|
std::vector<Input::State> states(64);
|
||||||
auto* controller = Common::Singleton<GameController>::Instance();
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
const auto* engine = controller->GetEngine();
|
if (!controller_id) {
|
||||||
int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count);
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count);
|
}
|
||||||
|
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
|
auto& controller = *controllers[*controller_id];
|
||||||
|
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
|
||||||
|
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected,
|
||||||
|
connected_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
|
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
|
||||||
@ -430,17 +475,18 @@ 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 < 1) {
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
auto* controller = Common::Singleton<GameController>::Instance();
|
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
const auto* engine = controller->GetEngine();
|
auto& controller = *controllers[*controller_id];
|
||||||
int connected_count = 0;
|
int connected_count = 0;
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
Input::State state;
|
Input::State state;
|
||||||
controller->ReadState(&state, &connected, &connected_count);
|
controller.ReadState(&state, &connected, &connected_count);
|
||||||
ProcessStates(handle, pData, &state, 1, connected, connected_count);
|
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,13 +496,30 @@ int PS4_SYSV_ABI scePadReadStateExt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
|
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
|
||||||
LOG_INFO(Lib_Pad, "(DUMMY) called");
|
LOG_DEBUG(Lib_Pad, "called, handle: {}", handle);
|
||||||
if (handle != 1) {
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
auto* controller = Common::Singleton<GameController>::Instance();
|
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
int* rgb = Config::GetControllerCustomColor();
|
s32 colour_index = UserManagement.GetUserByPlayerIndex(handle)->user_color - 1;
|
||||||
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
Input::Colour colour{255, 0, 0};
|
||||||
|
if (colour_index >= 0 && colour_index <= 3) {
|
||||||
|
static constexpr Input::Colour colours[4]{
|
||||||
|
{0, 0, 255}, // blue
|
||||||
|
{255, 0, 0}, // red
|
||||||
|
{0, 255, 0}, // green
|
||||||
|
{255, 0, 255}, // pink
|
||||||
|
};
|
||||||
|
colour = colours[colour_index];
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Lib_Pad, "Invalid user colour value {} for controller {}, falling back to blue",
|
||||||
|
colour_index, handle);
|
||||||
|
}
|
||||||
|
if (auto oc = GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||||
|
colour = *oc;
|
||||||
|
}
|
||||||
|
controllers[*controller_id]->SetLightBarRGB(colour.r, colour.g, colour.b);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,14 +536,15 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
|
|||||||
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
|
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
|
||||||
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
|
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
|
||||||
|
|
||||||
if (handle != 1) {
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* controller = Common::Singleton<GameController>::Instance();
|
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
controller->SetLastOrientation(defaultOrientation);
|
controllers[*controller_id]->SetLastOrientation(defaultOrientation);
|
||||||
controller->SetLastUpdate(std::chrono::steady_clock::now());
|
controllers[*controller_id]->SetLastUpdate(std::chrono::steady_clock::now());
|
||||||
|
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
@ -526,7 +590,11 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
|
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||||
if (Config::GetOverrideControllerColor()) {
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
if (GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
if (pParam != nullptr) {
|
if (pParam != nullptr) {
|
||||||
@ -538,8 +606,8 @@ 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& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||||
@ -555,8 +623,14 @@ int PS4_SYSV_ABI scePadSetLightBarBlinking() {
|
|||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadSetLightBarForTracker() {
|
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
LOG_INFO(Lib_Pad, "called, r: {} g: {} b: {}", pParam->r, pParam->g, pParam->b);
|
||||||
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
|
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,11 +677,15 @@ int PS4_SYSV_ABI scePadSetUserColor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
|
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
|
||||||
|
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||||
|
if (!controller_id) {
|
||||||
|
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||||
|
}
|
||||||
if (pParam != nullptr) {
|
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& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||||
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
controllers[*controller_id]->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||||
|
|||||||
@ -280,7 +280,7 @@ int PS4_SYSV_ABI scePadGetFeatureReport();
|
|||||||
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||||
s32 index);
|
s32 index);
|
||||||
int PS4_SYSV_ABI scePadGetIdleCount();
|
int PS4_SYSV_ABI scePadGetIdleCount();
|
||||||
int PS4_SYSV_ABI scePadGetInfo();
|
int PS4_SYSV_ABI scePadGetInfo(u32* data);
|
||||||
int PS4_SYSV_ABI scePadGetInfoByPortType();
|
int PS4_SYSV_ABI scePadGetInfoByPortType();
|
||||||
int PS4_SYSV_ABI scePadGetLicenseControllerInformation();
|
int PS4_SYSV_ABI scePadGetLicenseControllerInformation();
|
||||||
int PS4_SYSV_ABI scePadGetMotionSensorPosition();
|
int PS4_SYSV_ABI scePadGetMotionSensorPosition();
|
||||||
@ -324,7 +324,7 @@ int PS4_SYSV_ABI scePadSetForceIntercepted();
|
|||||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam);
|
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||||
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness();
|
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness();
|
||||||
int PS4_SYSV_ABI scePadSetLightBarBlinking();
|
int PS4_SYSV_ABI scePadSetLightBarBlinking();
|
||||||
int PS4_SYSV_ABI scePadSetLightBarForTracker();
|
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||||
int PS4_SYSV_ABI scePadSetLoginUserNumber();
|
int PS4_SYSV_ABI scePadSetLoginUserNumber();
|
||||||
int PS4_SYSV_ABI scePadSetMotionSensorState(s32 handle, bool bEnable);
|
int PS4_SYSV_ABI scePadSetMotionSensorState(s32 handle, bool bEnable);
|
||||||
int PS4_SYSV_ABI scePadSetProcessFocus();
|
int PS4_SYSV_ABI scePadSetProcessFocus();
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/config.h"
|
#include <queue>
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
#include <core/user_settings.h>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include "common/singleton.h"
|
||||||
|
#include "core/emulator_settings.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 {
|
||||||
|
|
||||||
@ -114,14 +120,15 @@ void AddUserServiceEvent(const OrbisUserServiceEvent 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, "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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,8 +511,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
// select first user (TODO add more)
|
*user_id = UserManagement.GetDefaultUser().user_id;
|
||||||
*user_id = 1;
|
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,20 +581,29 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
|
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
|
||||||
LOG_DEBUG(Lib_UserService, "called");
|
|
||||||
if (userIdList == nullptr) {
|
if (userIdList == nullptr) {
|
||||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
LOG_ERROR(Lib_UserService, "userIdList is null");
|
||||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
// TODO only first user, do the others as well
|
|
||||||
userIdList->user_id[0] = 1;
|
|
||||||
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
|
||||||
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
|
||||||
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
|
||||||
|
|
||||||
|
// Initialize all slots to invalid (-1)
|
||||||
|
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||||
|
userIdList->user_id[i] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& user_manager = UserManagement;
|
||||||
|
|
||||||
|
auto logged_in_users = user_manager.GetLoggedInUsers();
|
||||||
|
|
||||||
|
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||||
|
s32 id =
|
||||||
|
logged_in_users[i] ? logged_in_users[i]->user_id : ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||||
|
userIdList->user_id[i] = id;
|
||||||
|
LOG_DEBUG(Lib_UserService, "Slot {}: User ID {} (port {})", i, id,
|
||||||
|
logged_in_users[i] ? logged_in_users[i]->player_index : -1);
|
||||||
|
}
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceUserServiceGetMicLevel() {
|
int PS4_SYSV_ABI sceUserServiceGetMicLevel() {
|
||||||
LOG_ERROR(Lib_UserService, "(STUBBED) called");
|
LOG_ERROR(Lib_UserService, "(STUBBED) called");
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
@ -1056,7 +1071,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)UserManagement.GetUserByID(user_id)->user_color;
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1076,12 +1091,18 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
|
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
|
||||||
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
|
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
|
||||||
if (user_name == nullptr) {
|
if (user_name == nullptr) {
|
||||||
LOG_ERROR(Lib_UserService, "user_name is null");
|
LOG_ERROR(Lib_UserService, "user_name is null");
|
||||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
std::string name = Config::getUserName();
|
std::string name = "shadPS4";
|
||||||
|
auto const* u = UserManagement.GetUserByID(user_id);
|
||||||
|
if (u != nullptr) {
|
||||||
|
name = u->user_name;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Lib_UserService, "No user found");
|
||||||
|
}
|
||||||
if (size < name.length()) {
|
if (size < name.length()) {
|
||||||
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;
|
||||||
|
|||||||
@ -289,7 +289,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
|||||||
|
|
||||||
// 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
|
||||||
@ -331,7 +331,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
window = std::make_unique<Frontend::WindowSDL>(EmulatorSettings.GetWindowWidth(),
|
window = std::make_unique<Frontend::WindowSDL>(EmulatorSettings.GetWindowWidth(),
|
||||||
EmulatorSettings.GetWindowHeight(), controller,
|
EmulatorSettings.GetWindowHeight(), controllers,
|
||||||
window_title);
|
window_title);
|
||||||
|
|
||||||
g_window = window.get();
|
g_window = window.get();
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@ -43,7 +43,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;
|
||||||
|
|||||||
@ -737,9 +737,8 @@ static void UpdateGamepads() {
|
|||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
SdlData* bd = GetBackendData();
|
SdlData* bd = GetBackendData();
|
||||||
|
|
||||||
auto controller = Common::Singleton<Input::GameController>::Instance();
|
auto& controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||||
auto engine = controller->GetEngine();
|
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
|
||||||
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
|
|
||||||
// Update list of gamepads to use
|
// Update list of gamepads to use
|
||||||
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
|
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
|
||||||
if (SDLGamepad) {
|
if (SDLGamepad) {
|
||||||
|
|||||||
@ -1,15 +1,20 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include "common/config.h"
|
#include <common/elf_info.h>
|
||||||
|
#include <common/singleton.h>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "controller.h"
|
||||||
|
#include "core/emulator_settings.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 "core/user_settings.h"
|
||||||
#include "input/controller.h"
|
#include "input/controller.h"
|
||||||
|
|
||||||
static std::string SelectedGamepad = "";
|
|
||||||
|
|
||||||
namespace Input {
|
namespace Input {
|
||||||
|
|
||||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||||
@ -22,7 +27,15 @@ void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::OnAxis(Axis axis, int value) {
|
void State::OnAxis(Axis axis, int value, bool smooth) {
|
||||||
|
auto const i = std::to_underlying(axis);
|
||||||
|
// forcibly finish the previous smoothing task by jumping to the end
|
||||||
|
axes[i] = axis_smoothing_end_values[i];
|
||||||
|
|
||||||
|
axis_smoothing_start_times[i] = time;
|
||||||
|
axis_smoothing_start_values[i] = axes[i];
|
||||||
|
axis_smoothing_end_values[i] = value;
|
||||||
|
axis_smoothing_flags[i] = smooth;
|
||||||
const auto toggle = [&](const auto button) {
|
const auto toggle = [&](const auto button) {
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
buttonsState |= button;
|
buttonsState |= button;
|
||||||
@ -40,7 +53,6 @@ void State::OnAxis(Axis axis, int value) {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
axes[static_cast<int>(axis)] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
|
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
|
||||||
@ -61,6 +73,22 @@ void State::OnAccel(const float accel[3]) {
|
|||||||
acceleration.z = accel[2];
|
acceleration.z = accel[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void State::UpdateAxisSmoothing() {
|
||||||
|
for (int i = 0; i < std::to_underlying(Axis::AxisMax); i++) {
|
||||||
|
// if it's not to be smoothed or close enough, just jump to the end
|
||||||
|
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
|
||||||
|
if (axes[i] != axis_smoothing_end_values[i]) {
|
||||||
|
axes[i] = axis_smoothing_end_values[i];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto now = Libraries::Kernel::sceKernelGetProcessTime();
|
||||||
|
f32 t =
|
||||||
|
std::clamp((now - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
|
||||||
|
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GameController::GameController() : m_states_queue(64) {}
|
GameController::GameController() : m_states_queue(64) {}
|
||||||
|
|
||||||
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
|
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
|
||||||
@ -88,31 +116,82 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
|
|||||||
return ret_num;
|
return ret_num;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
|
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||||
m_state.OnButton(button, is_pressed);
|
m_state.OnButton(button, is_pressed);
|
||||||
PushState();
|
PushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::Axis(int id, Input::Axis axis, int value) {
|
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
|
||||||
m_state.OnAxis(axis, value);
|
m_state.OnAxis(axis, value, smooth);
|
||||||
PushState();
|
PushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::Gyro(int id, const float gyro[3]) {
|
void GameController::Gyro(int id) {
|
||||||
m_state.OnGyro(gyro);
|
m_state.OnGyro(gyro_buf);
|
||||||
PushState();
|
PushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::Acceleration(int id, const float acceleration[3]) {
|
void GameController::Acceleration(int id) {
|
||||||
m_state.OnAccel(acceleration);
|
m_state.OnAccel(accel_buf);
|
||||||
PushState();
|
PushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
void GameController::UpdateGyro(const float gyro[3]) {
|
||||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
std::scoped_lock l(m_states_queue_mutex);
|
||||||
float deltaTime,
|
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
|
||||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
}
|
||||||
Libraries::Pad::OrbisFQuaternion& orientation) {
|
|
||||||
|
void GameController::UpdateAcceleration(const float acceleration[3]) {
|
||||||
|
std::scoped_lock l(m_states_queue_mutex);
|
||||||
|
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameController::UpdateAxisSmoothing() {
|
||||||
|
m_state.UpdateAxisSmoothing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||||
|
colour = {r, g, b};
|
||||||
|
if (m_sdl_gamepad != nullptr) {
|
||||||
|
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameController::PollLightColour() {
|
||||||
|
if (m_sdl_gamepad != nullptr) {
|
||||||
|
SDL_SetGamepadLED(m_sdl_gamepad, colour.r, colour.g, colour.b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||||
|
if (m_sdl_gamepad != nullptr) {
|
||||||
|
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
|
||||||
|
(largeMotor / 255.0f) * 0xFFFF, -1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||||
|
if (touchIndex < 2) {
|
||||||
|
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||||
|
PushState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::optional<Colour>, 4> GameControllers::controller_override_colors{
|
||||||
|
std::nullopt, std::nullopt, std::nullopt, std::nullopt};
|
||||||
|
|
||||||
|
void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||||
|
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||||
|
float deltaTime,
|
||||||
|
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||||
|
Libraries::Pad::OrbisFQuaternion& orientation) {
|
||||||
|
// avoid wildly off values coming from elapsed time between two samples
|
||||||
|
// being too high, such as on the first time the controller is polled
|
||||||
|
if (deltaTime > 1.0f) {
|
||||||
|
orientation = lastOrientation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
|
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
|
||||||
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
|
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
|
||||||
0.0f};
|
0.0f};
|
||||||
@ -143,27 +222,100 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
|
|||||||
orientation.y, orientation.z, orientation.w);
|
orientation.y, orientation.z, orientation.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
bool is_first_check = true;
|
||||||
if (!m_engine) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_engine->SetLightBarRGB(r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
void GameControllers::TryOpenSDLControllers() {
|
||||||
if (!m_engine) {
|
using namespace Libraries::UserService;
|
||||||
return;
|
int controller_count;
|
||||||
}
|
s32 move_count = 0;
|
||||||
m_engine->SetVibration(smallMotor, largeMotor);
|
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
|
||||||
}
|
LOG_INFO(Input, "{} controllers are currently connected", controller_count);
|
||||||
|
|
||||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
std::unordered_set<SDL_JoystickID> assigned_ids;
|
||||||
if (touchIndex < 2) {
|
std::array<bool, 4> slot_taken{false, false, false, false};
|
||||||
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
|
||||||
PushState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
|
||||||
|
if (pad) {
|
||||||
|
SDL_JoystickID id = SDL_GetGamepadID(pad);
|
||||||
|
bool still_connected = false;
|
||||||
|
ControllerType type = ControllerType::Standard;
|
||||||
|
for (int j = 0; j < controller_count; j++) {
|
||||||
|
if (new_joysticks[j] == id) {
|
||||||
|
still_connected = true;
|
||||||
|
assigned_ids.insert(id);
|
||||||
|
slot_taken[i] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!still_connected) {
|
||||||
|
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
|
||||||
|
UserManagement.LogoutUser(u);
|
||||||
|
SDL_CloseGamepad(pad);
|
||||||
|
controllers[i]->m_sdl_gamepad = nullptr;
|
||||||
|
controllers[i]->user_id = -1;
|
||||||
|
slot_taken[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < controller_count; j++) {
|
||||||
|
SDL_JoystickID id = new_joysticks[j];
|
||||||
|
if (assigned_ids.contains(id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SDL_Gamepad* pad = SDL_OpenGamepad(id);
|
||||||
|
if (!pad) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (!slot_taken[i]) {
|
||||||
|
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
||||||
|
if (!u) {
|
||||||
|
LOG_INFO(Input, "User {} not found", i + 1);
|
||||||
|
continue; // for now, if you don't specify who Player N is in the config,
|
||||||
|
// Player N won't be registered at all
|
||||||
|
}
|
||||||
|
auto* c = controllers[i];
|
||||||
|
c->m_sdl_gamepad = pad;
|
||||||
|
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
|
||||||
|
SDL_GetGamepadID(pad));
|
||||||
|
c->user_id = u->user_id;
|
||||||
|
slot_taken[i] = true;
|
||||||
|
UserManagement.LoginUser(u, i + 1);
|
||||||
|
if (EmulatorSettings.IsMotionControlsEnabled()) {
|
||||||
|
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||||
|
c->gyro_poll_rate =
|
||||||
|
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
|
||||||
|
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
|
||||||
|
c->user_id);
|
||||||
|
}
|
||||||
|
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||||
|
c->accel_poll_rate =
|
||||||
|
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
|
||||||
|
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
|
||||||
|
c->user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_first_check) [[unlikely]] {
|
||||||
|
is_first_check = false;
|
||||||
|
if (controller_count - move_count == 0) {
|
||||||
|
auto u = UserManagement.GetUserByPlayerIndex(1);
|
||||||
|
controllers[0]->user_id = u->user_id;
|
||||||
|
UserManagement.LoginUser(u, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_free(new_joysticks);
|
||||||
|
}
|
||||||
u8 GameController::GetTouchCount() {
|
u8 GameController::GetTouchCount() {
|
||||||
return m_touch_count;
|
return m_touch_count;
|
||||||
}
|
}
|
||||||
@ -215,73 +367,37 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
|
|||||||
m_last_update = lastUpdate;
|
m_last_update = lastUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
|
|
||||||
m_engine = std::move(engine);
|
|
||||||
if (m_engine) {
|
|
||||||
m_engine->Init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Engine* GameController::GetEngine() {
|
|
||||||
return m_engine.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameController::PushState() {
|
void GameController::PushState() {
|
||||||
std::lock_guard lg(m_states_queue_mutex);
|
std::lock_guard lg(m_states_queue_mutex);
|
||||||
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||||
m_states_queue.Push(m_state);
|
m_states_queue.Push(m_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GameController::Poll() {
|
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
|
||||||
if (m_connected) {
|
auto g = SDL_GetGamepadFromID(id);
|
||||||
PushState();
|
ASSERT(g != nullptr);
|
||||||
}
|
for (int i = 0; i < 5; i++) {
|
||||||
return 33;
|
if (controllers[i]->m_sdl_gamepad == g) {
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Input
|
|
||||||
|
|
||||||
namespace GamepadSelect {
|
|
||||||
|
|
||||||
int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
|
|
||||||
char GUIDbuf[33];
|
|
||||||
if (Config::getDefaultControllerID() != "") {
|
|
||||||
for (int i = 0; i < gamepadCount; i++) {
|
|
||||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
|
||||||
std::string currentGUID = std::string(GUIDbuf);
|
|
||||||
if (currentGUID == Config::getDefaultControllerID()) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
|
|
||||||
char GUIDbuf[33];
|
|
||||||
for (int i = 0; i < gamepadCount; i++) {
|
|
||||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
|
||||||
std::string currentGUID = std::string(GUIDbuf);
|
|
||||||
if (currentGUID == GUID) {
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LOG_TRACE(Input, "Gamepad index: {}", index);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index) {
|
std::optional<u8> GameControllers::GetControllerIndexFromUserID(s32 user_id) {
|
||||||
char GUIDbuf[33];
|
auto const u = UserManagement.GetUserByID(user_id);
|
||||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[index]), GUIDbuf, 33);
|
if (!u) {
|
||||||
std::string GUID = std::string(GUIDbuf);
|
return std::nullopt;
|
||||||
return GUID;
|
}
|
||||||
|
return u->player_index - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetSelectedGamepad() {
|
std::optional<u8> GameControllers::GetControllerIndexFromControllerID(s32 controller_id) {
|
||||||
return SelectedGamepad;
|
if (controller_id < 1 || controller_id > 5) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return controller_id - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetSelectedGamepad(std::string GUID) {
|
} // namespace Input
|
||||||
SelectedGamepad = GUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace GamepadSelect
|
|
||||||
|
|||||||
@ -3,18 +3,25 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <SDL3/SDL_gamepad.h>
|
#include <SDL3/SDL_gamepad.h>
|
||||||
|
#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"
|
||||||
|
#include "core/libraries/system/userservice.h"
|
||||||
|
|
||||||
|
struct SDL_Gamepad;
|
||||||
|
|
||||||
namespace Input {
|
namespace Input {
|
||||||
|
|
||||||
|
enum class ControllerType {
|
||||||
|
Standard,
|
||||||
|
};
|
||||||
|
|
||||||
enum class Axis {
|
enum class Axis {
|
||||||
LeftX = 0,
|
LeftX = 0,
|
||||||
LeftY = 1,
|
LeftY = 1,
|
||||||
@ -33,37 +40,41 @@ struct TouchpadEntry {
|
|||||||
u16 y{};
|
u16 y{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class State {
|
struct Colour {
|
||||||
|
u8 r, g, b;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
using AxisArray = std::array<T, std::to_underlying(Axis::AxisMax)>;
|
||||||
|
static constexpr AxisArray<s32> axis_defaults{128, 128, 128, 128, 0, 0};
|
||||||
|
static constexpr u64 axis_smoothing_time{33000};
|
||||||
|
AxisArray<bool> axis_smoothing_flags{true};
|
||||||
|
AxisArray<u64> axis_smoothing_start_times{0};
|
||||||
|
AxisArray<int> axis_smoothing_start_values{axis_defaults};
|
||||||
|
AxisArray<int> axis_smoothing_end_values{axis_defaults};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
|
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
|
||||||
void OnAxis(Axis, int);
|
void OnAxis(Axis, int, bool smooth = true);
|
||||||
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
|
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
|
||||||
void OnGyro(const float[3]);
|
void OnGyro(const float[3]);
|
||||||
void OnAccel(const float[3]);
|
void OnAccel(const float[3]);
|
||||||
|
void UpdateAxisSmoothing();
|
||||||
|
|
||||||
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};
|
AxisArray<s32> axes{axis_defaults};
|
||||||
TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}};
|
TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}};
|
||||||
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, 0.0f, 0.0f};
|
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, -9.81f, 0.0f};
|
||||||
Libraries::Pad::OrbisFVector3 angularVelocity = {0.0f, 0.0f, 0.0f};
|
Libraries::Pad::OrbisFVector3 angularVelocity = {0.0f, 0.0f, 0.0f};
|
||||||
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;
|
|
||||||
SDL_Gamepad* m_gamepad;
|
|
||||||
};
|
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -98,6 +109,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class GameController {
|
class GameController {
|
||||||
|
friend class GameControllers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GameController();
|
GameController();
|
||||||
virtual ~GameController() = default;
|
virtual ~GameController() = default;
|
||||||
@ -105,16 +118,17 @@ public:
|
|||||||
void ReadState(State* state, bool* isConnected, int* connectedCount);
|
void ReadState(State* state, bool* isConnected, int* connectedCount);
|
||||||
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
|
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
|
||||||
|
|
||||||
void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
|
void Button(Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
|
||||||
void Axis(int id, Input::Axis axis, int value);
|
void Axis(Input::Axis axis, int value, bool smooth = true);
|
||||||
void Gyro(int id, const float gyro[3]);
|
void Gyro(int id);
|
||||||
void Acceleration(int id, const float acceleration[3]);
|
void Acceleration(int id);
|
||||||
|
void UpdateGyro(const float gyro[3]);
|
||||||
|
void UpdateAcceleration(const float acceleration[3]);
|
||||||
|
void UpdateAxisSmoothing();
|
||||||
void SetLightBarRGB(u8 r, u8 g, u8 b);
|
void SetLightBarRGB(u8 r, u8 g, u8 b);
|
||||||
void SetVibration(u8 smallMotor, u8 largeMotor);
|
void PollLightColour();
|
||||||
|
bool SetVibration(u8 smallMotor, u8 largeMotor);
|
||||||
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
|
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
|
||||||
void SetEngine(std::unique_ptr<Engine>);
|
|
||||||
Engine* GetEngine();
|
|
||||||
u32 Poll();
|
|
||||||
|
|
||||||
u8 GetTouchCount();
|
u8 GetTouchCount();
|
||||||
void SetTouchCount(u8 touchCount);
|
void SetTouchCount(u8 touchCount);
|
||||||
@ -129,11 +143,12 @@ public:
|
|||||||
Libraries::Pad::OrbisFQuaternion GetLastOrientation();
|
Libraries::Pad::OrbisFQuaternion GetLastOrientation();
|
||||||
std::chrono::steady_clock::time_point GetLastUpdate();
|
std::chrono::steady_clock::time_point GetLastUpdate();
|
||||||
void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
|
void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
|
||||||
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
|
||||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
float gyro_poll_rate;
|
||||||
float deltaTime,
|
float accel_poll_rate;
|
||||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
|
||||||
Libraries::Pad::OrbisFQuaternion& orientation);
|
s32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||||
|
SDL_Gamepad* m_sdl_gamepad = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PushState();
|
void PushState();
|
||||||
@ -146,22 +161,46 @@ private:
|
|||||||
bool m_was_secondary_reset = false;
|
bool m_was_secondary_reset = false;
|
||||||
std::chrono::steady_clock::time_point m_last_update = {};
|
std::chrono::steady_clock::time_point m_last_update = {};
|
||||||
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
Colour colour;
|
||||||
|
|
||||||
State m_state;
|
State m_state;
|
||||||
|
|
||||||
std::mutex m_states_queue_mutex;
|
std::mutex m_states_queue_mutex;
|
||||||
RingBufferQueue<State> m_states_queue;
|
RingBufferQueue<State> m_states_queue;
|
||||||
|
};
|
||||||
|
|
||||||
std::unique_ptr<Engine> m_engine = nullptr;
|
class GameControllers {
|
||||||
|
std::array<GameController*, 5> controllers;
|
||||||
|
|
||||||
|
static std::array<std::optional<Colour>, 4> controller_override_colors;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GameControllers()
|
||||||
|
: controllers({new GameController(), new GameController(), new GameController(),
|
||||||
|
new GameController(), new GameController()}) {};
|
||||||
|
virtual ~GameControllers() = default;
|
||||||
|
GameController* operator[](const size_t& i) const {
|
||||||
|
if (i > 4) {
|
||||||
|
UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i);
|
||||||
|
}
|
||||||
|
return controllers[i];
|
||||||
|
}
|
||||||
|
void TryOpenSDLControllers();
|
||||||
|
u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
|
||||||
|
static std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
|
||||||
|
static std::optional<u8> GetControllerIndexFromControllerID(s32 controller_id);
|
||||||
|
|
||||||
|
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||||
|
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||||
|
float deltaTime,
|
||||||
|
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||||
|
Libraries::Pad::OrbisFQuaternion& orientation);
|
||||||
|
static void SetControllerCustomColor(s32 i, u8 r, u8 g, u8 b) {
|
||||||
|
controller_override_colors[i] = {r, g, b};
|
||||||
|
}
|
||||||
|
static std::optional<Colour> GetControllerCustomColor(s32 i) {
|
||||||
|
return controller_override_colors[i];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Input
|
} // namespace Input
|
||||||
|
|
||||||
namespace GamepadSelect {
|
|
||||||
|
|
||||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
|
|
||||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
|
|
||||||
std::string GetSelectedGamepad();
|
|
||||||
void SetSelectedGamepad(std::string GUID);
|
|
||||||
|
|
||||||
} // namespace GamepadSelect
|
|
||||||
|
|||||||
@ -18,10 +18,10 @@
|
|||||||
#include "SDL3/SDL_events.h"
|
#include "SDL3/SDL_events.h"
|
||||||
#include "SDL3/SDL_timer.h"
|
#include "SDL3/SDL_timer.h"
|
||||||
|
|
||||||
#include "common/config.h"
|
|
||||||
#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 "core/devtools/layer.h"
|
#include "core/devtools/layer.h"
|
||||||
#include "core/emulator_settings.h"
|
#include "core/emulator_settings.h"
|
||||||
#include "core/emulator_state.h"
|
#include "core/emulator_state.h"
|
||||||
@ -43,78 +43,185 @@ What structs are needed?
|
|||||||
InputBinding(key1, key2, key3)
|
InputBinding(key1, key2, key3)
|
||||||
ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is
|
ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is
|
||||||
always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element))
|
always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element))
|
||||||
|
|
||||||
Things to always test before pushing like a dumbass:
|
|
||||||
Button outputs
|
|
||||||
Axis outputs
|
|
||||||
Input hierarchy
|
|
||||||
Multi key inputs
|
|
||||||
Mouse to joystick
|
|
||||||
Key toggle
|
|
||||||
Joystick halfmode
|
|
||||||
|
|
||||||
Don't be an idiot and test only the changed part expecting everything else to not be broken
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
constexpr std::string_view GetDefaultGlobalConfig() {
|
||||||
|
return R"(# Anything put here will be loaded for all games,
|
||||||
|
# alongside the game's config or default.ini depending on your preference.
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::string_view GetDefaultInputConfig() {
|
||||||
|
return R"(#Feeling lost? Check out the Help section!
|
||||||
|
|
||||||
|
# Keyboard bindings
|
||||||
|
|
||||||
|
triangle = kp8
|
||||||
|
circle = kp6
|
||||||
|
cross = kp2
|
||||||
|
square = kp4
|
||||||
|
# Alternatives for users without a keypad
|
||||||
|
triangle = c
|
||||||
|
circle = b
|
||||||
|
cross = n
|
||||||
|
square = v
|
||||||
|
|
||||||
|
l1 = q
|
||||||
|
r1 = u
|
||||||
|
l2 = e
|
||||||
|
r2 = o
|
||||||
|
l3 = x
|
||||||
|
r3 = m
|
||||||
|
|
||||||
|
options = enter
|
||||||
|
touchpad_center = space
|
||||||
|
|
||||||
|
pad_up = up
|
||||||
|
pad_down = down
|
||||||
|
pad_left = left
|
||||||
|
pad_right = right
|
||||||
|
|
||||||
|
axis_left_x_minus = a
|
||||||
|
axis_left_x_plus = d
|
||||||
|
axis_left_y_minus = w
|
||||||
|
axis_left_y_plus = s
|
||||||
|
|
||||||
|
axis_right_x_minus = j
|
||||||
|
axis_right_x_plus = l
|
||||||
|
axis_right_y_minus = i
|
||||||
|
axis_right_y_plus = k
|
||||||
|
|
||||||
|
# Controller bindings
|
||||||
|
|
||||||
|
triangle = triangle
|
||||||
|
cross = cross
|
||||||
|
square = square
|
||||||
|
circle = circle
|
||||||
|
|
||||||
|
l1 = l1
|
||||||
|
l2 = l2
|
||||||
|
l3 = l3
|
||||||
|
r1 = r1
|
||||||
|
r2 = r2
|
||||||
|
r3 = r3
|
||||||
|
|
||||||
|
options = options
|
||||||
|
touchpad_center = back
|
||||||
|
|
||||||
|
pad_up = pad_up
|
||||||
|
pad_down = pad_down
|
||||||
|
pad_left = pad_left
|
||||||
|
pad_right = pad_right
|
||||||
|
|
||||||
|
axis_left_x = axis_left_x
|
||||||
|
axis_left_y = axis_left_y
|
||||||
|
axis_right_x = axis_right_x
|
||||||
|
axis_right_y = axis_right_y
|
||||||
|
|
||||||
|
# Range of deadzones: 1 (almost none) to 127 (max)
|
||||||
|
analog_deadzone = leftjoystick, 2, 127
|
||||||
|
analog_deadzone = rightjoystick, 2, 127
|
||||||
|
|
||||||
|
override_controller_color = false, 0, 0, 255
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
std::filesystem::path GetInputConfigFile(const std::string& game_id) {
|
||||||
|
// Read configuration file of the game, and if it doesn't exist, generate it from default
|
||||||
|
// If that doesn't exist either, generate that from getDefaultConfig() and try again
|
||||||
|
// If even the folder is missing, we start with that.
|
||||||
|
|
||||||
|
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "input_config";
|
||||||
|
const auto config_file = config_dir / (game_id + ".ini");
|
||||||
|
const auto default_config_file = config_dir / "default.ini";
|
||||||
|
|
||||||
|
// Ensure the config directory exists
|
||||||
|
if (!std::filesystem::exists(config_dir)) {
|
||||||
|
std::filesystem::create_directories(config_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the default config exists
|
||||||
|
if (!std::filesystem::exists(default_config_file)) {
|
||||||
|
// If the default config is also missing, create it from getDefaultConfig()
|
||||||
|
const auto default_config = GetDefaultInputConfig();
|
||||||
|
std::ofstream default_config_stream(default_config_file);
|
||||||
|
if (default_config_stream) {
|
||||||
|
default_config_stream << default_config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if empty, we only need to execute the function up until this point
|
||||||
|
if (game_id.empty()) {
|
||||||
|
return default_config_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create global config if it doesn't exist yet
|
||||||
|
if (game_id == "global" && !std::filesystem::exists(config_file)) {
|
||||||
|
if (!std::filesystem::exists(config_file)) {
|
||||||
|
const auto global_config = GetDefaultGlobalConfig();
|
||||||
|
std::ofstream global_config_stream(config_file);
|
||||||
|
if (global_config_stream) {
|
||||||
|
global_config_stream << global_config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (game_id == "global") {
|
||||||
|
std::map<std::string, std::string> default_bindings_to_add = {
|
||||||
|
{"hotkey_renderdoc_capture", "f12"},
|
||||||
|
{"hotkey_fullscreen", "f11"},
|
||||||
|
{"hotkey_show_fps", "f10"},
|
||||||
|
{"hotkey_pause", "f9"},
|
||||||
|
{"hotkey_reload_inputs", "f8"},
|
||||||
|
{"hotkey_toggle_mouse_to_joystick", "f7"},
|
||||||
|
{"hotkey_toggle_mouse_to_gyro", "f6"},
|
||||||
|
{"hotkey_add_virtual_user", "f5"},
|
||||||
|
{"hotkey_remove_virtual_user", "f4"},
|
||||||
|
{"hotkey_toggle_mouse_to_touchpad", "delete"},
|
||||||
|
{"hotkey_quit", "lctrl, lshift, end"},
|
||||||
|
{"hotkey_volume_up", "kpplus"},
|
||||||
|
{"hotkey_volume_down", "kpminus"},
|
||||||
|
};
|
||||||
|
std::ifstream global_in(config_file);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(global_in, line)) {
|
||||||
|
line.erase(std::remove_if(line.begin(), line.end(),
|
||||||
|
[](unsigned char c) { return std::isspace(c); }),
|
||||||
|
line.end());
|
||||||
|
std::size_t equal_pos = line.find('=');
|
||||||
|
if (equal_pos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string output_string = line.substr(0, equal_pos);
|
||||||
|
default_bindings_to_add.erase(output_string);
|
||||||
|
}
|
||||||
|
global_in.close();
|
||||||
|
std::ofstream global_out(config_file, std::ios::app);
|
||||||
|
for (auto const& b : default_bindings_to_add) {
|
||||||
|
global_out << b.first << " = " << b.second << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If game-specific config doesn't exist, create it from the default config
|
||||||
|
if (!std::filesystem::exists(config_file)) {
|
||||||
|
std::filesystem::copy(default_config_file, config_file);
|
||||||
|
}
|
||||||
|
return config_file;
|
||||||
|
}
|
||||||
|
|
||||||
bool leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
bool leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
||||||
std::pair<int, int> leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone,
|
std::array<std::pair<int, int>, 4> leftjoystick_deadzone, rightjoystick_deadzone,
|
||||||
righttrigger_deadzone;
|
lefttrigger_deadzone, righttrigger_deadzone;
|
||||||
|
|
||||||
std::list<std::pair<InputEvent, bool>> pressed_keys;
|
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{
|
GameControllers ControllerOutput::controllers =
|
||||||
// Important: these have to be the first, or else they will update in the wrong order
|
*Common::Singleton<Input::GameControllers>::Instance();
|
||||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
|
||||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
|
||||||
ControllerOutput(KEY_TOGGLE),
|
|
||||||
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
|
||||||
|
|
||||||
// Button mappings
|
std::array<ControllerAllOutputs, 9> output_arrays = {
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
ControllerAllOutputs(0), ControllerAllOutputs(1), ControllerAllOutputs(2),
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
ControllerAllOutputs(3), ControllerAllOutputs(4), ControllerAllOutputs(5),
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
ControllerAllOutputs(6), ControllerAllOutputs(7), ControllerAllOutputs(8),
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
|
||||||
|
|
||||||
// Axis mappings
|
|
||||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
|
||||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
|
||||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
|
||||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
|
||||||
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
|
||||||
|
|
||||||
ControllerOutput(HOTKEY_FULLSCREEN),
|
|
||||||
ControllerOutput(HOTKEY_PAUSE),
|
|
||||||
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
|
||||||
ControllerOutput(HOTKEY_QUIT),
|
|
||||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
|
||||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
|
||||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
|
||||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
|
||||||
ControllerOutput(HOTKEY_RENDERDOC),
|
|
||||||
ControllerOutput(HOTKEY_VOLUME_UP),
|
|
||||||
ControllerOutput(HOTKEY_VOLUME_DOWN),
|
|
||||||
|
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ControllerOutput::LinkJoystickAxes() {
|
void ControllerOutput::LinkJoystickAxes() {
|
||||||
@ -158,6 +265,8 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
|||||||
return OPBDO::TouchPad;
|
return OPBDO::TouchPad;
|
||||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||||
return OPBDO::L1;
|
return OPBDO::L1;
|
||||||
|
case SDL_GAMEPAD_BUTTON_MISC1: // Move
|
||||||
|
return OPBDO::L1;
|
||||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||||
return OPBDO::R1;
|
return OPBDO::R1;
|
||||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
||||||
@ -223,10 +332,19 @@ 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 game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
|
std::string game_id_or_default =
|
||||||
const auto config_file = Config::GetInputConfigFile(game_id_or_default);
|
EmulatorSettings.IsUseUnifiedInputConfig() ? "default" : game_id;
|
||||||
const auto global_config_file = Config::GetInputConfigFile("global");
|
const auto config_file = GetInputConfigFile(game_id_or_default);
|
||||||
|
const auto global_config_file = GetInputConfigFile("global");
|
||||||
|
|
||||||
// we reset these here so in case the user fucks up or doesn't include some of these,
|
// we reset these here so in case the user fucks up or doesn't include some of these,
|
||||||
// we can fall back to default
|
// we can fall back to default
|
||||||
@ -235,13 +353,14 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
float mouse_speed = 1;
|
float mouse_speed = 1;
|
||||||
float mouse_speed_offset = 0.125;
|
float mouse_speed_offset = 0.125;
|
||||||
|
|
||||||
leftjoystick_deadzone = {1, 127};
|
// me when I'm in a type deduction tournament and my opponent is clang
|
||||||
rightjoystick_deadzone = {1, 127};
|
constexpr std::array<std::pair<int, int>, 4> default_deadzone = {
|
||||||
lefttrigger_deadzone = {1, 127};
|
std::pair{1, 127}, {1, 127}, {1, 127}, {1, 127}};
|
||||||
righttrigger_deadzone = {1, 127};
|
|
||||||
|
|
||||||
Config::SetOverrideControllerColor(false);
|
leftjoystick_deadzone = default_deadzone;
|
||||||
Config::SetControllerCustomColor(0, 0, 255);
|
rightjoystick_deadzone = default_deadzone;
|
||||||
|
lefttrigger_deadzone = default_deadzone;
|
||||||
|
righttrigger_deadzone = default_deadzone;
|
||||||
|
|
||||||
int lineCount = 0;
|
int lineCount = 0;
|
||||||
|
|
||||||
@ -278,21 +397,37 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
|
|
||||||
std::string output_string = line.substr(0, equal_pos);
|
std::string 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);
|
||||||
// Remove trailing semicolon from input_string
|
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
|
||||||
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
|
|
||||||
input_string != ";") {
|
// input gamepad id is only for controllers, it's discarded otherwise
|
||||||
line = line.substr(0, line.length() - 1);
|
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> {
|
|
||||||
try {
|
|
||||||
return std::stoi(s);
|
|
||||||
} catch (...) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// todo make override_controller_color and analog_deadzone be controller specific
|
||||||
|
// instead of global
|
||||||
if (output_string == "mouse_to_joystick") {
|
if (output_string == "mouse_to_joystick") {
|
||||||
if (input_string == "left") {
|
if (input_string == "left") {
|
||||||
SetMouseToJoystick(1);
|
SetMouseToJoystick(1);
|
||||||
@ -315,7 +450,7 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
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);
|
||||||
@ -356,15 +491,17 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
|
|
||||||
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
|
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
|
||||||
|
|
||||||
static std::unordered_map<std::string, std::pair<int, int>&> deadzone_map = {
|
static std::unordered_map<std::string, std::array<std::pair<int, int>, 4>&>
|
||||||
{"leftjoystick", leftjoystick_deadzone},
|
deadzone_map = {
|
||||||
{"rightjoystick", rightjoystick_deadzone},
|
{"leftjoystick", leftjoystick_deadzone},
|
||||||
{"l2", lefttrigger_deadzone},
|
{"rightjoystick", rightjoystick_deadzone},
|
||||||
{"r2", righttrigger_deadzone},
|
{"l2", lefttrigger_deadzone},
|
||||||
};
|
{"r2", righttrigger_deadzone},
|
||||||
|
};
|
||||||
|
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
|
||||||
|
|
||||||
if (auto it = deadzone_map.find(device); it != deadzone_map.end()) {
|
if (auto it = deadzone_map.find(device); it != deadzone_map.end()) {
|
||||||
it->second = deadzone;
|
it->second[output_gamepad_id - 1] = deadzone;
|
||||||
LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str,
|
LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str,
|
||||||
outer_deadzone_str);
|
outer_deadzone_str);
|
||||||
} else {
|
} else {
|
||||||
@ -390,10 +527,12 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
lineCount, line);
|
lineCount, line);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Config::SetOverrideControllerColor(enable == "true");
|
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
|
||||||
Config::SetControllerCustomColor(*r, *g, *b);
|
if (enable == "true") {
|
||||||
LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}",
|
GameControllers::SetControllerCustomColor(output_gamepad_id - 1, *r, *g, *b);
|
||||||
enable == "true" ? "override" : "no override", *r, *b, *g);
|
}
|
||||||
|
LOG_DEBUG(Input, "Parsed color settings: {} {} - {} {} {}",
|
||||||
|
enable == "true" ? "override" : "no override", output_gamepad_id, *r, *b, *g);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,31 +549,46 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
auto axis_it = string_to_axis_map.find(output_string);
|
auto axis_it = string_to_axis_map.find(output_string);
|
||||||
if (button_it != string_to_cbutton_map.end()) {
|
if (button_it != string_to_cbutton_map.end()) {
|
||||||
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 (hotkey_it != string_to_hotkey_map.end()) {
|
} else if (hotkey_it != string_to_hotkey_map.end()) {
|
||||||
connection = BindingConnection(
|
connection = BindingConnection(
|
||||||
binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second)));
|
binding,
|
||||||
connections.insert(connections.end(), connection);
|
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||||
|
ControllerOutput(hotkey_it->second)));
|
||||||
} else if (axis_it != string_to_axis_map.end()) {
|
} else if (axis_it != string_to_axis_map.end()) {
|
||||||
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);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// if the input binding contains a controller input, and gamepad ID
|
||||||
|
// isn't specified for either inputs or output (both are -1), then multiply the binding and
|
||||||
|
// add it to all 4 controllers
|
||||||
|
if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) {
|
||||||
|
for (int i = 0; i < output_arrays.size(); i++) {
|
||||||
|
BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1);
|
||||||
|
copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output);
|
||||||
|
connections.push_back(copy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connections.push_back(connection);
|
||||||
|
}
|
||||||
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
|
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
|
||||||
};
|
};
|
||||||
while (std::getline(global_config_stream, line)) {
|
while (std::getline(global_config_stream, line)) {
|
||||||
ProcessLine();
|
ProcessLine();
|
||||||
}
|
}
|
||||||
|
lineCount = 0;
|
||||||
while (std::getline(config_stream, line)) {
|
while (std::getline(config_stream, line)) {
|
||||||
ProcessLine();
|
ProcessLine();
|
||||||
}
|
}
|
||||||
@ -446,6 +600,16 @@ void ParseInputConfig(const std::string game_id = "") {
|
|||||||
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!");
|
||||||
@ -464,6 +628,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||||
|
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:
|
||||||
@ -478,21 +643,17 @@ 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, static_cast<u32>(e.gbutton.button), e.gbutton.down,
|
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gbutton.which) + 1;
|
||||||
0); // clang made me do it
|
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, static_cast<u32>(e.gaxis.axis), true,
|
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gaxis.which) + 1;
|
||||||
e.gaxis.value / 256); // this too
|
return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256);
|
||||||
default:
|
default:
|
||||||
return InputEvent();
|
return InputEvent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GameController* ControllerOutput::controller = nullptr;
|
|
||||||
void ControllerOutput::SetControllerOutputController(GameController* c) {
|
|
||||||
ControllerOutput::controller = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleKeyInList(InputID input) {
|
void ToggleKeyInList(InputID input) {
|
||||||
if (input.type == InputType::Axis) {
|
if (input.type == InputType::Axis) {
|
||||||
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
|
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
|
||||||
@ -538,7 +699,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
|
|||||||
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ControllerOutput::FinalizeUpdate() {
|
void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
|
||||||
auto PushSDLEvent = [&](u32 event_type) {
|
auto PushSDLEvent = [&](u32 event_type) {
|
||||||
if (new_button_state) {
|
if (new_button_state) {
|
||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
@ -553,20 +714,24 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
}
|
}
|
||||||
old_button_state = new_button_state;
|
old_button_state = new_button_state;
|
||||||
old_param = *new_param;
|
old_param = *new_param;
|
||||||
bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed();
|
GameController* controller;
|
||||||
|
if (gamepad_index < 5)
|
||||||
|
controller = controllers[gamepad_index];
|
||||||
|
else
|
||||||
|
UNREACHABLE();
|
||||||
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
|
||||||
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
|
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
|
||||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
|
||||||
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
|
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
|
||||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
|
||||||
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
|
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
|
||||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
break;
|
break;
|
||||||
case LEFTJOYSTICK_HALFMODE:
|
case LEFTJOYSTICK_HALFMODE:
|
||||||
leftjoystick_halfmode = new_button_state;
|
leftjoystick_halfmode = new_button_state;
|
||||||
@ -598,6 +763,12 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
case HOTKEY_RENDERDOC:
|
case HOTKEY_RENDERDOC:
|
||||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||||
break;
|
break;
|
||||||
|
case HOTKEY_ADD_VIRTUAL_USER:
|
||||||
|
PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER);
|
||||||
|
break;
|
||||||
|
case HOTKEY_REMOVE_VIRTUAL_USER:
|
||||||
|
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
|
||||||
|
break;
|
||||||
case HOTKEY_VOLUME_UP:
|
case HOTKEY_VOLUME_UP:
|
||||||
EmulatorSettings.SetVolumeSlider(
|
EmulatorSettings.SetVolumeSlider(
|
||||||
std::clamp(EmulatorSettings.GetVolumeSlider() + 10, 0, 500));
|
std::clamp(EmulatorSettings.GetVolumeSlider() + 10, 0, 500));
|
||||||
@ -618,7 +789,7 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
SetMouseGyroRollMode(new_button_state);
|
SetMouseGyroRollMode(new_button_state);
|
||||||
break;
|
break;
|
||||||
default: // is a normal key (hopefully)
|
default: // is a normal key (hopefully)
|
||||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
controller->Button(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) {
|
||||||
@ -638,28 +809,28 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
switch (c_axis) {
|
switch (c_axis) {
|
||||||
case Axis::LeftX:
|
case Axis::LeftX:
|
||||||
case Axis::LeftY:
|
case Axis::LeftY:
|
||||||
ApplyDeadzone(new_param, leftjoystick_deadzone);
|
ApplyDeadzone(new_param, leftjoystick_deadzone[gamepad_index]);
|
||||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||||
break;
|
break;
|
||||||
case Axis::RightX:
|
case Axis::RightX:
|
||||||
case Axis::RightY:
|
case Axis::RightY:
|
||||||
ApplyDeadzone(new_param, rightjoystick_deadzone);
|
ApplyDeadzone(new_param, rightjoystick_deadzone[gamepad_index]);
|
||||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||||
break;
|
break;
|
||||||
case Axis::TriggerLeft:
|
case Axis::TriggerLeft:
|
||||||
ApplyDeadzone(new_param, lefttrigger_deadzone);
|
ApplyDeadzone(new_param, lefttrigger_deadzone[gamepad_index]);
|
||||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||||
controller->Button(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
controller->Button(OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
||||||
return;
|
return;
|
||||||
case Axis::TriggerRight:
|
case Axis::TriggerRight:
|
||||||
ApplyDeadzone(new_param, righttrigger_deadzone);
|
ApplyDeadzone(new_param, righttrigger_deadzone[gamepad_index]);
|
||||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||||
controller->Button(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
controller->Button(OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
controller->Axis(c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,11 +845,9 @@ bool UpdatePressedKeys(InputEvent event) {
|
|||||||
if (input.type == InputType::Axis) {
|
if (input.type == InputType::Axis) {
|
||||||
// analog input, it gets added when it first sends an event,
|
// analog input, it gets added when it first sends an event,
|
||||||
// and from there, it only changes the parameter
|
// and from there, it only changes the parameter
|
||||||
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
|
auto it = std::lower_bound(
|
||||||
[](const std::pair<InputEvent, bool>& e, InputID i) {
|
pressed_keys.begin(), pressed_keys.end(), input,
|
||||||
return std::tie(e.first.input.type, e.first.input.sdl_id) <
|
[](const std::pair<InputEvent, bool>& e, InputID i) { return e.first.input < i; });
|
||||||
std::tie(i.type, i.sdl_id);
|
|
||||||
});
|
|
||||||
if (it == pressed_keys.end() || it->first.input != input) {
|
if (it == pressed_keys.end() || it->first.input != input) {
|
||||||
pressed_keys.insert(it, {event, false});
|
pressed_keys.insert(it, {event, false});
|
||||||
LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id);
|
LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id);
|
||||||
@ -791,25 +960,33 @@ 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for input blockers
|
// todo find a better solution
|
||||||
ApplyMouseInputBlockers();
|
for (int i = 0; i < output_arrays.size(); i++) {
|
||||||
|
|
||||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
// Reset values and flags
|
||||||
for (auto& it : connections) {
|
for (auto& it : pressed_keys) {
|
||||||
it.output->AddUpdate(it.ProcessBinding());
|
it.second = false;
|
||||||
}
|
}
|
||||||
|
for (auto& it : output_arrays[i].data) {
|
||||||
|
it.ResetUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
// Update all outputs
|
// Check for input blockers
|
||||||
for (auto& it : output_array) {
|
ApplyMouseInputBlockers();
|
||||||
it.FinalizeUpdate();
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "SDL3/SDL_events.h"
|
#include "SDL3/SDL_events.h"
|
||||||
#include "SDL3/SDL_timer.h"
|
#include "SDL3/SDL_timer.h"
|
||||||
@ -35,9 +36,11 @@
|
|||||||
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
|
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
|
||||||
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
|
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
|
||||||
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
|
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
|
||||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
|
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
|
||||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
|
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
|
#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11
|
||||||
|
#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12
|
||||||
|
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 13
|
||||||
|
|
||||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||||
@ -57,6 +60,8 @@
|
|||||||
#define HOTKEY_RENDERDOC 0xf0000009
|
#define HOTKEY_RENDERDOC 0xf0000009
|
||||||
#define HOTKEY_VOLUME_UP 0xf000000a
|
#define HOTKEY_VOLUME_UP 0xf000000a
|
||||||
#define HOTKEY_VOLUME_DOWN 0xf000000b
|
#define HOTKEY_VOLUME_DOWN 0xf000000b
|
||||||
|
#define HOTKEY_ADD_VIRTUAL_USER 0xf000000c
|
||||||
|
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d
|
||||||
|
|
||||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||||
|
|
||||||
@ -77,21 +82,24 @@ class InputID {
|
|||||||
public:
|
public:
|
||||||
InputType type;
|
InputType type;
|
||||||
u32 sdl_id;
|
u32 sdl_id;
|
||||||
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
|
u8 gamepad_id;
|
||||||
|
InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1)
|
||||||
|
: type(d), sdl_id(i), gamepad_id(g) {}
|
||||||
bool operator==(const InputID& o) const {
|
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 {
|
auto operator<=>(const InputID& o) const {
|
||||||
return type <= o.type && sdl_id <= o.sdl_id;
|
return std::tie(gamepad_id, type, sdl_id, gamepad_id) <=>
|
||||||
|
std::tie(o.gamepad_id, o.type, o.sdl_id, o.gamepad_id);
|
||||||
}
|
}
|
||||||
bool IsValid() const {
|
bool IsValid() const {
|
||||||
return *this != InputID();
|
return *this != InputID();
|
||||||
}
|
}
|
||||||
std::string ToString() {
|
std::string ToString() {
|
||||||
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
|
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,6 +157,8 @@ const std::map<std::string, u32> string_to_hotkey_map = {
|
|||||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||||
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
||||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||||
|
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
|
||||||
|
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
|
||||||
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
|
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
|
||||||
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
|
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
|
||||||
};
|
};
|
||||||
@ -401,7 +411,7 @@ public:
|
|||||||
inline bool IsEmpty() {
|
inline bool IsEmpty() {
|
||||||
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
|
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
|
||||||
}
|
}
|
||||||
std::string ToString() { // todo add device type
|
std::string ToString() {
|
||||||
switch (KeyCount()) {
|
switch (KeyCount()) {
|
||||||
case 1:
|
case 1:
|
||||||
return fmt::format("({})", keys[0].ToString());
|
return fmt::format("({})", keys[0].ToString());
|
||||||
@ -420,14 +430,14 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ControllerOutput {
|
class ControllerOutput {
|
||||||
static GameController* controller;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void SetControllerOutputController(GameController* c);
|
static GameControllers controllers;
|
||||||
|
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
|
||||||
@ -441,6 +451,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);
|
||||||
@ -466,7 +477,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:
|
||||||
@ -481,6 +492,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)
|
||||||
@ -494,9 +512,81 @@ 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:
|
||||||
|
static constexpr u64 output_count = 39;
|
||||||
|
std::array<ControllerOutput, output_count> data = {
|
||||||
|
// Important: these have to be the first, or else they will update in the wrong order
|
||||||
|
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||||
|
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||||
|
ControllerOutput(KEY_TOGGLE),
|
||||||
|
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||||
|
|
||||||
|
// Button mappings
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||||
|
|
||||||
|
// Axis mappings
|
||||||
|
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||||
|
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||||
|
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||||
|
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||||
|
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||||
|
|
||||||
|
ControllerOutput(HOTKEY_FULLSCREEN),
|
||||||
|
ControllerOutput(HOTKEY_PAUSE),
|
||||||
|
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
||||||
|
ControllerOutput(HOTKEY_QUIT),
|
||||||
|
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||||
|
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||||
|
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||||
|
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||||
|
ControllerOutput(HOTKEY_RENDERDOC),
|
||||||
|
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
|
||||||
|
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
|
||||||
|
ControllerOutput(HOTKEY_VOLUME_UP),
|
||||||
|
ControllerOutput(HOTKEY_VOLUME_DOWN),
|
||||||
|
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||||
|
};
|
||||||
|
ControllerAllOutputs(u8 g) {
|
||||||
|
for (int i = 0; i < output_count; i++) {
|
||||||
|
data[i].gamepad_id = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Updates the list of pressed keys with the given input.
|
// 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);
|
||||||
|
|||||||
@ -77,11 +77,11 @@ void EmulateJoystick(GameController* controller, u32 interval) {
|
|||||||
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
|
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
|
||||||
|
|
||||||
if (d_x != 0 || d_y != 0) {
|
if (d_x != 0 || d_y != 0) {
|
||||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, a_x));
|
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, a_x), false);
|
||||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, a_y));
|
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, a_y), false);
|
||||||
} else {
|
} else {
|
||||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0));
|
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, 0), false);
|
||||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0));
|
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, 0), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,13 +89,13 @@ constexpr float constant_down_accel[3] = {0.0f, 9.81f, 0.0f};
|
|||||||
void EmulateGyro(GameController* controller, u32 interval) {
|
void EmulateGyro(GameController* controller, u32 interval) {
|
||||||
float d_x = 0, d_y = 0;
|
float d_x = 0, d_y = 0;
|
||||||
SDL_GetRelativeMouseState(&d_x, &d_y);
|
SDL_GetRelativeMouseState(&d_x, &d_y);
|
||||||
controller->Acceleration(1, constant_down_accel);
|
controller->UpdateAcceleration(constant_down_accel);
|
||||||
float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f};
|
float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f};
|
||||||
if (mouse_gyro_roll_mode) {
|
if (mouse_gyro_roll_mode) {
|
||||||
gyro_from_mouse[1] = 0.0f;
|
gyro_from_mouse[1] = 0.0f;
|
||||||
gyro_from_mouse[2] = -d_x / 100;
|
gyro_from_mouse[2] = -d_x / 100;
|
||||||
}
|
}
|
||||||
controller->Gyro(1, gyro_from_mouse);
|
controller->UpdateGyro(gyro_from_mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulateTouchpad(GameController* controller, u32 interval) {
|
void EmulateTouchpad(GameController* controller, u32 interval) {
|
||||||
@ -104,7 +104,7 @@ void EmulateTouchpad(GameController* controller, u32 interval) {
|
|||||||
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
|
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
|
||||||
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
|
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
|
||||||
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
|
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
|
||||||
controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
|
controller->Button(Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
|
||||||
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
|
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,14 @@
|
|||||||
#include "SDL3/SDL_timer.h"
|
#include "SDL3/SDL_timer.h"
|
||||||
#include "SDL3/SDL_video.h"
|
#include "SDL3/SDL_video.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/config.h"
|
|
||||||
#include "common/elf_info.h"
|
#include "common/elf_info.h"
|
||||||
#include "core/debug_state.h"
|
#include "core/debug_state.h"
|
||||||
#include "core/devtools/layer.h"
|
#include "core/devtools/layer.h"
|
||||||
|
#include "core/emulator_settings.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 "core/user_settings.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"
|
||||||
@ -26,9 +28,9 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <core/emulator_settings.h>
|
#include <core/emulator_settings.h>
|
||||||
|
|
||||||
namespace Input {
|
namespace Frontend {
|
||||||
|
|
||||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
using namespace Libraries::Pad;
|
||||||
|
|
||||||
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||||
using OPBDO = OrbisPadButtonDataOffset;
|
using OPBDO = OrbisPadButtonDataOffset;
|
||||||
@ -69,220 +71,24 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
|
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
||||||
switch (axis) {
|
|
||||||
case Axis::LeftX:
|
|
||||||
return SDL_GAMEPAD_AXIS_LEFTX;
|
|
||||||
case Axis::LeftY:
|
|
||||||
return SDL_GAMEPAD_AXIS_LEFTY;
|
|
||||||
case Axis::RightX:
|
|
||||||
return SDL_GAMEPAD_AXIS_RIGHTX;
|
|
||||||
case Axis::RightY:
|
|
||||||
return SDL_GAMEPAD_AXIS_RIGHTY;
|
|
||||||
case Axis::TriggerLeft:
|
|
||||||
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
|
|
||||||
case Axis::TriggerRight:
|
|
||||||
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLInputEngine::~SDLInputEngine() {
|
|
||||||
if (m_gamepad) {
|
|
||||||
SDL_CloseGamepad(m_gamepad);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLInputEngine::Init() {
|
|
||||||
if (m_gamepad) {
|
|
||||||
SDL_CloseGamepad(m_gamepad);
|
|
||||||
m_gamepad = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int gamepad_count;
|
|
||||||
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
|
|
||||||
if (!gamepads) {
|
|
||||||
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (gamepad_count == 0) {
|
|
||||||
LOG_INFO(Input, "No gamepad found!");
|
|
||||||
SDL_free(gamepads);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count,
|
|
||||||
GamepadSelect::GetSelectedGamepad());
|
|
||||||
int defaultIndex =
|
|
||||||
GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID());
|
|
||||||
|
|
||||||
// If user selects a gamepad in the GUI, use that, otherwise try the default
|
|
||||||
if (!m_gamepad) {
|
|
||||||
if (selectedIndex != -1) {
|
|
||||||
m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]);
|
|
||||||
LOG_INFO(Input, "Opening gamepad selected in GUI.");
|
|
||||||
} else if (defaultIndex != -1) {
|
|
||||||
m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]);
|
|
||||||
LOG_INFO(Input, "Opening default gamepad.");
|
|
||||||
} else {
|
|
||||||
m_gamepad = SDL_OpenGamepad(gamepads[0]);
|
|
||||||
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_gamepad) {
|
|
||||||
if (!m_gamepad) {
|
|
||||||
LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError());
|
|
||||||
SDL_free(gamepads);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
|
|
||||||
Uint16 vendor = SDL_GetJoystickVendor(joystick);
|
|
||||||
Uint16 product = SDL_GetJoystickProduct(joystick);
|
|
||||||
|
|
||||||
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
|
|
||||||
|
|
||||||
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
|
|
||||||
if (isDualSense) {
|
|
||||||
LOG_INFO(Input, "Detected DualSense Controller");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Config::getIsMotionControlsEnabled()) {
|
|
||||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
|
|
||||||
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
|
|
||||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
|
|
||||||
SDL_GetError());
|
|
||||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
|
|
||||||
}
|
|
||||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
|
|
||||||
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
|
|
||||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
|
|
||||||
SDL_GetError());
|
|
||||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_free(gamepads);
|
|
||||||
|
|
||||||
int* rgb = Config::GetControllerCustomColor();
|
|
||||||
|
|
||||||
if (isDualSense) {
|
|
||||||
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
|
|
||||||
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
|
||||||
if (m_gamepad) {
|
|
||||||
SDL_SetGamepadLED(m_gamepad, r, g, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
|
|
||||||
if (m_gamepad) {
|
|
||||||
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
|
|
||||||
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
|
|
||||||
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
State SDLInputEngine::ReadState() {
|
|
||||||
State state{};
|
|
||||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
|
|
||||||
auto orbisButton = SDLGamepadToOrbisButton(i);
|
|
||||||
if (orbisButton == OrbisPadButtonDataOffset::None) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Axes
|
|
||||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
|
|
||||||
const auto axis = static_cast<Axis>(i);
|
|
||||||
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
|
|
||||||
switch (axis) {
|
|
||||||
case Axis::TriggerLeft:
|
|
||||||
case Axis::TriggerRight:
|
|
||||||
state.OnAxis(axis, GetAxis(0, 0x8000, value));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touchpad
|
|
||||||
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
|
|
||||||
for (int finger = 0; finger < 2; ++finger) {
|
|
||||||
bool down;
|
|
||||||
float x, y;
|
|
||||||
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
|
|
||||||
state.OnTouchpad(finger, down, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gyro
|
|
||||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
|
|
||||||
float gyro[3];
|
|
||||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
|
|
||||||
state.OnGyro(gyro);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accel
|
|
||||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
|
|
||||||
float accel[3];
|
|
||||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
|
|
||||||
state.OnAccel(accel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
float SDLInputEngine::GetGyroPollRate() const {
|
|
||||||
return m_gyro_poll_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
float SDLInputEngine::GetAccelPollRate() const {
|
|
||||||
return m_accel_poll_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Input
|
|
||||||
|
|
||||||
namespace Frontend {
|
|
||||||
|
|
||||||
using namespace Libraries::Pad;
|
|
||||||
|
|
||||||
std::mutex motion_control_mutex;
|
|
||||||
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
|
|
||||||
static Uint32 SDLCALL PollGyroAndAccel(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
|
||||||
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||||
std::scoped_lock l{motion_control_mutex};
|
controller->UpdateAxisSmoothing();
|
||||||
controller->Gyro(0, gyro_buf);
|
controller->Gyro(0);
|
||||||
controller->Acceleration(0, accel_buf);
|
controller->Acceleration(0);
|
||||||
return 4;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
|
static Uint32 SDLCALL PollControllerLightColour(void* userdata, SDL_TimerID timer_id,
|
||||||
|
Uint32 interval) {
|
||||||
|
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||||
|
controller->PollLightColour();
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
|
||||||
std::string_view window_title)
|
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());
|
||||||
}
|
}
|
||||||
@ -330,7 +136,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
|||||||
SDL_SyncWindow(window);
|
SDL_SyncWindow(window);
|
||||||
|
|
||||||
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;
|
||||||
@ -355,11 +160,11 @@ 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()));
|
||||||
|
controllers.TryOpenSDLControllers();
|
||||||
|
|
||||||
if (Config::getBackgroundControllerInput()) {
|
if (EmulatorSettings.IsBackgroundControllerInput()) {
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,37 +204,16 @@ 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>());
|
controllers.TryOpenSDLControllers();
|
||||||
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);
|
|
||||||
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);
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||||
break;
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||||
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||||
// AND IT DOESN'T EVEN USE PROPER ENUMS
|
|
||||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||||
switch ((SDL_SensorType)event.gsensor.sensor) {
|
OnGamepadEvent(&event);
|
||||||
case SDL_SENSOR_GYRO: {
|
|
||||||
std::scoped_lock l{motion_control_mutex};
|
|
||||||
memcpy(gyro_buf, event.gsensor.data, sizeof(gyro_buf));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDL_SENSOR_ACCEL: {
|
|
||||||
std::scoped_lock l{motion_control_mutex};
|
|
||||||
memcpy(accel_buf, event.gsensor.data, sizeof(accel_buf));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_QUIT:
|
case SDL_EVENT_QUIT:
|
||||||
is_open = false;
|
is_open = false;
|
||||||
@ -455,7 +239,7 @@ void WindowSDL::WaitEvent() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_CHANGE_CONTROLLER:
|
case SDL_EVENT_CHANGE_CONTROLLER:
|
||||||
controller->GetEngine()->Init();
|
UNREACHABLE_MSG("todo");
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
|
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
|
||||||
Overlay::ToggleSimpleFps();
|
Overlay::ToggleSimpleFps();
|
||||||
@ -476,6 +260,29 @@ void WindowSDL::WaitEvent() {
|
|||||||
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
|
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
|
||||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
|
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
|
||||||
break;
|
break;
|
||||||
|
case SDL_EVENT_ADD_VIRTUAL_USER:
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (controllers[i]->user_id == -1) {
|
||||||
|
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
||||||
|
if (!u) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controllers[i]->user_id = u->user_id;
|
||||||
|
UserManagement.LoginUser(u, i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_REMOVE_VIRTUAL_USER:
|
||||||
|
LOG_INFO(Input, "Remove user");
|
||||||
|
for (int i = 3; i >= 0; i--) {
|
||||||
|
if (controllers[i]->user_id != -1) {
|
||||||
|
UserManagement.LogoutUser(UserManagement.GetUserByID(controllers[i]->user_id));
|
||||||
|
controllers[i]->user_id = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case SDL_EVENT_RDOC_CAPTURE:
|
case SDL_EVENT_RDOC_CAPTURE:
|
||||||
VideoCore::TriggerCapture();
|
VideoCore::TriggerCapture();
|
||||||
break;
|
break;
|
||||||
@ -485,8 +292,10 @@ void WindowSDL::WaitEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WindowSDL::InitTimers() {
|
void WindowSDL::InitTimers() {
|
||||||
SDL_AddTimer(4, &PollGyroAndAccel, controller);
|
for (int i = 0; i < 4; ++i) {
|
||||||
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
|
SDL_AddTimer(4, &PollController, controllers[i]);
|
||||||
|
}
|
||||||
|
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowSDL::RequestKeyboard() {
|
void WindowSDL::RequestKeyboard() {
|
||||||
@ -554,10 +363,44 @@ 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->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down);
|
controllers[controllers.GetGamepadIndexFromJoystickId(event->gbutton.which)]->Button(
|
||||||
|
OrbisPadButtonDataOffset::TouchPad, input_down);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u8 gamepad;
|
||||||
|
|
||||||
|
switch (event->type) {
|
||||||
|
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||||
|
switch ((SDL_SensorType)event->gsensor.sensor) {
|
||||||
|
case SDL_SENSOR_GYRO:
|
||||||
|
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
||||||
|
if (gamepad < 5) {
|
||||||
|
controllers[gamepad]->UpdateGyro(event->gsensor.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_SENSOR_ACCEL:
|
||||||
|
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
||||||
|
if (gamepad < 5) {
|
||||||
|
controllers[gamepad]->UpdateAcceleration(event->gsensor.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||||
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||||
|
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||||
|
controllers[controllers.GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
|
||||||
|
->SetTouchpadState(event->gtouchpad.finger,
|
||||||
|
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
|
||||||
|
event->gtouchpad.y);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// add/remove it from the list
|
// add/remove it from the list
|
||||||
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@ -14,23 +14,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:
|
|
||||||
float m_gyro_poll_rate = 0.0f;
|
|
||||||
float m_accel_poll_rate = 0.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Input
|
|
||||||
|
|
||||||
namespace Frontend {
|
namespace Frontend {
|
||||||
|
|
||||||
@ -62,7 +47,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();
|
||||||
|
|
||||||
@ -100,7 +85,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{};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user