mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-09 13:19:00 +00:00
Squashed multiple controllers PR
This commit is contained in:
@@ -46,7 +46,7 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
|
||||
|
||||
if constexpr (std::is_same_v<T, int>) {
|
||||
if (it->second.is_integer()) {
|
||||
return static_cast<int>(toml::get<int>(it->second));
|
||||
return static_cast<s32>(toml::get<int>(it->second));
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, unsigned int>) {
|
||||
if (it->second.is_integer()) {
|
||||
@@ -54,15 +54,19 @@ std::optional<T> get_optional(const toml::value& v, const std::string& key) {
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
if (it->second.is_floating()) {
|
||||
return toml::get<double>(it->second);
|
||||
return toml::get<T>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, std::string>) {
|
||||
if (it->second.is_string()) {
|
||||
return toml::get<std::string>(it->second);
|
||||
return toml::get<T>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
if (it->second.is_boolean()) {
|
||||
return toml::get<bool>(it->second);
|
||||
return toml::get<T>(it->second);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, std::array<string, 4>>) {
|
||||
if (it->second.is_array()) {
|
||||
return toml::get<T>(it->second);
|
||||
}
|
||||
} else {
|
||||
static_assert([] { return false; }(), "Unsupported type in get_optional<T>");
|
||||
@@ -114,6 +118,9 @@ public:
|
||||
void set(const T value, bool is_game_specific = false) {
|
||||
is_game_specific ? game_specific_value = value : base_value = value;
|
||||
}
|
||||
void setDefault(bool is_game_specific = false) {
|
||||
is_game_specific ? game_specific_value = default_value : base_value = default_value;
|
||||
}
|
||||
void setTomlValue(toml::ordered_value& data, const std::string& header, const std::string& key,
|
||||
bool is_game_specific = false) {
|
||||
if (is_game_specific) {
|
||||
@@ -130,7 +137,12 @@ public:
|
||||
|
||||
// General
|
||||
static ConfigEntry<int> volumeSlider(100);
|
||||
static ConfigEntry<string> userName("shadPS4");
|
||||
static ConfigEntry<std::array<std::string, 4>> userNames({
|
||||
"shadPS4",
|
||||
"shadps4-2",
|
||||
"shadPS4-3",
|
||||
"shadPS4-4",
|
||||
});
|
||||
|
||||
// Input
|
||||
static ConfigEntry<int> cursorState(HideCursorState::Idle);
|
||||
@@ -296,8 +308,18 @@ u32 getInternalScreenHeight() {
|
||||
return internalScreenHeight.get();
|
||||
}
|
||||
|
||||
string getUserName() {
|
||||
return userName.get();
|
||||
void setUserName(int id, string name) {
|
||||
auto temp = userNames.get();
|
||||
temp[id] = name;
|
||||
userNames.set(temp);
|
||||
}
|
||||
|
||||
std::array<string, 4> const getUserNames() {
|
||||
return userNames.get();
|
||||
}
|
||||
|
||||
std::string getUserName(int id) {
|
||||
return userNames.get()[id];
|
||||
}
|
||||
|
||||
bool getUseSpecialPad() {
|
||||
@@ -479,10 +501,6 @@ void setLanguage(u32 language, bool is_game_specific) {
|
||||
m_language.set(language, is_game_specific);
|
||||
}
|
||||
|
||||
void setUserName(const string& name, bool is_game_specific) {
|
||||
userName.set(name, is_game_specific);
|
||||
}
|
||||
|
||||
void setUseSpecialPad(bool use) {
|
||||
useSpecialPad.base_value = use;
|
||||
}
|
||||
@@ -584,7 +602,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
const toml::value& general = data.at("General");
|
||||
|
||||
volumeSlider.setFromToml(general, "volumeSlider", is_game_specific);
|
||||
userName.setFromToml(general, "userName", is_game_specific);
|
||||
userNames.setFromToml(general, "userNames", is_game_specific);
|
||||
defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific);
|
||||
}
|
||||
|
||||
@@ -746,7 +764,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
}
|
||||
// Entries saved by the game-specific settings GUI
|
||||
volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific);
|
||||
userName.setTomlValue(data, "General", "userName", is_game_specific);
|
||||
userNames.setTomlValue(data, "General", "userNames", is_game_specific);
|
||||
|
||||
cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific);
|
||||
cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific);
|
||||
@@ -845,36 +863,36 @@ void setDefaultValues(bool is_game_specific) {
|
||||
// Entries with game-specific settings that are in the game-specific setings GUI but not in
|
||||
// the global settings GUI
|
||||
if (is_game_specific) {
|
||||
readbacksEnabled.set(false, is_game_specific);
|
||||
readbackLinearImagesEnabled.set(false, is_game_specific);
|
||||
directMemoryAccessEnabled.set(false, is_game_specific);
|
||||
readbacksEnabled.setDefault(is_game_specific);
|
||||
readbackLinearImagesEnabled.setDefault(is_game_specific);
|
||||
directMemoryAccessEnabled.setDefault(is_game_specific);
|
||||
}
|
||||
|
||||
// Entries with game-specific settings that are in both the game-specific and global GUI
|
||||
// GS - General
|
||||
volumeSlider.set(100, is_game_specific);
|
||||
userName.set("shadPS4", is_game_specific);
|
||||
volumeSlider.setDefault(is_game_specific);
|
||||
userNames.setDefault(is_game_specific);
|
||||
|
||||
// GS - Input
|
||||
cursorState.set(HideCursorState::Idle, is_game_specific);
|
||||
cursorHideTimeout.set(5, is_game_specific);
|
||||
isMotionControlsEnabled.set(true, is_game_specific);
|
||||
backgroundControllerInput.set(false, is_game_specific);
|
||||
usbDeviceBackend.set(UsbBackendType::Real, is_game_specific);
|
||||
cursorState.setDefault(is_game_specific);
|
||||
cursorHideTimeout.setDefault(is_game_specific);
|
||||
isMotionControlsEnabled.setDefault(is_game_specific);
|
||||
backgroundControllerInput.setDefault(is_game_specific);
|
||||
usbDeviceBackend.setDefault(is_game_specific);
|
||||
|
||||
// GS - Audio
|
||||
micDevice.set("Default Device", is_game_specific);
|
||||
micDevice.setDefault(is_game_specific);
|
||||
|
||||
// GS - GPU
|
||||
windowWidth.set(1280, is_game_specific);
|
||||
windowHeight.set(720, is_game_specific);
|
||||
shouldCopyGPUBuffers.set(false, is_game_specific);
|
||||
shouldDumpShaders.set(false, is_game_specific);
|
||||
vblankFrequency.set(60, is_game_specific);
|
||||
isHDRAllowed.set(false, is_game_specific);
|
||||
fsrEnabled.set(true, is_game_specific);
|
||||
rcasEnabled.set(true, is_game_specific);
|
||||
rcasAttenuation.set(250, is_game_specific);
|
||||
windowWidth.setDefault(is_game_specific);
|
||||
windowHeight.setDefault(is_game_specific);
|
||||
shouldCopyGPUBuffers.setDefault(is_game_specific);
|
||||
shouldDumpShaders.setDefault(is_game_specific);
|
||||
vblankFrequency.setDefault(is_game_specific);
|
||||
isHDRAllowed.setDefault(is_game_specific);
|
||||
fsrEnabled.setDefault(is_game_specific);
|
||||
rcasEnabled.setDefault(is_game_specific);
|
||||
rcasAttenuation.setDefault(is_game_specific);
|
||||
|
||||
// GS - Vulkan
|
||||
vkValidation.set(false, is_game_specific);
|
||||
@@ -887,7 +905,7 @@ void setDefaultValues(bool is_game_specific) {
|
||||
rdocEnable.set(false, is_game_specific);
|
||||
|
||||
// GS - Settings
|
||||
m_language.set(1, is_game_specific);
|
||||
m_language.setDefault(is_game_specific);
|
||||
|
||||
// All other entries
|
||||
if (!is_game_specific) {
|
||||
@@ -924,6 +942,8 @@ hotkey_pause = f9
|
||||
hotkey_reload_inputs = f8
|
||||
hotkey_toggle_mouse_to_joystick = f7
|
||||
hotkey_toggle_mouse_to_gyro = f6
|
||||
hotkey_add_virtual_user = f5
|
||||
hotkey_remove_virtual_user = f4
|
||||
hotkey_quit = lctrl, lshift, end
|
||||
)";
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ void setUsbDeviceBackend(int value, bool is_game_specific = false);
|
||||
|
||||
// TODO
|
||||
std::filesystem::path GetSaveDataPath();
|
||||
std::string getUserName();
|
||||
std::string getUserName(int id);
|
||||
std::array<std::string, 4> const getUserNames();
|
||||
bool GetUseUnifiedInputConfig();
|
||||
void SetUseUnifiedInputConfig(bool use);
|
||||
bool GetOverrideControllerColor();
|
||||
|
||||
@@ -627,7 +627,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(np_id, 0, sizeof(OrbisNpId));
|
||||
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
|
||||
strncpy(np_id->handle.data, Config::getUserName(user_id - 1).c_str(),
|
||||
sizeof(np_id->handle.data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -641,7 +642,7 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(online_id, 0, sizeof(OrbisNpOnlineId));
|
||||
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
|
||||
strncpy(online_id->data, Config::getUserName(user_id - 1).c_str(), sizeof(online_id->data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
@@ -274,7 +274,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
|
||||
g_opened = true;
|
||||
scePadResetLightBar(userId);
|
||||
scePadResetOrientation(userId);
|
||||
return 1; // dummy
|
||||
return userId; // TODO: userId shouldn't be used as the handle too
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
|
||||
@@ -287,7 +287,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
return 1; // dummy
|
||||
return userId; // dummy
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt2() {
|
||||
@@ -301,12 +301,16 @@ int PS4_SYSV_ABI scePadOutputReport() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
LOG_TRACE(Lib_Pad, "handle: {}", handle);
|
||||
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
Input::State states[64];
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
auto const& controller = controllers[*controller_id];
|
||||
int ret_num = controller->ReadStates(states, num, &connected, &connected_count);
|
||||
|
||||
if (!connected) {
|
||||
@@ -321,9 +325,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
pData[i].rightStick.y = states[i].axes[static_cast<int>(Input::Axis::RightY)];
|
||||
pData[i].analogButtons.l2 = states[i].axes[static_cast<int>(Input::Axis::TriggerLeft)];
|
||||
pData[i].analogButtons.r2 = states[i].axes[static_cast<int>(Input::Axis::TriggerRight)];
|
||||
pData[i].acceleration.x = states[i].acceleration.x;
|
||||
pData[i].acceleration.y = states[i].acceleration.y;
|
||||
pData[i].acceleration.z = states[i].acceleration.z;
|
||||
pData[i].angularVelocity.x = states[i].angularVelocity.x;
|
||||
pData[i].angularVelocity.y = states[i].angularVelocity.y;
|
||||
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
||||
@@ -335,8 +336,8 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
pData[i].angularVelocity.y = states[i].angularVelocity.y;
|
||||
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
||||
|
||||
if (engine && handle == 1) {
|
||||
const auto gyro_poll_rate = engine->GetAccelPollRate();
|
||||
if (handle == 1) {
|
||||
const auto gyro_poll_rate = controller->accel_poll_rate;
|
||||
if (gyro_poll_rate != 0.0f) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
@@ -431,24 +432,34 @@ int PS4_SYSV_ABI scePadReadHistory() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) {
|
||||
LOG_TRACE(Lib_Pad, "handle: {}", handle);
|
||||
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
int connectedCount = 0;
|
||||
bool isConnected = false;
|
||||
Input::State state;
|
||||
auto const& controller = controllers[*controller_id];
|
||||
controller->ReadState(&state, &isConnected, &connectedCount);
|
||||
pData->buttons = state.buttonsState;
|
||||
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
|
||||
pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)];
|
||||
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
|
||||
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
|
||||
pData->rightStick.y = state.axes[static_cast<int>(Input::Axis::RightY)];
|
||||
pData->analogButtons.l2 = state.axes[static_cast<int>(Input::Axis::TriggerLeft)];
|
||||
pData->analogButtons.r2 = state.axes[static_cast<int>(Input::Axis::TriggerRight)];
|
||||
|
||||
auto getAxisValue = [&state, &controller](Input::Axis a) {
|
||||
auto i = static_cast<int>(a);
|
||||
if (controller->axis_smoothing_ticks[i] > 0) {
|
||||
--controller->axis_smoothing_ticks[i];
|
||||
return (state.axes[i] + controller->axis_smoothing_values[i]) / 2;
|
||||
}
|
||||
return state.axes[i];
|
||||
};
|
||||
|
||||
pData->leftStick.x = getAxisValue(Input::Axis::LeftX);
|
||||
pData->leftStick.y = getAxisValue(Input::Axis::LeftY);
|
||||
pData->rightStick.x = getAxisValue(Input::Axis::RightX);
|
||||
pData->rightStick.y = getAxisValue(Input::Axis::RightY);
|
||||
pData->analogButtons.l2 = getAxisValue(Input::Axis::TriggerLeft);
|
||||
pData->analogButtons.r2 = getAxisValue(Input::Axis::TriggerRight);
|
||||
pData->acceleration.x = state.acceleration.x * 0.098;
|
||||
pData->acceleration.y = state.acceleration.y * 0.098;
|
||||
pData->acceleration.z = state.acceleration.z * 0.098;
|
||||
@@ -457,21 +468,19 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
||||
pData->angularVelocity.z = state.angularVelocity.z;
|
||||
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
// Only do this on handle 1 for now
|
||||
if (engine && handle == 1) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller->SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
|
||||
lastOrientation, outputOrientation);
|
||||
pData->orientation = outputOrientation;
|
||||
controller->SetLastOrientation(outputOrientation);
|
||||
}
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller->SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
|
||||
lastOrientation, outputOrientation);
|
||||
pData->orientation = outputOrientation;
|
||||
controller->SetLastOrientation(outputOrientation);
|
||||
|
||||
pData->touchData.touchNum =
|
||||
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
|
||||
|
||||
@@ -536,12 +545,13 @@ int PS4_SYSV_ABI scePadReadStateExt() {
|
||||
|
||||
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "(DUMMY) called");
|
||||
if (handle != 1) {
|
||||
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
int* rgb = Config::GetControllerCustomColor();
|
||||
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
controllers[*controller_id]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -558,7 +568,8 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
|
||||
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
|
||||
|
||||
if (handle != 1) {
|
||||
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -614,6 +625,10 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
|
||||
if (Config::GetOverrideControllerColor()) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (pParam != nullptr) {
|
||||
LOG_DEBUG(Lib_Pad, "called handle = {} rgb = {} {} {}", handle, pParam->r, pParam->g,
|
||||
pParam->b);
|
||||
@@ -623,7 +638,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
|
||||
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@@ -691,7 +706,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP
|
||||
if (pParam != nullptr) {
|
||||
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
|
||||
pParam->smallMotor, pParam->largeMotor);
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/system/userservice_error.h"
|
||||
#include "core/tls.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
namespace Libraries::UserService {
|
||||
|
||||
@@ -105,15 +110,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
std::queue<OrbisUserServiceEvent> user_service_event_queue = {};
|
||||
|
||||
void AddUserServiceEvent(const OrbisUserServiceEvent e) {
|
||||
LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId);
|
||||
user_service_event_queue.push(e);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
|
||||
LOG_TRACE(Lib_UserService, "(DUMMY) called");
|
||||
// fake a loggin event
|
||||
static bool logged_in = false;
|
||||
|
||||
if (!logged_in) {
|
||||
logged_in = true;
|
||||
event->event = OrbisUserServiceEventType::Login;
|
||||
event->userId = 1;
|
||||
if (!user_service_event_queue.empty()) {
|
||||
OrbisUserServiceEvent& temp = user_service_event_queue.front();
|
||||
event->event = temp.event;
|
||||
event->userId = temp.userId;
|
||||
user_service_event_queue.pop();
|
||||
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
|
||||
temp.userId);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -567,17 +580,22 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
|
||||
LOG_DEBUG(Lib_UserService, "called");
|
||||
// LOG_DEBUG(Lib_UserService, "called");
|
||||
if (userIdList == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// TODO only first user, do the others as well
|
||||
userIdList->user_id[0] = 1;
|
||||
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
|
||||
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
int li = 0;
|
||||
for (int ci = 0; ci < 4; ci++) {
|
||||
if (controllers[ci]->user_id != -1) {
|
||||
userIdList->user_id[li++] = controllers[ci]->user_id;
|
||||
}
|
||||
}
|
||||
for (; li < 4; li++) {
|
||||
userIdList->user_id[li] = -1;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -1048,7 +1066,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
|
||||
LOG_ERROR(Lib_UserService, "color is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*color = OrbisUserServiceUserColor::Blue;
|
||||
*color = (OrbisUserServiceUserColor)(user_id - 1);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@@ -1068,12 +1086,12 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
|
||||
if (user_name == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_name is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
std::string name = Config::getUserName();
|
||||
std::string name = Config::getUserName(user_id - 1);
|
||||
if (size < name.length()) {
|
||||
LOG_ERROR(Lib_UserService, "buffer is too short");
|
||||
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// reference :
|
||||
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/user.h
|
||||
@@ -57,6 +57,8 @@ struct OrbisUserServiceEvent {
|
||||
OrbisUserServiceUserId userId;
|
||||
};
|
||||
|
||||
void AddUserServiceEvent(const OrbisUserServiceEvent e);
|
||||
|
||||
int PS4_SYSV_ABI sceUserServiceInitializeForShellCore();
|
||||
int PS4_SYSV_ABI sceUserServiceTerminateForShellCore();
|
||||
int PS4_SYSV_ABI sceUserServiceDestroyUser();
|
||||
|
||||
@@ -259,7 +259,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
|
||||
// Initialize components
|
||||
memory = Core::Memory::Instance();
|
||||
controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controllers = Common::Singleton<Input::GameControllers>::Instance();
|
||||
linker = Common::Singleton<Core::Linker>::Instance();
|
||||
|
||||
// Load renderdoc module
|
||||
@@ -301,7 +301,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
}
|
||||
window = std::make_unique<Frontend::WindowSDL>(
|
||||
Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
|
||||
Config::getWindowWidth(), Config::getWindowHeight(), controllers, window_title);
|
||||
|
||||
g_window = window.get();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -43,7 +43,7 @@ private:
|
||||
void LoadSystemModules(const std::string& game_serial);
|
||||
|
||||
Core::MemoryManager* memory;
|
||||
Input::GameController* controller;
|
||||
Input::GameControllers* controllers;
|
||||
Core::Linker* linker;
|
||||
std::unique_ptr<Frontend::WindowSDL> window;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on imgui_impl_sdl3.cpp from Dear ImGui repository
|
||||
@@ -737,9 +737,8 @@ static void UpdateGamepads() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
SdlData* bd = GetBackendData();
|
||||
|
||||
auto controller = Common::Singleton<Input::GameController>::Instance();
|
||||
auto engine = controller->GetEngine();
|
||||
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
|
||||
auto controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
|
||||
// Update list of gamepads to use
|
||||
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
|
||||
if (SDLGamepad) {
|
||||
|
||||
@@ -1,66 +1,21 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <unordered_set>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <common/singleton.h>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "controller.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
static std::string SelectedGamepad = "";
|
||||
|
||||
namespace Input {
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
|
||||
if (isPressed) {
|
||||
buttonsState |= button;
|
||||
} else {
|
||||
buttonsState &= ~button;
|
||||
}
|
||||
}
|
||||
|
||||
void State::OnAxis(Axis axis, int value) {
|
||||
const auto toggle = [&](const auto button) {
|
||||
if (value > 0) {
|
||||
buttonsState |= button;
|
||||
} else {
|
||||
buttonsState &= ~button;
|
||||
}
|
||||
};
|
||||
switch (axis) {
|
||||
case Axis::TriggerLeft:
|
||||
toggle(OrbisPadButtonDataOffset::L2);
|
||||
break;
|
||||
case Axis::TriggerRight:
|
||||
toggle(OrbisPadButtonDataOffset::R2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
axes[static_cast<int>(axis)] = value;
|
||||
}
|
||||
|
||||
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
|
||||
touchpad[touchIndex].state = isDown;
|
||||
touchpad[touchIndex].x = static_cast<u16>(x * 1920);
|
||||
touchpad[touchIndex].y = static_cast<u16>(y * 941);
|
||||
}
|
||||
|
||||
void State::OnGyro(const float gyro[3]) {
|
||||
angularVelocity.x = gyro[0];
|
||||
angularVelocity.y = gyro[1];
|
||||
angularVelocity.z = gyro[2];
|
||||
}
|
||||
|
||||
void State::OnAccel(const float accel[3]) {
|
||||
acceleration.x = accel[0];
|
||||
acceleration.y = accel[1];
|
||||
acceleration.z = accel[2];
|
||||
}
|
||||
|
||||
GameController::GameController() {
|
||||
m_states_num = 0;
|
||||
m_last_state = State();
|
||||
@@ -110,7 +65,8 @@ State GameController::GetLastState() const {
|
||||
return m_last_state;
|
||||
}
|
||||
const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES;
|
||||
return m_states[last];
|
||||
auto copy = m_states[last];
|
||||
return copy;
|
||||
}
|
||||
|
||||
void GameController::AddState(const State& state) {
|
||||
@@ -126,22 +82,50 @@ void GameController::AddState(const State& state) {
|
||||
m_states_num++;
|
||||
}
|
||||
|
||||
void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||
void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button,
|
||||
bool is_pressed) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto state = GetLastState();
|
||||
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
state.OnButton(button, is_pressed);
|
||||
if (is_pressed) {
|
||||
state.buttonsState |= button;
|
||||
} else {
|
||||
state.buttonsState &= ~button;
|
||||
}
|
||||
|
||||
AddState(state);
|
||||
}
|
||||
|
||||
void GameController::Axis(int id, Input::Axis axis, int value) {
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto state = GetLastState();
|
||||
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
state.OnAxis(axis, value);
|
||||
int axis_id = static_cast<int>(axis);
|
||||
if (std::abs(state.axes[axis_id] - value) > 120) {
|
||||
LOG_DEBUG(Input, "Keyboard axis change detected");
|
||||
axis_smoothing_ticks[axis_id] = GameController::max_smoothing_ticks;
|
||||
axis_smoothing_values[axis_id] = state.axes[axis_id];
|
||||
}
|
||||
state.axes[axis_id] = value;
|
||||
|
||||
if (axis == Input::Axis::TriggerLeft) {
|
||||
if (value > 0) {
|
||||
state.buttonsState |= OrbisPadButtonDataOffset::L2;
|
||||
} else {
|
||||
state.buttonsState &= ~OrbisPadButtonDataOffset::L2;
|
||||
}
|
||||
}
|
||||
|
||||
if (axis == Input::Axis::TriggerRight) {
|
||||
if (value > 0) {
|
||||
state.buttonsState |= OrbisPadButtonDataOffset::R2;
|
||||
} else {
|
||||
state.buttonsState &= ~OrbisPadButtonDataOffset::R2;
|
||||
}
|
||||
}
|
||||
|
||||
AddState(state);
|
||||
}
|
||||
@@ -152,7 +136,9 @@ void GameController::Gyro(int id, const float gyro[3]) {
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Update the angular velocity (gyro data)
|
||||
state.OnGyro(gyro);
|
||||
state.angularVelocity.x = gyro[0]; // X-axis
|
||||
state.angularVelocity.y = gyro[1]; // Y-axis
|
||||
state.angularVelocity.z = gyro[2]; // Z-axis
|
||||
|
||||
AddState(state);
|
||||
}
|
||||
@@ -162,7 +148,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Update the acceleration values
|
||||
state.OnAccel(acceleration);
|
||||
state.acceleration.x = acceleration[0]; // X-axis
|
||||
state.acceleration.y = acceleration[1]; // Y-axis
|
||||
state.acceleration.z = acceleration[2]; // Z-axis
|
||||
|
||||
AddState(state);
|
||||
}
|
||||
@@ -203,33 +191,116 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
|
||||
}
|
||||
|
||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
|
||||
}
|
||||
std::scoped_lock _{m_mutex};
|
||||
m_engine->SetLightBarRGB(r, g, b);
|
||||
}
|
||||
|
||||
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
|
||||
(largeMotor / 255.0f) * 0xFFFF, -1);
|
||||
}
|
||||
std::scoped_lock _{m_mutex};
|
||||
m_engine->SetVibration(smallMotor, largeMotor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||
if (touchIndex < 2) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto state = GetLastState();
|
||||
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||
|
||||
state.touchpad[touchIndex].state = touchDown;
|
||||
state.touchpad[touchIndex].x = static_cast<u16>(x * 1920);
|
||||
state.touchpad[touchIndex].y = static_cast<u16>(y * 941);
|
||||
|
||||
AddState(state);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_first_check = true;
|
||||
|
||||
void GameControllers::TryOpenSDLControllers(GameControllers& controllers) {
|
||||
using namespace Libraries::UserService;
|
||||
int controller_count;
|
||||
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
|
||||
|
||||
std::unordered_set<SDL_JoystickID> assigned_ids;
|
||||
std::array<bool, 4> slot_taken{false, false, false, false};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
|
||||
if (pad) {
|
||||
SDL_JoystickID id = SDL_GetGamepadID(pad);
|
||||
bool still_connected = false;
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
if (new_joysticks[j] == id) {
|
||||
still_connected = true;
|
||||
assigned_ids.insert(id);
|
||||
slot_taken[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!still_connected) {
|
||||
AddUserServiceEvent({OrbisUserServiceEventType::Logout, i + 1});
|
||||
SDL_CloseGamepad(pad);
|
||||
controllers[i]->m_sdl_gamepad = nullptr;
|
||||
controllers[i]->user_id = -1;
|
||||
slot_taken[i] = false;
|
||||
} else {
|
||||
controllers[i]->player_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
SDL_JoystickID id = new_joysticks[j];
|
||||
if (assigned_ids.contains(id))
|
||||
continue;
|
||||
|
||||
SDL_Gamepad* pad = SDL_OpenGamepad(id);
|
||||
if (!pad)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!slot_taken[i]) {
|
||||
auto* c = controllers[i];
|
||||
c->m_sdl_gamepad = pad;
|
||||
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
|
||||
SDL_GetGamepadID(pad));
|
||||
c->user_id = i + 1;
|
||||
slot_taken[i] = true;
|
||||
c->player_index = i;
|
||||
AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1});
|
||||
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||
c->gyro_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
|
||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||
c->accel_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
|
||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_first_check) [[unlikely]] {
|
||||
is_first_check = false;
|
||||
if (controller_count == 0) {
|
||||
controllers[0]->user_id = 1;
|
||||
AddUserServiceEvent({OrbisUserServiceEventType::Login, 1});
|
||||
}
|
||||
}
|
||||
}
|
||||
u8 GameController::GetTouchCount() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_touch_count;
|
||||
@@ -293,21 +364,9 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
|
||||
m_last_update = lastUpdate;
|
||||
}
|
||||
|
||||
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
|
||||
std::scoped_lock _{m_mutex};
|
||||
m_engine = std::move(engine);
|
||||
if (m_engine) {
|
||||
m_engine->Init();
|
||||
}
|
||||
}
|
||||
|
||||
Engine* GameController::GetEngine() {
|
||||
return m_engine.get();
|
||||
}
|
||||
|
||||
u32 GameController::Poll() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_connected) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
if (m_states_num == 0) {
|
||||
auto diff = (time - m_last_state.time) / 1000;
|
||||
@@ -325,6 +384,12 @@ u32 GameController::Poll() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
|
||||
s32 index = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id));
|
||||
LOG_TRACE(Input, "Gamepad index: {}", index);
|
||||
return index;
|
||||
}
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace GamepadSelect {
|
||||
@@ -343,6 +408,12 @@ int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::optional<u8> GetControllerIndexFromUserID(s32 user_id) {
|
||||
if (user_id < 1 || user_id > 4)
|
||||
return std::nullopt;
|
||||
return static_cast<u8>(user_id - 1);
|
||||
}
|
||||
|
||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
|
||||
char GUIDbuf[33];
|
||||
for (int i = 0; i < gamepadCount; i++) {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include "SDL3/SDL_joystick.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
|
||||
struct SDL_Gamepad;
|
||||
|
||||
namespace Input {
|
||||
|
||||
enum class Axis {
|
||||
@@ -30,14 +32,7 @@ struct TouchpadEntry {
|
||||
u16 y{};
|
||||
};
|
||||
|
||||
class State {
|
||||
public:
|
||||
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
|
||||
void OnAxis(Axis, int);
|
||||
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
|
||||
void OnGyro(const float[3]);
|
||||
void OnAccel(const float[3]);
|
||||
|
||||
struct State {
|
||||
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
|
||||
u64 time = 0;
|
||||
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
|
||||
@@ -47,25 +42,16 @@ public:
|
||||
Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
virtual ~Engine() = default;
|
||||
virtual void Init() = 0;
|
||||
virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0;
|
||||
virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0;
|
||||
virtual State ReadState() = 0;
|
||||
virtual float GetAccelPollRate() const = 0;
|
||||
virtual float GetGyroPollRate() const = 0;
|
||||
SDL_Gamepad* m_gamepad;
|
||||
};
|
||||
|
||||
inline int GetAxis(int min, int max, int value) {
|
||||
return std::clamp((255 * (value - min)) / (max - min), 0, 255);
|
||||
int v = (255 * (value - min)) / (max - min);
|
||||
return (v < 0 ? 0 : (v > 255 ? 255 : v));
|
||||
}
|
||||
|
||||
constexpr u32 MAX_STATES = 32;
|
||||
|
||||
class GameController {
|
||||
friend class GameControllers;
|
||||
|
||||
public:
|
||||
GameController();
|
||||
virtual ~GameController() = default;
|
||||
@@ -79,10 +65,8 @@ public:
|
||||
void Gyro(int id, const float gyro[3]);
|
||||
void Acceleration(int id, const float acceleration[3]);
|
||||
void SetLightBarRGB(u8 r, u8 g, u8 b);
|
||||
void SetVibration(u8 smallMotor, u8 largeMotor);
|
||||
bool SetVibration(u8 smallMotor, u8 largeMotor);
|
||||
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
|
||||
void SetEngine(std::unique_ptr<Engine>);
|
||||
Engine* GetEngine();
|
||||
u32 Poll();
|
||||
|
||||
u8 GetTouchCount();
|
||||
@@ -104,6 +88,14 @@ public:
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation);
|
||||
|
||||
float gyro_poll_rate;
|
||||
float accel_poll_rate;
|
||||
u32 user_id = -1; // ORBIS_USER_SERVICE_USER_ID_INVALID
|
||||
SDL_Gamepad* m_sdl_gamepad = nullptr;
|
||||
static constexpr int max_smoothing_ticks = 2;
|
||||
int axis_smoothing_ticks[static_cast<int>(Input::Axis::AxisMax)]{0};
|
||||
int axis_smoothing_values[static_cast<int>(Input::Axis::AxisMax)]{0};
|
||||
|
||||
private:
|
||||
struct StateInternal {
|
||||
bool obtained = false;
|
||||
@@ -125,13 +117,32 @@ private:
|
||||
std::chrono::steady_clock::time_point m_last_update = {};
|
||||
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
std::unique_ptr<Engine> m_engine = nullptr;
|
||||
u8 player_index = -1;
|
||||
};
|
||||
|
||||
class GameControllers {
|
||||
std::array<GameController*, 4> controllers;
|
||||
|
||||
public:
|
||||
GameControllers()
|
||||
: controllers({new GameController(), new GameController(), new GameController(),
|
||||
new GameController()}) {};
|
||||
virtual ~GameControllers() = default;
|
||||
GameController* operator[](const size_t& i) const {
|
||||
if (i > 3) {
|
||||
UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i);
|
||||
}
|
||||
return controllers[i];
|
||||
}
|
||||
static void TryOpenSDLControllers(GameControllers& controllers);
|
||||
static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
|
||||
};
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace GamepadSelect {
|
||||
|
||||
std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
|
||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
|
||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
|
||||
std::string GetSelectedGamepad();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "input_handler.h"
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "common/elf_info.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_mouse.h"
|
||||
|
||||
@@ -61,54 +62,11 @@ std::list<std::pair<InputEvent, bool>> pressed_keys;
|
||||
std::list<InputID> toggled_keys;
|
||||
static std::vector<BindingConnection> connections;
|
||||
|
||||
auto output_array = std::array{
|
||||
// Important: these have to be the first, or else they will update in the wrong order
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||
|
||||
// Button mappings
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||
|
||||
// Axis mappings
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
||||
|
||||
ControllerOutput(HOTKEY_FULLSCREEN),
|
||||
ControllerOutput(HOTKEY_PAUSE),
|
||||
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
||||
ControllerOutput(HOTKEY_QUIT),
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
std::array<ControllerAllOutputs, 4> output_arrays = {
|
||||
ControllerAllOutputs(0),
|
||||
ControllerAllOutputs(1),
|
||||
ControllerAllOutputs(2),
|
||||
ControllerAllOutputs(3),
|
||||
};
|
||||
|
||||
void ControllerOutput::LinkJoystickAxes() {
|
||||
@@ -215,6 +173,14 @@ InputBinding GetBindingFromString(std::string& line) {
|
||||
return InputBinding(keys[0], keys[1], keys[2]);
|
||||
}
|
||||
|
||||
std::optional<int> parseInt(const std::string& s) {
|
||||
try {
|
||||
return std::stoi(s);
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
void ParseInputConfig(const std::string game_id = "") {
|
||||
std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
|
||||
const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default);
|
||||
@@ -270,21 +236,39 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
|
||||
std::string output_string = line.substr(0, equal_pos);
|
||||
std::string input_string = line.substr(equal_pos + 1);
|
||||
// Remove trailing semicolon from input_string
|
||||
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
|
||||
input_string != ";") {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
|
||||
|
||||
// todo: here the inputs and outputs are formatted and split, we need to extract the
|
||||
// controller ID now
|
||||
|
||||
// input gamepad id is only for controllers, it's discarded otherwise
|
||||
std::size_t input_colon_pos = input_string.find(':');
|
||||
if (input_colon_pos != std::string::npos) {
|
||||
auto temp = parseInt(input_string.substr(input_colon_pos + 1));
|
||||
if (!temp) {
|
||||
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
|
||||
} else {
|
||||
input_gamepad_id = *temp;
|
||||
}
|
||||
input_string = input_string.substr(0, input_colon_pos);
|
||||
}
|
||||
|
||||
// if not provided, assume it's for all gamepads, if the input is a controller and that also
|
||||
// doesn't have an ID, and for the first otherwise
|
||||
std::size_t output_colon_pos = output_string.find(':');
|
||||
if (output_colon_pos != std::string::npos) {
|
||||
auto temp = parseInt(output_string.substr(output_colon_pos + 1));
|
||||
if (!temp) {
|
||||
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
|
||||
} else {
|
||||
output_gamepad_id = *temp;
|
||||
}
|
||||
output_string = output_string.substr(0, output_colon_pos);
|
||||
}
|
||||
|
||||
std::size_t comma_pos = input_string.find(',');
|
||||
auto parseInt = [](const std::string& s) -> std::optional<int> {
|
||||
try {
|
||||
return std::stoi(s);
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
// todo: make remapping work for special bindings for gamepads that are not the first
|
||||
if (output_string == "mouse_to_joystick") {
|
||||
if (input_string == "left") {
|
||||
SetMouseToJoystick(1);
|
||||
@@ -307,7 +291,7 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
return;
|
||||
}
|
||||
ControllerOutput* toggle_out =
|
||||
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
|
||||
&*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE));
|
||||
BindingConnection toggle_connection = BindingConnection(
|
||||
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
|
||||
connections.insert(connections.end(), toggle_connection);
|
||||
@@ -400,40 +384,67 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
return;
|
||||
}
|
||||
if (button_it != string_to_cbutton_map.end()) {
|
||||
// todo add new shit here
|
||||
connection = BindingConnection(
|
||||
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
|
||||
connections.insert(connections.end(), connection);
|
||||
binding,
|
||||
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||
ControllerOutput(button_it->second)));
|
||||
|
||||
} else if (axis_it != string_to_axis_map.end()) {
|
||||
// todo add new shit here
|
||||
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
|
||||
connection = BindingConnection(
|
||||
binding,
|
||||
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
|
||||
axis_it->second.axis,
|
||||
axis_it->second.value >= 0)),
|
||||
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
|
||||
axis_it->second.axis,
|
||||
axis_it->second.value >= 0)),
|
||||
value_to_set);
|
||||
connections.insert(connections.end(), connection);
|
||||
} else {
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
return;
|
||||
}
|
||||
// todo make the following: if the input binding contains a controller input, and gamepad ID
|
||||
// isn't specified for either inputs or output (both are -1), then multiply the binding and
|
||||
// add it to all 4 controllers
|
||||
if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1);
|
||||
copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output);
|
||||
connections.push_back(copy);
|
||||
}
|
||||
} else {
|
||||
connections.push_back(connection);
|
||||
}
|
||||
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
|
||||
};
|
||||
while (std::getline(global_config_stream, line)) {
|
||||
ProcessLine();
|
||||
}
|
||||
lineCount = 0;
|
||||
while (std::getline(config_stream, line)) {
|
||||
ProcessLine();
|
||||
}
|
||||
config_stream.close();
|
||||
std::sort(connections.begin(), connections.end());
|
||||
for (auto& c : connections) {
|
||||
// todo add new shit here
|
||||
LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString());
|
||||
}
|
||||
LOG_DEBUG(Input, "Done parsing the input config!");
|
||||
}
|
||||
|
||||
BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) {
|
||||
BindingConnection copy = *this;
|
||||
for (auto& key : copy.binding.keys) {
|
||||
if (key.type == InputType::Controller || key.type == InputType::Axis) {
|
||||
key.gamepad_id = gamepad;
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
u32 GetMouseWheelEvent(const SDL_Event& event) {
|
||||
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
|
||||
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
|
||||
@@ -452,6 +463,8 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
|
||||
}
|
||||
|
||||
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||
// todo add new shit here
|
||||
u8 gamepad = 1;
|
||||
switch (e.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
@@ -466,20 +479,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
return InputEvent(InputType::Controller, static_cast<u32>(e.gbutton.button), e.gbutton.down,
|
||||
0); // clang made me do it
|
||||
gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gbutton.which) + 1;
|
||||
return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down,
|
||||
0);
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
return InputEvent(InputType::Axis, static_cast<u32>(e.gaxis.axis), true,
|
||||
e.gaxis.value / 256); // this too
|
||||
gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gaxis.which) + 1;
|
||||
return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256);
|
||||
default:
|
||||
return InputEvent();
|
||||
}
|
||||
}
|
||||
|
||||
GameController* ControllerOutput::controller = nullptr;
|
||||
void ControllerOutput::SetControllerOutputController(GameController* c) {
|
||||
ControllerOutput::controller = c;
|
||||
}
|
||||
GameControllers ControllerOutput::controllers =
|
||||
*Common::Singleton<Input::GameControllers>::Instance();
|
||||
|
||||
void ToggleKeyInList(InputID input) {
|
||||
if (input.type == InputType::Axis) {
|
||||
@@ -526,7 +538,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
|
||||
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||
}
|
||||
}
|
||||
void ControllerOutput::FinalizeUpdate() {
|
||||
void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
|
||||
auto PushSDLEvent = [&](u32 event_type) {
|
||||
if (new_button_state) {
|
||||
SDL_Event e;
|
||||
@@ -542,6 +554,7 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
old_button_state = new_button_state;
|
||||
old_param = *new_param;
|
||||
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||
auto controller = controllers[gamepad_index];
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
|
||||
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
|
||||
@@ -582,6 +595,12 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_RENDERDOC:
|
||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||
break;
|
||||
case HOTKEY_ADD_VIRTUAL_USER:
|
||||
PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER);
|
||||
break;
|
||||
case HOTKEY_REMOVE_VIRTUAL_USER:
|
||||
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
|
||||
break;
|
||||
case HOTKEY_QUIT:
|
||||
PushSDLEvent(SDL_EVENT_QUIT_DIALOG);
|
||||
break;
|
||||
@@ -622,18 +641,20 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
break;
|
||||
case Axis::TriggerLeft:
|
||||
ApplyDeadzone(new_param, lefttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
||||
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2,
|
||||
*new_param > 0x20);
|
||||
return;
|
||||
case Axis::TriggerRight:
|
||||
ApplyDeadzone(new_param, righttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2,
|
||||
*new_param > 0x20);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
||||
controllers[gamepad_index]->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,22 +786,30 @@ InputEvent BindingConnection::ProcessBinding() {
|
||||
}
|
||||
|
||||
void ActivateOutputsFromInputs() {
|
||||
// Reset values and flags
|
||||
for (auto& it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
for (auto& it : output_array) {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
}
|
||||
// todo find a better solution
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
// Update all outputs
|
||||
for (auto& it : output_array) {
|
||||
it.FinalizeUpdate();
|
||||
// Reset values and flags
|
||||
for (auto& it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
for (auto& it : output_arrays[i].data) {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
// only update this when it's the correct pass
|
||||
if (it.output->gamepad_id == i) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
}
|
||||
}
|
||||
|
||||
// Update all outputs
|
||||
for (auto& it : output_arrays[i].data) {
|
||||
it.FinalizeUpdate(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
@@ -37,6 +38,8 @@
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11
|
||||
#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12
|
||||
|
||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||
@@ -53,6 +56,8 @@
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
|
||||
#define HOTKEY_RENDERDOC 0xf0000008
|
||||
#define HOTKEY_ADD_VIRTUAL_USER 0xf0000009
|
||||
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000a
|
||||
|
||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||
|
||||
@@ -73,21 +78,24 @@ class InputID {
|
||||
public:
|
||||
InputType type;
|
||||
u32 sdl_id;
|
||||
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
|
||||
u8 gamepad_id;
|
||||
InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1)
|
||||
: type(d), sdl_id(i), gamepad_id(g) {}
|
||||
bool operator==(const InputID& o) const {
|
||||
return type == o.type && sdl_id == o.sdl_id;
|
||||
return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id;
|
||||
}
|
||||
bool operator!=(const InputID& o) const {
|
||||
return type != o.type || sdl_id != o.sdl_id;
|
||||
return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id;
|
||||
}
|
||||
bool operator<=(const InputID& o) const {
|
||||
return type <= o.type && sdl_id <= o.sdl_id;
|
||||
return std::tie(gamepad_id, type, sdl_id) <= std::tie(o.gamepad_id, o.type, o.sdl_id);
|
||||
}
|
||||
bool IsValid() const {
|
||||
return *this != InputID();
|
||||
}
|
||||
std::string ToString() {
|
||||
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
|
||||
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,6 +150,8 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
|
||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
|
||||
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
|
||||
};
|
||||
|
||||
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
||||
@@ -392,7 +402,7 @@ public:
|
||||
inline bool IsEmpty() {
|
||||
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
|
||||
}
|
||||
std::string ToString() { // todo add device type
|
||||
std::string ToString() {
|
||||
switch (KeyCount()) {
|
||||
case 1:
|
||||
return fmt::format("({})", keys[0].ToString());
|
||||
@@ -411,14 +421,15 @@ public:
|
||||
};
|
||||
|
||||
class ControllerOutput {
|
||||
static GameController* controller;
|
||||
static GameControllers controllers;
|
||||
|
||||
public:
|
||||
static void SetControllerOutputController(GameController* c);
|
||||
static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {}
|
||||
static void LinkJoystickAxes();
|
||||
|
||||
u32 button;
|
||||
u32 axis;
|
||||
u8 gamepad_id;
|
||||
// these are only used as s8,
|
||||
// but I added some padding to avoid overflow if it's activated by multiple inputs
|
||||
// axis_plus and axis_minus pairs share a common new_param, the other outputs have their own
|
||||
@@ -432,6 +443,7 @@ public:
|
||||
new_param = new s16(0);
|
||||
old_param = 0;
|
||||
positive_axis = p;
|
||||
gamepad_id = 0;
|
||||
}
|
||||
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {
|
||||
new_param = new s16(*o.new_param);
|
||||
@@ -457,7 +469,7 @@ public:
|
||||
|
||||
void ResetUpdate();
|
||||
void AddUpdate(InputEvent event);
|
||||
void FinalizeUpdate();
|
||||
void FinalizeUpdate(u8 gamepad_index);
|
||||
};
|
||||
class BindingConnection {
|
||||
public:
|
||||
@@ -472,6 +484,13 @@ public:
|
||||
output = out;
|
||||
toggle = t;
|
||||
}
|
||||
BindingConnection& operator=(const BindingConnection& o) {
|
||||
binding = o.binding;
|
||||
output = o.output;
|
||||
axis_param = o.axis_param;
|
||||
toggle = o.toggle;
|
||||
return *this;
|
||||
}
|
||||
bool operator<(const BindingConnection& other) const {
|
||||
// a button is a higher priority than an axis, as buttons can influence axes
|
||||
// (e.g. joystick_halfmode)
|
||||
@@ -485,9 +504,79 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HasGamepadInput() {
|
||||
for (auto& key : binding.keys) {
|
||||
if (key.type == InputType::Controller || key.type == InputType::Axis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
BindingConnection CopyWithChangedGamepadId(u8 gamepad);
|
||||
InputEvent ProcessBinding();
|
||||
};
|
||||
|
||||
class ControllerAllOutputs {
|
||||
public:
|
||||
static constexpr u64 output_count = 37;
|
||||
std::array<ControllerOutput, output_count> data = {
|
||||
// Important: these have to be the first, or else they will update in the wrong order
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||
|
||||
// Button mappings
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||
|
||||
// Axis mappings
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
||||
|
||||
ControllerOutput(HOTKEY_FULLSCREEN),
|
||||
ControllerOutput(HOTKEY_PAUSE),
|
||||
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
||||
ControllerOutput(HOTKEY_QUIT),
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
|
||||
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
};
|
||||
ControllerAllOutputs(u8 g) {
|
||||
for (int i = 0; i < output_count; i++) {
|
||||
data[i].gamepad_id = g;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Updates the list of pressed keys with the given input.
|
||||
// Returns whether the list was updated or not.
|
||||
bool UpdatePressedKeys(InputEvent event);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_handler.h"
|
||||
@@ -26,258 +27,55 @@
|
||||
#include "SDL3/SDL_metal.h"
|
||||
#endif
|
||||
|
||||
namespace Input {
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
|
||||
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
using OPBDO = OrbisPadButtonDataOffset;
|
||||
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return OPBDO::Down;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return OPBDO::Up;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return OPBDO::Left;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return OPBDO::Right;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
return OPBDO::Cross;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH:
|
||||
return OPBDO::Triangle;
|
||||
case SDL_GAMEPAD_BUTTON_WEST:
|
||||
return OPBDO::Square;
|
||||
case SDL_GAMEPAD_BUTTON_EAST:
|
||||
return OPBDO::Circle;
|
||||
case SDL_GAMEPAD_BUTTON_START:
|
||||
return OPBDO::Options;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
||||
return OPBDO::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
return OPBDO::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return OPBDO::L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return OPBDO::R1;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
return OPBDO::L3;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||
return OPBDO::R3;
|
||||
default:
|
||||
return OPBDO::None;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
|
||||
switch (axis) {
|
||||
case Axis::LeftX:
|
||||
return SDL_GAMEPAD_AXIS_LEFTX;
|
||||
case Axis::LeftY:
|
||||
return SDL_GAMEPAD_AXIS_LEFTY;
|
||||
case Axis::RightX:
|
||||
return SDL_GAMEPAD_AXIS_RIGHTX;
|
||||
case Axis::RightY:
|
||||
return SDL_GAMEPAD_AXIS_RIGHTY;
|
||||
case Axis::TriggerLeft:
|
||||
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
|
||||
case Axis::TriggerRight:
|
||||
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
SDLInputEngine::~SDLInputEngine() {
|
||||
if (m_gamepad) {
|
||||
SDL_CloseGamepad(m_gamepad);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::Init() {
|
||||
if (m_gamepad) {
|
||||
SDL_CloseGamepad(m_gamepad);
|
||||
m_gamepad = nullptr;
|
||||
}
|
||||
|
||||
int gamepad_count;
|
||||
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
|
||||
if (!gamepads) {
|
||||
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
if (gamepad_count == 0) {
|
||||
LOG_INFO(Input, "No gamepad found!");
|
||||
SDL_free(gamepads);
|
||||
return;
|
||||
}
|
||||
|
||||
int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count,
|
||||
GamepadSelect::GetSelectedGamepad());
|
||||
int defaultIndex =
|
||||
GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID());
|
||||
|
||||
// If user selects a gamepad in the GUI, use that, otherwise try the default
|
||||
if (!m_gamepad) {
|
||||
if (selectedIndex != -1) {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]);
|
||||
LOG_INFO(Input, "Opening gamepad selected in GUI.");
|
||||
} else if (defaultIndex != -1) {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]);
|
||||
LOG_INFO(Input, "Opening default gamepad.");
|
||||
} else {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[0]);
|
||||
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_gamepad) {
|
||||
if (!m_gamepad) {
|
||||
LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError());
|
||||
SDL_free(gamepads);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
|
||||
Uint16 vendor = SDL_GetJoystickVendor(joystick);
|
||||
Uint16 product = SDL_GetJoystickProduct(joystick);
|
||||
|
||||
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
|
||||
|
||||
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
|
||||
if (isDualSense) {
|
||||
LOG_INFO(Input, "Detected DualSense Controller");
|
||||
}
|
||||
|
||||
if (Config::getIsMotionControlsEnabled()) {
|
||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
|
||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
|
||||
SDL_GetError());
|
||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
|
||||
}
|
||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
|
||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
|
||||
SDL_GetError());
|
||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(gamepads);
|
||||
|
||||
int* rgb = Config::GetControllerCustomColor();
|
||||
|
||||
if (isDualSense) {
|
||||
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
|
||||
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
|
||||
}
|
||||
} else {
|
||||
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
if (m_gamepad) {
|
||||
SDL_SetGamepadLED(m_gamepad, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_gamepad) {
|
||||
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
|
||||
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
|
||||
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
|
||||
}
|
||||
}
|
||||
|
||||
State SDLInputEngine::ReadState() {
|
||||
State state{};
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Buttons
|
||||
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
|
||||
auto orbisButton = SDLGamepadToOrbisButton(i);
|
||||
if (orbisButton == OrbisPadButtonDataOffset::None) {
|
||||
continue;
|
||||
}
|
||||
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
|
||||
}
|
||||
|
||||
// Axes
|
||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
|
||||
const auto axis = static_cast<Axis>(i);
|
||||
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
|
||||
switch (axis) {
|
||||
case Axis::TriggerLeft:
|
||||
case Axis::TriggerRight:
|
||||
state.OnAxis(axis, GetAxis(0, 0x8000, value));
|
||||
break;
|
||||
default:
|
||||
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Touchpad
|
||||
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
|
||||
for (int finger = 0; finger < 2; ++finger) {
|
||||
bool down;
|
||||
float x, y;
|
||||
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
|
||||
state.OnTouchpad(finger, down, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gyro
|
||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
|
||||
float gyro[3];
|
||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
|
||||
state.OnGyro(gyro);
|
||||
}
|
||||
}
|
||||
|
||||
// Accel
|
||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
|
||||
float accel[3];
|
||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
|
||||
state.OnAccel(accel);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
float SDLInputEngine::GetGyroPollRate() const {
|
||||
return m_gyro_poll_rate;
|
||||
}
|
||||
|
||||
float SDLInputEngine::GetAccelPollRate() const {
|
||||
return m_accel_poll_rate;
|
||||
}
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
using namespace Libraries::Pad;
|
||||
|
||||
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return OrbisPadButtonDataOffset::Down;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return OrbisPadButtonDataOffset::Up;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return OrbisPadButtonDataOffset::Left;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return OrbisPadButtonDataOffset::Right;
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
return OrbisPadButtonDataOffset::Cross;
|
||||
case SDL_GAMEPAD_BUTTON_NORTH:
|
||||
return OrbisPadButtonDataOffset::Triangle;
|
||||
case SDL_GAMEPAD_BUTTON_WEST:
|
||||
return OrbisPadButtonDataOffset::Square;
|
||||
case SDL_GAMEPAD_BUTTON_EAST:
|
||||
return OrbisPadButtonDataOffset::Circle;
|
||||
case SDL_GAMEPAD_BUTTON_START:
|
||||
return OrbisPadButtonDataOffset::Options;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
||||
return OrbisPadButtonDataOffset::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
return OrbisPadButtonDataOffset::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return OrbisPadButtonDataOffset::L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return OrbisPadButtonDataOffset::R1;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
return OrbisPadButtonDataOffset::L3;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
|
||||
return OrbisPadButtonDataOffset::R3;
|
||||
default:
|
||||
return OrbisPadButtonDataOffset::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
||||
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||
return controller->Poll();
|
||||
}
|
||||
|
||||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
|
||||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
|
||||
std::string_view window_title)
|
||||
: width{width_}, height{height_}, controller{controller_} {
|
||||
: width{width_}, height{height_}, controllers{*controllers_} {
|
||||
if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
|
||||
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError());
|
||||
}
|
||||
@@ -323,7 +121,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
||||
SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen());
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32)
|
||||
window_info.type = WindowSystemType::Windows;
|
||||
@@ -348,9 +145,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
||||
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
|
||||
#endif
|
||||
// input handler init-s
|
||||
Input::ControllerOutput::SetControllerOutputController(controller);
|
||||
Input::ControllerOutput::LinkJoystickAxes();
|
||||
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
Input::GameControllers::TryOpenSDLControllers(controllers);
|
||||
|
||||
if (Config::getBackgroundControllerInput()) {
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
@@ -392,34 +189,32 @@ void WindowSDL::WaitEvent() {
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
controller->SetTouchpadState(event.gtouchpad.finger,
|
||||
event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event.gtouchpad.x,
|
||||
event.gtouchpad.y);
|
||||
// todo handle userserviceevents here
|
||||
Input::GameControllers::TryOpenSDLControllers(controllers);
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
OnGamepadEvent(&event);
|
||||
break;
|
||||
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS
|
||||
// AND IT DOESN'T EVEN USE PROPER ENUMS
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
|
||||
int controller_id =
|
||||
Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which);
|
||||
switch ((SDL_SensorType)event.gsensor.sensor) {
|
||||
case SDL_SENSOR_GYRO:
|
||||
controller->Gyro(0, event.gsensor.data);
|
||||
controllers[controller_id]->Gyro(0, event.gsensor.data);
|
||||
break;
|
||||
case SDL_SENSOR_ACCEL:
|
||||
controller->Acceleration(0, event.gsensor.data);
|
||||
controllers[controller_id]->Acceleration(0, event.gsensor.data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_QUIT:
|
||||
is_open = false;
|
||||
break;
|
||||
@@ -444,7 +239,7 @@ void WindowSDL::WaitEvent() {
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_CHANGE_CONTROLLER:
|
||||
controller->GetEngine()->Init();
|
||||
UNREACHABLE_MSG("todo");
|
||||
break;
|
||||
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
|
||||
Overlay::ToggleSimpleFps();
|
||||
@@ -460,6 +255,29 @@ void WindowSDL::WaitEvent() {
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
||||
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
|
||||
break;
|
||||
case SDL_EVENT_ADD_VIRTUAL_USER:
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (controllers[i]->user_id == -1) {
|
||||
controllers[i]->user_id = i + 1;
|
||||
Libraries::UserService::AddUserServiceEvent(
|
||||
{Libraries::UserService::OrbisUserServiceEventType::Login,
|
||||
(s32)controllers[i]->user_id});
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_REMOVE_VIRTUAL_USER:
|
||||
LOG_INFO(Input, "Remove user");
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
if (controllers[i]->user_id != -1) {
|
||||
Libraries::UserService::AddUserServiceEvent(
|
||||
{Libraries::UserService::OrbisUserServiceEventType::Logout,
|
||||
(s32)controllers[i]->user_id});
|
||||
controllers[i]->user_id = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_RDOC_CAPTURE:
|
||||
VideoCore::TriggerCapture();
|
||||
break;
|
||||
@@ -469,8 +287,10 @@ void WindowSDL::WaitEvent() {
|
||||
}
|
||||
|
||||
void WindowSDL::InitTimers() {
|
||||
SDL_AddTimer(100, &PollController, controller);
|
||||
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
SDL_AddTimer(250, &PollController, controllers[i]);
|
||||
}
|
||||
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
|
||||
}
|
||||
|
||||
void WindowSDL::RequestKeyboard() {
|
||||
@@ -538,10 +358,38 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
|
||||
// as it would break the entire touchpad handling
|
||||
// You can still bind other things to it though
|
||||
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
|
||||
controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
|
||||
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gbutton.which)]
|
||||
->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||
switch ((SDL_SensorType)event->gsensor.sensor) {
|
||||
case SDL_SENSOR_GYRO:
|
||||
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
|
||||
->Gyro(0, event->gsensor.data);
|
||||
break;
|
||||
case SDL_SENSOR_ACCEL:
|
||||
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)]
|
||||
->Acceleration(0, event->gsensor.data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
|
||||
->SetTouchpadState(event->gtouchpad.finger,
|
||||
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
|
||||
event->gtouchpad.y);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// add/remove it from the list
|
||||
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -14,23 +14,8 @@ struct SDL_Gamepad;
|
||||
union SDL_Event;
|
||||
|
||||
namespace Input {
|
||||
|
||||
class SDLInputEngine : public Engine {
|
||||
public:
|
||||
~SDLInputEngine() override;
|
||||
void Init() override;
|
||||
void SetLightBarRGB(u8 r, u8 g, u8 b) override;
|
||||
void SetVibration(u8 smallMotor, u8 largeMotor) override;
|
||||
float GetGyroPollRate() const override;
|
||||
float GetAccelPollRate() const override;
|
||||
State ReadState() override;
|
||||
|
||||
private:
|
||||
float m_gyro_poll_rate = 0.0f;
|
||||
float m_accel_poll_rate = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace Input
|
||||
class GameController;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
@@ -62,7 +47,7 @@ class WindowSDL {
|
||||
int keyboard_grab = 0;
|
||||
|
||||
public:
|
||||
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller,
|
||||
explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers,
|
||||
std::string_view window_title);
|
||||
~WindowSDL();
|
||||
|
||||
@@ -100,7 +85,7 @@ private:
|
||||
private:
|
||||
s32 width;
|
||||
s32 height;
|
||||
Input::GameController* controller;
|
||||
Input::GameControllers controllers{};
|
||||
WindowSystemInfo window_info{};
|
||||
SDL_Window* window{};
|
||||
bool is_shown{};
|
||||
|
||||
Reference in New Issue
Block a user