mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
Input mapping tabs UI, emulated touchpad, and rumble for non-PS5 controllers (#1541)
* Revamp input mapping dialog with tabs, gamepad remapping and gyro emulation toggle Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add emulated touchpad (mouse/gamepad) with particle skins; widen input dialog Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Disable mouse touchpad mode when the cursor leaves the window Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add touchpad hand-cursor toggle; share touchpad options across both tabs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Hide touchpad trail and particles when the visual toggle is off Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Rename input mapping tabs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Rumble non-DualSense gamepads from haptic audio Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix input mapping, touchpad transitions, and rumble fallback --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
+101
-1
@@ -126,6 +126,7 @@ public:
|
||||
void Motion(int id, Sensor sensor, const float* data, uint64_t time_us);
|
||||
void SetMotionSensorState(bool enable);
|
||||
void ResetOrientation();
|
||||
void ToggleGyroEmulation(int id);
|
||||
void ResetInputState();
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
@@ -147,6 +148,8 @@ private:
|
||||
|
||||
void CheckActive();
|
||||
void AddState();
|
||||
// Emulated tilt: the right stick drives pitch and roll instead of the stick axes.
|
||||
void UpdateEmulatedTilt();
|
||||
// Apply cached output without changing its game-requested lifetime; caller holds m_mutex.
|
||||
void ApplyVibration();
|
||||
bool SendTriggerEffect(const PadTriggerEffectParam& param);
|
||||
@@ -160,6 +163,9 @@ private:
|
||||
int m_connected_count = 0;
|
||||
bool m_motion_enabled = true;
|
||||
uint64_t m_gyro_time = 0;
|
||||
bool m_gyro_emulation = false;
|
||||
uint64_t m_emulated_time = 0;
|
||||
std::array<int, 2> m_emulated_stick {128, 128};
|
||||
// Accelerometer gravity direction in the reset frame.
|
||||
std::array<float, 3> m_up_reference {0.0f, 1.0f, 0.0f};
|
||||
bool m_up_reference_valid = false;
|
||||
@@ -479,6 +485,9 @@ void GameController::CheckActive() {
|
||||
m_connected = new_connected;
|
||||
m_state = {};
|
||||
m_gyro_time = 0;
|
||||
m_gyro_emulation = false;
|
||||
m_emulated_time = 0;
|
||||
m_emulated_stick = {128, 128};
|
||||
m_up_reference_valid = false;
|
||||
m_states_num = 0;
|
||||
m_first_state = 0;
|
||||
@@ -524,6 +533,12 @@ void GameController::Axis(int id, Controller::Axis axis, int value) {
|
||||
|
||||
EXIT_IF(axis_id < 0 || axis_id >= static_cast<int>(Controller::Axis::AxisMax));
|
||||
|
||||
if (m_gyro_emulation && (axis == Controller::Axis::RightX || axis == Controller::Axis::RightY)) {
|
||||
m_emulated_stick[axis == Controller::Axis::RightY ? 1 : 0] = value;
|
||||
UpdateEmulatedTilt();
|
||||
return;
|
||||
}
|
||||
|
||||
m_state.axes[axis_id] = value;
|
||||
|
||||
uint32_t trigger = 0;
|
||||
@@ -545,7 +560,12 @@ void GameController::RightStick(int id, int x, int y) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (m_active_id == id || id == HOST_INPUT_CONTROLLER_ID) {
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
if (m_gyro_emulation) {
|
||||
m_emulated_stick = {x, y};
|
||||
UpdateEmulatedTilt();
|
||||
return;
|
||||
}
|
||||
m_state.axes[static_cast<int>(Axis::RightX)] = x;
|
||||
m_state.axes[static_cast<int>(Axis::RightY)] = y;
|
||||
AddState();
|
||||
@@ -677,6 +697,82 @@ void GameController::SetMotionSensorState(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
static Quat QuatMultiply(const Quat& a, const Quat& b) {
|
||||
return {a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
|
||||
a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
|
||||
a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
|
||||
a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2]};
|
||||
}
|
||||
|
||||
void GameController::UpdateEmulatedTilt() {
|
||||
constexpr float max_angle = 0.7853982f; // 45 degrees at full stick deflection
|
||||
const auto angle = [](int value) {
|
||||
return std::clamp((value - 128) / 127.0f, -1.0f, 1.0f) * max_angle;
|
||||
};
|
||||
const float roll = angle(m_emulated_stick[0]);
|
||||
const float pitch = angle(m_emulated_stick[1]);
|
||||
|
||||
const Quat q_pitch {std::sin(pitch * 0.5f), 0.0f, 0.0f, std::cos(pitch * 0.5f)};
|
||||
const Quat q_roll {0.0f, 0.0f, std::sin(-roll * 0.5f), std::cos(-roll * 0.5f)};
|
||||
Quat q = QuatMultiply(q_roll, q_pitch);
|
||||
|
||||
const uint64_t now = LibKernel::KernelGetProcessTime();
|
||||
Vec3 rate {};
|
||||
if (m_emulated_time != 0 && now > m_emulated_time) {
|
||||
const float dt = std::clamp(static_cast<float>(now - m_emulated_time) * 0.000001f,
|
||||
0.001f, 0.1f);
|
||||
auto delta = QuatMultiply(QuatConjugate(m_state.orientation), q);
|
||||
if (delta[3] < 0.0f) {
|
||||
for (auto& v: delta) {
|
||||
v = -v;
|
||||
}
|
||||
}
|
||||
rate = {2.0f * delta[0] / dt, 2.0f * delta[1] / dt, 2.0f * delta[2] / dt};
|
||||
}
|
||||
m_emulated_time = now;
|
||||
|
||||
m_state.time = now;
|
||||
m_state.orientation = q;
|
||||
m_state.accel = QuatRotate(QuatConjugate(q), {0.0f, 1.0f, 0.0f});
|
||||
m_state.gyro = rate;
|
||||
AddState();
|
||||
// Angular velocity is only nonzero while the tilt changes.
|
||||
m_state.gyro = {};
|
||||
}
|
||||
|
||||
void GameController::ToggleGyroEmulation(int id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (id != HOST_INPUT_CONTROLLER_ID && id != m_active_id) {
|
||||
return;
|
||||
}
|
||||
// Pads with a real gyroscope report it directly.
|
||||
if (m_active_id != HOST_INPUT_CONTROLLER_ID) {
|
||||
auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
if (pad != nullptr && SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_gyro_emulation = !m_gyro_emulation;
|
||||
m_emulated_time = 0;
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
if (m_gyro_emulation) {
|
||||
m_emulated_stick = {m_state.axes[static_cast<int>(Axis::RightX)],
|
||||
m_state.axes[static_cast<int>(Axis::RightY)]};
|
||||
m_state.axes[static_cast<int>(Axis::RightX)] = 128;
|
||||
m_state.axes[static_cast<int>(Axis::RightY)] = 128;
|
||||
UpdateEmulatedTilt();
|
||||
} else {
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_state.accel = {0.0f, 1.0f, 0.0f};
|
||||
m_state.gyro = {};
|
||||
m_state.axes[static_cast<int>(Axis::RightX)] = m_emulated_stick[0];
|
||||
m_state.axes[static_cast<int>(Axis::RightY)] = m_emulated_stick[1];
|
||||
AddState();
|
||||
}
|
||||
LOGF("Emulated gyro tilt: %s\n", m_gyro_emulation ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
void GameController::ResetOrientation() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
@@ -931,6 +1027,10 @@ void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us) {
|
||||
g_controller->Motion(id, sensor, data, time_us);
|
||||
}
|
||||
|
||||
void ToggleGyroEmulation(int id) {
|
||||
g_controller->ToggleGyroEmulation(id);
|
||||
}
|
||||
|
||||
void ResetInputState() {
|
||||
g_controller->ResetInputState();
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ void SetAxis(int id, Axis axis, int value);
|
||||
void SetRightStick(int id, int x, int y);
|
||||
void SetTouchPad(int id, int finger, bool down, float x, float y);
|
||||
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us);
|
||||
void ToggleGyroEmulation(int id);
|
||||
void ResetInputState();
|
||||
int GetActiveControllerId();
|
||||
|
||||
|
||||
@@ -326,6 +326,33 @@ uint64_t Queue(Stream* stream, int controller, const void* data, uint32_t frames
|
||||
if (frames == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (!stream->speaker &&
|
||||
SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
CloseDevice(stream);
|
||||
stream->next_check = 0;
|
||||
// Non-DualSense pads have no haptic audio path: approximate it with the rumble motors.
|
||||
float peak[2] = {0.0f, 0.0f};
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < 2; ch++) {
|
||||
const auto src_ch = channels == 1 ? 0 : ch;
|
||||
const auto index = (static_cast<size_t>(first_frame) + frame) * channels + src_ch;
|
||||
float value = is_float ? static_cast<const float*>(data)[index]
|
||||
: static_cast<const int16_t*>(data)[index] / 32768.0f;
|
||||
value *= volume[src_ch] / 32768.0f * gain * (gains != nullptr ? gains[src_ch] : 1.0f);
|
||||
peak[ch] = std::max(peak[ch], std::fabs(value));
|
||||
}
|
||||
}
|
||||
const auto to_motor = [](float v) {
|
||||
return static_cast<uint16_t>(std::clamp(v * 2.0f, 0.0f, 1.0f) * 65535.0f);
|
||||
};
|
||||
SDL_LockJoysticks();
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(controller));
|
||||
pad != nullptr) {
|
||||
(void) SDL_RumbleGamepad(pad, to_motor(peak[0]), to_motor(peak[1]), 100);
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
return static_cast<uint64_t>(frames) * 1000000 / stream->freq;
|
||||
}
|
||||
const bool wireless = IsWireless(controller);
|
||||
if (!wireless && !CanUseDevice(controller)) {
|
||||
CloseDevice(stream);
|
||||
|
||||
Reference in New Issue
Block a user