From d7013dc2b3629e318bd1587cbc599a172486d90d Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 9 Aug 2026 04:24:47 +0200 Subject: [PATCH] add left/right joystick remapping --- .../presentation/window/hostInput.cpp | 119 ++++++++++++------ src/launcher/src/inputMappingDialog.cpp | 32 +++-- src/main.cpp | 2 +- 3 files changed, 104 insertions(+), 49 deletions(-) diff --git a/src/graphics/presentation/window/hostInput.cpp b/src/graphics/presentation/window/hostInput.cpp index 29c046294..dea6635d3 100644 --- a/src/graphics/presentation/window/hostInput.cpp +++ b/src/graphics/presentation/window/hostInput.cpp @@ -18,35 +18,57 @@ namespace Libs::Graphics { namespace { +struct ControlInfo { + std::string_view name; + uint32_t button = 0; + Controller::Axis axis = Controller::Axis::AxisMax; + bool positive = false; +}; + +static constexpr std::array CONTROL_INFO = { + ControlInfo {"L3", Controller::PAD_BUTTON_L3}, + ControlInfo {"R3", Controller::PAD_BUTTON_R3}, + ControlInfo {"Options", Controller::PAD_BUTTON_OPTIONS}, + ControlInfo {"Up", Controller::PAD_BUTTON_UP}, + ControlInfo {"Right", Controller::PAD_BUTTON_RIGHT}, + ControlInfo {"Down", Controller::PAD_BUTTON_DOWN}, + ControlInfo {"Left", Controller::PAD_BUTTON_LEFT}, + ControlInfo {"L2", Controller::PAD_BUTTON_L2}, + ControlInfo {"R2", Controller::PAD_BUTTON_R2}, + ControlInfo {"L1", Controller::PAD_BUTTON_L1}, + ControlInfo {"R1", Controller::PAD_BUTTON_R1}, + ControlInfo {"Triangle", Controller::PAD_BUTTON_TRIANGLE}, + ControlInfo {"Circle", Controller::PAD_BUTTON_CIRCLE}, + ControlInfo {"Cross", Controller::PAD_BUTTON_CROSS}, + ControlInfo {"Square", Controller::PAD_BUTTON_SQUARE}, + ControlInfo {"TouchPad", Controller::PAD_BUTTON_TOUCH_PAD}, + ControlInfo {"LeftStickLeft", 0, Controller::Axis::LeftX, false}, + ControlInfo {"LeftStickRight", 0, Controller::Axis::LeftX, true}, + ControlInfo {"LeftStickUp", 0, Controller::Axis::LeftY, false}, + ControlInfo {"LeftStickDown", 0, Controller::Axis::LeftY, true}, + ControlInfo {"RightStickLeft", 0, Controller::Axis::RightX, false}, + ControlInfo {"RightStickRight", 0, Controller::Axis::RightX, true}, + ControlInfo {"RightStickUp", 0, Controller::Axis::RightY, false}, + ControlInfo {"RightStickDown", 0, Controller::Axis::RightY, true}, +}; + +constexpr std::size_t INVALID_CONTROL = CONTROL_INFO.size(); + struct Binding { SDL_Keycode key = SDLK_UNKNOWN; uint8_t mouse_button = 0; - uint32_t pad_button = 0; + std::size_t control = INVALID_CONTROL; }; -static constexpr std::array PAD_BUTTONS = { - std::pair {std::string_view("L3"), Controller::PAD_BUTTON_L3}, - std::pair {std::string_view("R3"), Controller::PAD_BUTTON_R3}, - std::pair {std::string_view("Options"), Controller::PAD_BUTTON_OPTIONS}, - std::pair {std::string_view("Up"), Controller::PAD_BUTTON_UP}, - std::pair {std::string_view("Right"), Controller::PAD_BUTTON_RIGHT}, - std::pair {std::string_view("Down"), Controller::PAD_BUTTON_DOWN}, - std::pair {std::string_view("Left"), Controller::PAD_BUTTON_LEFT}, - std::pair {std::string_view("L2"), Controller::PAD_BUTTON_L2}, - std::pair {std::string_view("R2"), Controller::PAD_BUTTON_R2}, - std::pair {std::string_view("L1"), Controller::PAD_BUTTON_L1}, - std::pair {std::string_view("R1"), Controller::PAD_BUTTON_R1}, - std::pair {std::string_view("Triangle"), Controller::PAD_BUTTON_TRIANGLE}, - std::pair {std::string_view("Circle"), Controller::PAD_BUTTON_CIRCLE}, - std::pair {std::string_view("Cross"), Controller::PAD_BUTTON_CROSS}, - std::pair {std::string_view("Square"), Controller::PAD_BUTTON_SQUARE}, - std::pair {std::string_view("TouchPad"), Controller::PAD_BUTTON_TOUCH_PAD}, -}; +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; }); + return static_cast(std::distance(CONTROL_INFO.begin(), info)); +} -uint32_t PadButtonFromName(std::string_view name) { - const auto button = std::find_if(PAD_BUTTONS.begin(), PAD_BUTTONS.end(), - [name](const auto& item) { return item.first == name; }); - return button != PAD_BUTTONS.end() ? button->second : 0; +const ControlInfo& Info(std::size_t control) { + EXIT_IF(control >= CONTROL_INFO.size()); + return CONTROL_INFO[control]; } SDL_Keycode NormalizeKey(SDL_Keycode key) { @@ -74,7 +96,7 @@ uint8_t MouseButtonFromName(std::string_view name) { } bool Conflicts(const Binding& first, const Binding& second) { - return first.pad_button == second.pad_button || + return first.control == second.control || (first.key != SDLK_UNKNOWN && first.key == second.key) || (first.mouse_button != 0 && first.mouse_button == second.mouse_button); } @@ -88,7 +110,7 @@ public: Binding binding; if (split != std::string_view::npos) { - binding.pad_button = PadButtonFromName(entry.substr(0, split)); + binding.control = ControlFromName(entry.substr(0, split)); const auto host_input = entry.substr(split + 1); binding.mouse_button = MouseButtonFromName(host_input); if (binding.mouse_button == 0) { @@ -98,7 +120,7 @@ public: const bool reserved = binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1; - if (binding.pad_button == 0 || reserved || + if (binding.control == INVALID_CONTROL || reserved || (binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) { EXIT("Invalid input mapping: %s\n", value.c_str()); } @@ -107,19 +129,19 @@ public: } [[nodiscard]] bool Custom() const { return m_custom; } - [[nodiscard]] uint32_t FindKey(int key_code) const { + [[nodiscard]] std::size_t FindKey(int key_code) const { key_code = NormalizeKey(static_cast(key_code)); const auto binding = std::find_if(m_bindings.begin(), m_bindings.begin() + m_size, [key_code](const auto& item) { return item.key == key_code; }); - return binding != m_bindings.begin() + m_size ? binding->pad_button : 0; + return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL; } - [[nodiscard]] uint32_t FindMouseButton(uint8_t mouse_button) const { + [[nodiscard]] std::size_t FindMouseButton(uint8_t mouse_button) const { const auto binding = std::find_if( m_bindings.begin(), m_bindings.begin() + m_size, [mouse_button](const auto& item) { return item.mouse_button == mouse_button; }); - return binding != m_bindings.begin() + m_size ? binding->pad_button : 0; + return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL; } private: @@ -135,9 +157,9 @@ private: m_bindings[m_size++] = binding; } - std::array m_bindings {}; - std::size_t m_size = 0; - bool m_custom; + std::array m_bindings {}; + std::size_t m_size = 0; + bool m_custom; }; const InputMap& GetInputMap() { @@ -191,6 +213,33 @@ void SetStickAxis(Controller::Axis axis, bool negative, bool positive) { Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value); } +void SetControl(std::size_t control, bool down) { + if (control == INVALID_CONTROL) { + return; + } + + const auto& info = Info(control); + if (info.button != 0) { + SetButton(info.button, down); + return; + } + + struct AxisKeys { + bool negative = false; + bool positive = false; + }; + static std::array axes; + + const auto axis = static_cast(info.axis); + EXIT_IF(axis >= axes.size()); + if (info.positive) { + axes[axis].positive = down; + } else { + axes[axis].negative = down; + } + SetStickAxis(info.axis, axes[axis].negative, axes[axis].positive); +} + void DefaultKeyboardInput(int key_code, bool down) { static StickKeys left; static StickKeys right; @@ -240,7 +289,7 @@ void HostInputInit() { void HostInputKey(int key_code, bool down) { const auto& map = GetInputMap(); if (map.Custom()) { - SetButton(map.FindKey(key_code), down); + SetControl(map.FindKey(key_code), down); } else { DefaultKeyboardInput(key_code, down); } @@ -249,7 +298,7 @@ void HostInputKey(int key_code, bool down) { void HostInputMouseButton(uint8_t mouse_button, bool down) { const auto& map = GetInputMap(); if (map.Custom() && mouse_button != 0) { - SetButton(map.FindMouseButton(mouse_button), down); + SetControl(map.FindMouseButton(mouse_button), down); } } diff --git a/src/launcher/src/inputMappingDialog.cpp b/src/launcher/src/inputMappingDialog.cpp index 402e199e3..074356179 100644 --- a/src/launcher/src/inputMappingDialog.cpp +++ b/src/launcher/src/inputMappingDialog.cpp @@ -18,17 +18,25 @@ namespace { constexpr int BINDING_COLUMN = 1; -struct PadButton { +struct PadControl { const char* id; const char* label; const char* default_binding; }; -constexpr PadButton PAD_BUTTONS[] = { +constexpr PadControl PAD_CONTROLS[] = { {"Up", "D-pad Up", "Up"}, {"Down", "D-pad Down", "Down"}, {"Left", "D-pad Left", "Left"}, {"Right", "D-pad Right", "Right"}, + {"LeftStickUp", "Left stick Up", "W"}, + {"LeftStickDown", "Left stick Down", "S"}, + {"LeftStickLeft", "Left stick Left", "A"}, + {"LeftStickRight", "Left stick Right", "D"}, + {"RightStickUp", "Right stick Up", "T"}, + {"RightStickDown", "Right stick Down", "G"}, + {"RightStickLeft", "Right stick Left", "F"}, + {"RightStickRight", "Right stick Right", "H"}, {"Triangle", "Triangle", "I"}, {"Circle", "Circle", "L"}, {"Cross", "Cross", "J"}, @@ -190,16 +198,14 @@ QHash ParseMapping(const QStringList& mapping) { InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* parent) : QDialog(parent), m_customized(!mapping.isEmpty()) { setWindowTitle(tr("Input Mapping")); - resize(440, 520); + resize(460, 650); auto* layout = new QVBoxLayout(this); - layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense buttons.\n" - "Defaults: left stick W/A/S/D, right stick T/F/G/H."), - this)); + layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense controls."), this)); m_bindings = new QTreeWidget(this); m_bindings->setColumnCount(2); - m_bindings->setHeaderLabels({tr("DualSense button"), tr("Host input")}); + m_bindings->setHeaderLabels({tr("DualSense control"), tr("Host input")}); m_bindings->setRootIsDecorated(false); m_bindings->setSelectionMode(QAbstractItemView::SingleSelection); m_bindings->header()->setSectionResizeMode(0, QHeaderView::Stretch); @@ -207,12 +213,12 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* pare layout->addWidget(m_bindings); const auto parsed = ParseMapping(mapping); - for (const auto& button: PAD_BUTTONS) { + for (const auto& control: PAD_CONTROLS) { auto* item = new QTreeWidgetItem(m_bindings); - item->setText(0, tr(button.label)); - item->setData(0, Qt::UserRole, QString::fromLatin1(button.id)); - SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(button.id)) - : QString::fromLatin1(button.default_binding)); + item->setText(0, tr(control.label)); + item->setData(0, Qt::UserRole, QString::fromLatin1(control.id)); + SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(control.id)) + : QString::fromLatin1(control.default_binding)); } m_bindings->setCurrentItem(m_bindings->topLevelItem(0)); @@ -286,7 +292,7 @@ void InputMappingDialog::ClearBinding() { void InputMappingDialog::RestoreDefaults() { for (int index = 0; index < m_bindings->topLevelItemCount(); index++) { SetBinding(m_bindings->topLevelItem(index), - QString::fromLatin1(PAD_BUTTONS[index].default_binding)); + QString::fromLatin1(PAD_CONTROLS[index].default_binding)); } m_customized = false; } diff --git a/src/main.cpp b/src/main.cpp index 57cddad2e..8463d46f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ static void PrintUsage() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS ::printf(" --redzone Protect the guest SysV red zone.\n"); #endif - ::printf(" --keymap DualSense mapping; may be repeated.\n"); + ::printf(" --keymap DualSense mapping; may be repeated.\n"); ::printf(" --rd Enable RenderDoc capture.\n"); }