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:
Felipe
2026-10-15 23:04:37 -03:00
committed by GitHub
parent 6a0a666a41
commit 3ff6606fcb
18 changed files with 1909 additions and 210 deletions
+567 -3
View File
@@ -12,8 +12,10 @@
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
namespace Libs::Graphics {
@@ -27,6 +29,8 @@ struct ControlInfo {
float touch_x = 0.0f;
Controller::Setting setting {};
bool gyro = false;
bool touchpad_mode = false;
};
static constexpr std::array CONTROL_INFO = {
@@ -61,10 +65,66 @@ static constexpr std::array CONTROL_INFO = {
ControlInfo {.name = "VibrationIntensity", .setting = Controller::Setting::VibrationIntensity},
ControlInfo {.name = "TriggerEffectIntensity",
.setting = Controller::Setting::TriggerEffectIntensity},
ControlInfo {.name = "GyroToggle", .gyro = true},
ControlInfo {.name = "TouchPadMode", .touchpad_mode = true},
};
constexpr std::size_t INVALID_CONTROL = CONTROL_INFO.size();
struct PadSource {
std::string_view name;
int button; // SDL_GamepadButton, or -1 for an analog trigger
};
constexpr std::array PAD_SOURCES = {
PadSource {"South", SDL_GAMEPAD_BUTTON_SOUTH},
PadSource {"East", SDL_GAMEPAD_BUTTON_EAST},
PadSource {"West", SDL_GAMEPAD_BUTTON_WEST},
PadSource {"North", SDL_GAMEPAD_BUTTON_NORTH},
PadSource {"Back", SDL_GAMEPAD_BUTTON_BACK},
PadSource {"Guide", SDL_GAMEPAD_BUTTON_GUIDE},
PadSource {"Start", SDL_GAMEPAD_BUTTON_START},
PadSource {"LeftStick", SDL_GAMEPAD_BUTTON_LEFT_STICK},
PadSource {"RightStick", SDL_GAMEPAD_BUTTON_RIGHT_STICK},
PadSource {"LeftShoulder", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
PadSource {"RightShoulder", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
PadSource {"DpadUp", SDL_GAMEPAD_BUTTON_DPAD_UP},
PadSource {"DpadDown", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
PadSource {"DpadLeft", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
PadSource {"DpadRight", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
PadSource {"Misc1", SDL_GAMEPAD_BUTTON_MISC1},
PadSource {"RightPaddle1", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
PadSource {"LeftPaddle1", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
PadSource {"RightPaddle2", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
PadSource {"LeftPaddle2", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
PadSource {"Touchpad", SDL_GAMEPAD_BUTTON_TOUCHPAD},
PadSource {"LeftTrigger", -1},
PadSource {"RightTrigger", -1},
};
constexpr std::size_t LEFT_TRIGGER_SOURCE = PAD_SOURCES.size() - 2;
constexpr std::size_t RIGHT_TRIGGER_SOURCE = PAD_SOURCES.size() - 1;
constexpr std::size_t INVALID_SOURCE = PAD_SOURCES.size();
struct PadDefault {
std::string_view control;
std::string_view source;
};
constexpr std::array PAD_DEFAULTS = {
PadDefault {"Cross", "South"}, PadDefault {"Circle", "East"},
PadDefault {"Square", "West"}, PadDefault {"Triangle", "North"},
PadDefault {"Options", "Start"}, PadDefault {"L3", "LeftStick"},
PadDefault {"R3", "RightStick"}, PadDefault {"L1", "LeftShoulder"},
PadDefault {"R1", "RightShoulder"}, PadDefault {"L2", "LeftTrigger"},
PadDefault {"R2", "RightTrigger"}, PadDefault {"Up", "DpadUp"},
PadDefault {"Down", "DpadDown"}, PadDefault {"Left", "DpadLeft"},
PadDefault {"Right", "DpadRight"}, PadDefault {"TouchPad", "Touchpad"},
PadDefault {"GyroToggle", "Back"},
};
constexpr std::string_view PAD_PREFIX = "Pad:";
struct Binding {
SDL_Keycode key = SDLK_UNKNOWN;
uint8_t mouse_button = 0;
@@ -74,6 +134,7 @@ struct Binding {
constexpr int MOUSE_POLL_INTERVAL_MS = 33;
constexpr uint64_t CURSOR_IDLE_HIDE_MS = 2000;
constexpr std::string_view MOUSE_SENSITIVITY = "MouseSensitivity=";
constexpr std::string_view TOUCH_PARTICLE_SKIN = "TouchParticleSkin=";
struct MouseJoystickState {
bool enabled = false;
@@ -85,6 +146,38 @@ MouseJoystickState g_mouse;
SDL_Window* g_mouse_window = nullptr;
uint64_t g_cursor_hide_at = 0;
struct MouseTouchPadState {
bool enabled = false;
bool cursor_visible = true;
bool drawing = false;
bool position_valid = false;
float x = 0.5f;
float y = 0.5f;
uint64_t revision = 0;
uint64_t gesture = 0;
};
struct GamepadTouchPadState {
bool enabled = false;
bool drawing = false;
int id = -1;
int x_axis = 128;
int y_axis = 128;
float x = 0.5f;
float y = 0.5f;
uint64_t next_poll = 0;
uint64_t last_poll = 0;
};
constexpr int GAMEPAD_TOUCHPAD_POLL_MS = 8;
constexpr int GAMEPAD_TOUCHPAD_DEADZONE = 18;
constexpr float GAMEPAD_TOUCHPAD_SPEED = 0.8f;
MouseTouchPadState g_mouse_touchpad;
GamepadTouchPadState g_gamepad_touchpad;
std::mutex g_touchpad_mutex;
std::unordered_map<int, std::array<bool, 2>> g_pad_trigger_pressed;
std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
[name](const auto& item) { return item.name == name; });
@@ -126,6 +219,14 @@ public:
InputMap() {
for (const auto& value: Config::GetKeymap()) {
const std::string_view entry = value;
if (entry == "Keyboard=Disabled") {
m_custom = true;
continue;
}
if (entry == "Pad=Disabled") {
m_custom_pad = true;
continue;
}
if (entry.starts_with(MOUSE_SENSITIVITY)) {
const float sensitivity =
std::strtof(value.c_str() + MOUSE_SENSITIVITY.size(), nullptr);
@@ -133,6 +234,13 @@ public:
std::clamp(std::isfinite(sensitivity) ? sensitivity : 1.0f, 0.1f, 5.0f);
continue;
}
if (entry.starts_with(TOUCH_PARTICLE_SKIN) || entry.starts_with("TouchCursorImage=")) {
continue;
}
if (entry.starts_with(PAD_PREFIX)) {
AddPadEntry(entry.substr(PAD_PREFIX.size()), value);
continue;
}
const auto split = entry.find('=');
Binding binding;
@@ -153,10 +261,25 @@ public:
}
Add(binding);
}
if (!m_custom_pad) {
for (const auto& item: PAD_DEFAULTS) {
AssignPad(ControlFromName(item.control), SourceFromName(item.source));
}
}
}
[[nodiscard]] bool Custom() const { return m_size != 0; }
[[nodiscard]] bool Custom() const { return m_custom; }
[[nodiscard]] float MouseSensitivity() const { return m_mouse_sensitivity; }
[[nodiscard]] std::size_t PadControl(std::size_t source) const {
return source < m_pad_controls.size() ? m_pad_controls[source] : INVALID_CONTROL;
}
static std::size_t SourceFromButton(int button) {
const auto item = std::find_if(PAD_SOURCES.begin(), PAD_SOURCES.end(),
[button](const auto& s) { return s.button == button; });
return static_cast<std::size_t>(std::distance(PAD_SOURCES.begin(), item));
}
[[nodiscard]] std::size_t FindKey(int key_code) const {
key_code = NormalizeKey(static_cast<SDL_Keycode>(key_code));
const auto binding =
@@ -173,7 +296,39 @@ public:
}
private:
static std::size_t SourceFromName(std::string_view name) {
const auto item = std::find_if(PAD_SOURCES.begin(), PAD_SOURCES.end(),
[name](const auto& s) { return s.name == name; });
return static_cast<std::size_t>(std::distance(PAD_SOURCES.begin(), item));
}
void AssignPad(std::size_t control, std::size_t source) {
for (auto& existing: m_pad_controls) {
if (existing == control) {
existing = INVALID_CONTROL;
}
}
m_pad_controls[source] = control;
}
void AddPadEntry(std::string_view entry, const std::string& original) {
const auto split = entry.find('=');
const auto control = split != std::string_view::npos ? ControlFromName(entry.substr(0, split))
: INVALID_CONTROL;
const auto source = split != std::string_view::npos ? SourceFromName(entry.substr(split + 1))
: INVALID_SOURCE;
if (control == INVALID_CONTROL || source == INVALID_SOURCE) {
EXIT("Invalid input mapping: %s\n", original.c_str());
}
const auto& info = CONTROL_INFO[control];
if (info.button == 0 && !info.gyro && info.axis != Controller::Axis::AxisMax) {
EXIT("Invalid input mapping: %s\n", original.c_str());
}
m_custom_pad = true;
AssignPad(control, source);
}
void Add(const Binding& binding) {
m_custom = true;
for (std::size_t index = 0; index < m_size;) {
if (Conflicts(m_bindings[index], binding)) {
m_bindings[index] = m_bindings[--m_size];
@@ -186,6 +341,13 @@ private:
}
std::array<Binding, CONTROL_INFO.size()> m_bindings {};
std::array<std::size_t, PAD_SOURCES.size()> m_pad_controls = [] {
std::array<std::size_t, PAD_SOURCES.size()> a {};
a.fill(INVALID_CONTROL);
return a;
}();
bool m_custom = false;
bool m_custom_pad = false;
std::size_t m_size = 0;
float m_mouse_sensitivity = 1.0f;
};
@@ -211,6 +373,39 @@ void SetTouchPad(float x, bool down) {
Controller::SetTouchPad(Controller::HOST_INPUT_CONTROLLER_ID, 0, down, x, 0.5f);
}
void SetSwipeTouchPad(bool down, float x, float y) {
Controller::SetTouchPad(Controller::HOST_INPUT_CONTROLLER_ID, 1, down, x, y);
}
void RecordMouseTouchPadPosition(float x, float y) {
int width = 0;
int height = 0;
if (g_mouse_window != nullptr) {
SDL_GetWindowSize(g_mouse_window, &width, &height);
}
if (width > 0 && height > 0) {
x = std::clamp(x / static_cast<float>(width), 0.0f, 1.0f);
y = std::clamp(y / static_cast<float>(height), 0.0f, 1.0f);
} else {
x = 0.5f;
y = 0.5f;
}
std::scoped_lock lock(g_touchpad_mutex);
if (!g_mouse_touchpad.enabled) {
return;
}
g_mouse_touchpad.x = x;
g_mouse_touchpad.y = y;
g_mouse_touchpad.position_valid = true;
g_mouse_touchpad.revision++;
}
bool TouchPadModeActive() {
std::scoped_lock lock(g_touchpad_mutex);
return g_mouse_touchpad.enabled || g_gamepad_touchpad.enabled;
}
uint32_t DefaultKeyboardButton(int key_code) {
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_UP: return Controller::PAD_BUTTON_UP;
@@ -270,6 +465,18 @@ void SetControl(std::size_t control, bool down) {
SetButton(info.button, down);
return;
}
if (info.gyro) {
if (down) {
Controller::ToggleGyroEmulation(Controller::HOST_INPUT_CONTROLLER_ID);
}
return;
}
if (info.touchpad_mode) {
if (down) {
HostInputToggleTouchPadMode();
}
return;
}
if (info.axis == Controller::Axis::AxisMax) {
CycleSettingOnPress(info.setting, down);
return;
@@ -297,6 +504,11 @@ void DefaultKeyboardInput(int key_code, bool down) {
switch (NormalizeKey(static_cast<SDL_Keycode>(key_code))) {
case SDLK_BACKSPACE: SetTouchPad(0.25f, down); return;
case SDLK_F6:
if (down) {
HostInputToggleTouchPadMode();
}
return;
case SDLK_TAB: SetTouchPad(0.75f, down); return;
case SDLK_1: CycleSettingOnPress(Controller::Setting::SpeakerVolume, down); return;
case SDLK_2: CycleSettingOnPress(Controller::Setting::VibrationIntensity, down); return;
@@ -399,8 +611,134 @@ bool IsCursorActivity(const SDL_Event& event) {
return which != SDL_TOUCH_MOUSEID && which != SDL_PEN_MOUSEID;
}
std::array<int, 2> GamepadLeftStick(int id) {
auto* pad = SDL_GetGamepadFromID(id);
if (pad == nullptr) {
return {128, 128};
}
const auto value = [pad](SDL_GamepadAxis axis) {
return Controller::controller_get_axis(SDL_JOYSTICK_AXIS_MIN, SDL_JOYSTICK_AXIS_MAX,
SDL_GetGamepadAxis(pad, axis));
};
return {value(SDL_GAMEPAD_AXIS_LEFTX), value(SDL_GAMEPAD_AXIS_LEFTY)};
}
void ApplyPadControl(int id, std::size_t control, std::size_t source, bool down) {
if (control == INVALID_CONTROL) {
return;
}
const auto& info = CONTROL_INFO[control];
if (info.gyro) {
if (down) {
Controller::ToggleGyroEmulation(id);
}
} else if (info.touchpad_mode) {
if (down) {
HostInputToggleGamepadTouchPad(id);
}
} else if (info.button == Controller::PAD_BUTTON_CROSS && HostInputGamepadDrawButton(id, down)) {
return;
} else if (info.button == Controller::PAD_BUTTON_TOUCH_PAD) {
// Real touchpads report their own finger position; other inputs emulate a click.
if (source != PAD_SOURCES.size() && PAD_SOURCES[source].button != SDL_GAMEPAD_BUTTON_TOUCHPAD) {
Controller::SetTouchPad(id, 0, down, info.touch_x != 0.0f ? info.touch_x : 0.5f, 0.5f);
}
Controller::SetButton(id, Controller::PAD_BUTTON_TOUCH_PAD, down);
} else if (info.button == Controller::PAD_BUTTON_L2) {
Controller::SetAxis(id, Controller::Axis::TriggerLeft, down ? 255 : 0);
} else if (info.button == Controller::PAD_BUTTON_R2) {
Controller::SetAxis(id, Controller::Axis::TriggerRight, down ? 255 : 0);
} else if (info.button != 0) {
Controller::SetButton(id, info.button, down);
} else if (info.axis == Controller::Axis::AxisMax) {
CycleSettingOnPress(info.setting, down);
}
}
int PollGamepadTouchPad(uint64_t now_ms) {
float x = 0;
float y = 0;
int id = -1;
bool drawing = false;
{
std::scoped_lock lock(g_touchpad_mutex);
if (!g_gamepad_touchpad.enabled) {
return -1;
}
if (now_ms < g_gamepad_touchpad.next_poll) {
return static_cast<int>(g_gamepad_touchpad.next_poll - now_ms);
}
const float dt = g_gamepad_touchpad.last_poll == 0 || now_ms <= g_gamepad_touchpad.last_poll
? 0.008f
: std::min(static_cast<float>(now_ms - g_gamepad_touchpad.last_poll) *
0.001f,
0.05f);
g_gamepad_touchpad.last_poll = now_ms;
g_gamepad_touchpad.next_poll = now_ms + GAMEPAD_TOUCHPAD_POLL_MS;
const auto velocity = [](int axis) {
const int offset = axis - 128;
if (std::abs(offset) <= GAMEPAD_TOUCHPAD_DEADZONE) {
return 0.0f;
}
const int adjusted =
offset > 0 ? offset - GAMEPAD_TOUCHPAD_DEADZONE : offset + GAMEPAD_TOUCHPAD_DEADZONE;
return static_cast<float>(adjusted) / static_cast<float>(127 - GAMEPAD_TOUCHPAD_DEADZONE);
};
const float old_x = g_gamepad_touchpad.x;
const float old_y = g_gamepad_touchpad.y;
g_gamepad_touchpad.x = std::clamp(
old_x + velocity(g_gamepad_touchpad.x_axis) * GAMEPAD_TOUCHPAD_SPEED * dt, 0.0f, 1.0f);
g_gamepad_touchpad.y = std::clamp(
old_y + velocity(g_gamepad_touchpad.y_axis) * GAMEPAD_TOUCHPAD_SPEED * dt, 0.0f, 1.0f);
if (old_x != g_gamepad_touchpad.x || old_y != g_gamepad_touchpad.y) {
g_mouse_touchpad.revision++;
}
id = g_gamepad_touchpad.id;
x = g_gamepad_touchpad.x;
y = g_gamepad_touchpad.y;
drawing = g_gamepad_touchpad.drawing;
}
if (drawing) {
Controller::SetTouchPad(id, 1, true, x, y);
}
return GAMEPAD_TOUCHPAD_POLL_MS;
}
} // namespace
void HostInputPadButton(int id, int button, bool down) {
const auto source = InputMap::SourceFromButton(button);
const auto& map = GetInputMap();
ApplyPadControl(id, map.PadControl(source), source, down);
}
void HostInputPadTrigger(int id, bool left, int value) {
const auto source = left ? LEFT_TRIGGER_SOURCE : RIGHT_TRIGGER_SOURCE;
const auto control = GetInputMap().PadControl(source);
if (control == INVALID_CONTROL) {
return;
}
const auto& info = CONTROL_INFO[control];
if (info.button == Controller::PAD_BUTTON_L2) {
Controller::SetAxis(id, Controller::Axis::TriggerLeft, value);
} else if (info.button == Controller::PAD_BUTTON_R2) {
Controller::SetAxis(id, Controller::Axis::TriggerRight, value);
} else {
auto& state = g_pad_trigger_pressed[id][left ? 0 : 1];
const bool down = state ? value > 32 : value > 96;
if (down != state) {
state = down;
ApplyPadControl(id, control, source, down);
}
}
}
void HostInputDisconnectGamepad(int id) {
g_pad_trigger_pressed.erase(id);
HostInputDisconnectGamepadTouchPad(id);
}
void HostInputInit(SDL_Window* window) {
GetInputMap();
g_mouse_window = window;
@@ -410,6 +748,16 @@ void HostInputInit(SDL_Window* window) {
}
void HostInputShutdown() {
g_pad_trigger_pressed.clear();
HostInputSetTouchPadMode(false);
int gamepad_touch_id = -1;
{
std::scoped_lock lock(g_touchpad_mutex);
gamepad_touch_id = g_gamepad_touchpad.enabled ? g_gamepad_touchpad.id : -1;
}
if (gamepad_touch_id >= 0) {
HostInputDisconnectGamepadTouchPad(gamepad_touch_id);
}
if (g_mouse.enabled) {
SetRelativeMouseMode(false);
CenterMouseStick();
@@ -438,6 +786,219 @@ void HostInputMouseButton(uint8_t mouse_button, bool down) {
}
}
void HostInputMouseTouchPad(bool down, float x, float y) {
RecordMouseTouchPadPosition(x, y);
{
std::scoped_lock lock(g_touchpad_mutex);
if (!g_mouse_touchpad.enabled) {
return;
}
if (down && !g_mouse_touchpad.drawing) {
g_mouse_touchpad.gesture++;
}
g_mouse_touchpad.drawing = down;
g_mouse_touchpad.revision++;
x = g_mouse_touchpad.x;
y = g_mouse_touchpad.y;
}
SetSwipeTouchPad(down, x, y);
}
void HostInputMouseMotion(float x, float y) {
RecordMouseTouchPadPosition(x, y);
std::scoped_lock lock(g_touchpad_mutex);
if (g_mouse_touchpad.enabled && g_mouse_touchpad.drawing) {
SetSwipeTouchPad(true, g_mouse_touchpad.x, g_mouse_touchpad.y);
}
}
void HostInputSetTouchPadMode(bool enabled) {
if (enabled) {
int gamepad_id = -1;
{
std::scoped_lock lock(g_touchpad_mutex);
if (g_gamepad_touchpad.enabled) {
gamepad_id = g_gamepad_touchpad.id;
}
}
if (gamepad_id >= 0) {
HostInputDisconnectGamepadTouchPad(gamepad_id);
}
}
{
std::scoped_lock lock(g_touchpad_mutex);
if (g_mouse_touchpad.enabled == enabled) {
return;
}
}
if (!enabled) {
float x = 0;
float y = 0;
(void) SDL_GetMouseState(&x, &y);
HostInputMouseTouchPad(false, x, y);
}
bool show_system_cursor = false;
{
std::scoped_lock lock(g_touchpad_mutex);
g_mouse_touchpad.enabled = enabled;
g_mouse_touchpad.cursor_visible = true;
g_mouse_touchpad.drawing = false;
g_mouse_touchpad.position_valid = false;
g_mouse_touchpad.gesture++;
g_mouse_touchpad.revision++;
show_system_cursor = !enabled && !g_gamepad_touchpad.enabled;
}
if (enabled) {
if (!SDL_HideCursor()) {
LOGF("Failed to hide mouse cursor for touchpad mode: %s\n", SDL_GetError());
}
} else if (show_system_cursor && !Config::HideCursorEnabled() && !SDL_ShowCursor()) {
LOGF("Failed to restore mouse cursor after touchpad mode: %s\n", SDL_GetError());
}
LOGF("Mouse touchpad mode: %s\n", enabled ? "enabled" : "disabled");
}
void HostInputToggleTouchPadMode() {
bool enabled = false;
{
std::scoped_lock lock(g_touchpad_mutex);
enabled = !g_mouse_touchpad.enabled;
}
HostInputSetTouchPadMode(enabled);
}
void HostInputToggleGamepadTouchPad(int id) {
bool mouse_mode = false;
{
std::scoped_lock lock(g_touchpad_mutex);
mouse_mode = g_mouse_touchpad.enabled;
}
if (mouse_mode) {
HostInputSetTouchPadMode(false);
}
const auto stick = GamepadLeftStick(id);
bool enabled = false;
float x = 0.5f;
float y = 0.5f;
{
std::scoped_lock lock(g_touchpad_mutex);
if (g_gamepad_touchpad.enabled && g_gamepad_touchpad.id != id) {
return;
}
enabled = !g_gamepad_touchpad.enabled;
if (enabled) {
g_gamepad_touchpad = {};
g_gamepad_touchpad.enabled = true;
g_gamepad_touchpad.id = id;
g_gamepad_touchpad.x_axis = stick[0];
g_gamepad_touchpad.y_axis = stick[1];
g_gamepad_touchpad.last_poll = SDL_GetTicks();
g_gamepad_touchpad.next_poll = g_gamepad_touchpad.last_poll + GAMEPAD_TOUCHPAD_POLL_MS;
g_mouse_touchpad.gesture++;
} else {
x = g_gamepad_touchpad.x;
y = g_gamepad_touchpad.y;
g_gamepad_touchpad = {};
}
g_mouse_touchpad.revision++;
}
Controller::SetAxis(id, Controller::Axis::LeftX, enabled ? 128 : stick[0]);
Controller::SetAxis(id, Controller::Axis::LeftY, enabled ? 128 : stick[1]);
if (enabled) {
if (!SDL_HideCursor()) {
LOGF("Failed to hide mouse cursor for gamepad touchpad mode: %s\n", SDL_GetError());
}
} else {
Controller::SetTouchPad(id, 1, false, x, y);
if (!Config::HideCursorEnabled() && !SDL_ShowCursor()) {
LOGF("Failed to restore mouse cursor after gamepad touchpad mode: %s\n",
SDL_GetError());
}
}
LOGF("Gamepad touchpad mode: %s\n", enabled ? "enabled" : "disabled");
}
bool HostInputGamepadDrawButton(int id, bool down) {
float x = 0;
float y = 0;
{
std::scoped_lock lock(g_touchpad_mutex);
if (!g_gamepad_touchpad.enabled || g_gamepad_touchpad.id != id) {
return false;
}
if (!down && !g_gamepad_touchpad.drawing) {
return false;
}
if (down && !g_gamepad_touchpad.drawing) {
g_mouse_touchpad.gesture++;
}
g_gamepad_touchpad.drawing = down;
x = g_gamepad_touchpad.x;
y = g_gamepad_touchpad.y;
g_mouse_touchpad.revision++;
}
Controller::SetTouchPad(id, 1, down, x, y);
return true;
}
bool HostInputGamepadAxis(int id, bool horizontal, int value) {
std::scoped_lock lock(g_touchpad_mutex);
if (!g_gamepad_touchpad.enabled || g_gamepad_touchpad.id != id) {
return false;
}
(horizontal ? g_gamepad_touchpad.x_axis : g_gamepad_touchpad.y_axis) = std::clamp(value, 0, 255);
g_gamepad_touchpad.next_poll = SDL_GetTicks();
return true;
}
void HostInputDisconnectGamepadTouchPad(int id) {
float x = 0;
float y = 0;
{
std::scoped_lock lock(g_touchpad_mutex);
if (!g_gamepad_touchpad.enabled || g_gamepad_touchpad.id != id) {
return;
}
x = g_gamepad_touchpad.x;
y = g_gamepad_touchpad.y;
g_gamepad_touchpad = {};
g_mouse_touchpad.revision++;
}
Controller::SetTouchPad(id, 1, false, x, y);
const auto stick = GamepadLeftStick(id);
Controller::SetAxis(id, Controller::Axis::LeftX, stick[0]);
Controller::SetAxis(id, Controller::Axis::LeftY, stick[1]);
if (!TouchPadModeActive() && !Config::HideCursorEnabled() && !SDL_ShowCursor()) {
LOGF("Failed to restore mouse cursor after gamepad disconnect: %s\n", SDL_GetError());
}
}
HostInputTouchPadVisualState GetHostInputTouchPadVisualState() {
std::scoped_lock lock(g_touchpad_mutex);
HostInputTouchPadVisualState state {};
if (g_gamepad_touchpad.enabled) {
state.enabled = true;
state.cursor_visible = true;
state.drawing = g_gamepad_touchpad.drawing;
state.x = g_gamepad_touchpad.x;
state.y = g_gamepad_touchpad.y;
state.revision = g_mouse_touchpad.revision;
state.gesture = g_mouse_touchpad.gesture;
state.position_valid = true;
return state;
}
state.enabled = g_mouse_touchpad.enabled;
state.cursor_visible = g_mouse_touchpad.cursor_visible;
state.drawing = g_mouse_touchpad.drawing;
state.x = g_mouse_touchpad.x;
state.y = g_mouse_touchpad.y;
state.revision = g_mouse_touchpad.revision;
state.gesture = g_mouse_touchpad.gesture;
state.position_valid = g_mouse_touchpad.position_valid;
return state;
}
void HostInputToggleMouseToJoystick() {
if (g_mouse.enabled) {
SetRelativeMouseMode(false);
@@ -467,16 +1028,19 @@ bool HostInputWaitEvent(SDL_Event* event, int max_wait_ms) {
timeout = PollMouse(SDL_GetTicks());
}
if (g_cursor_hide_at != 0) {
if (g_cursor_hide_at != 0 && !TouchPadModeActive()) {
const auto now_ms = SDL_GetTicks();
const int cursor_timeout =
now_ms < g_cursor_hide_at ? static_cast<int>(g_cursor_hide_at - now_ms) : 0;
timeout = timeout < 0 ? cursor_timeout : std::min(timeout, cursor_timeout);
}
if (const int touch_timeout = PollGamepadTouchPad(SDL_GetTicks()); touch_timeout >= 0) {
timeout = timeout < 0 ? touch_timeout : std::min(timeout, touch_timeout);
}
timeout = timeout < 0 ? max_wait_ms : std::min(timeout, max_wait_ms);
const bool has_event = SDL_WaitEventTimeout(event, timeout);
if (Config::HideCursorEnabled()) {
if (Config::HideCursorEnabled() && !TouchPadModeActive()) {
const auto now_ms = SDL_GetTicks();
if (has_event && !g_mouse.enabled && IsCursorActivity(*event) &&
SDL_GetWindowFromEvent(event) == g_mouse_window) {