From 057bf986c4f6adca945b1bfb2e7d9bf128d8300e Mon Sep 17 00:00:00 2001 From: Silent Date: Sat, 29 Aug 2020 14:19:28 +0200 Subject: [PATCH] Allow mapping half axes to buttons This allows to bind pressure sensitive NeGcon buttons to keyboard, mouse and controller buttons --- src/core/analog_controller.cpp | 8 +- src/core/controller.h | 8 +- src/core/negcon.cpp | 6 +- .../controllersettingswidget.cpp | 6 +- src/duckstation-qt/inputbindingdialog.cpp | 127 ++++++++++-------- src/duckstation-qt/inputbindingdialog.h | 24 ++-- src/duckstation-qt/inputbindingwidgets.cpp | 127 ++++++++++-------- src/duckstation-qt/inputbindingwidgets.h | 20 ++- src/duckstation-qt/main.cpp | 4 + src/duckstation-qt/qtutils.h | 3 + src/frontend-common/common_host_interface.cpp | 66 +++++++-- src/frontend-common/common_host_interface.h | 4 +- src/frontend-common/controller_interface.h | 3 +- .../sdl_controller_interface.cpp | 33 ++++- .../sdl_controller_interface.h | 9 +- .../xinput_controller_interface.cpp | 27 +++- .../xinput_controller_interface.h | 2 + 17 files changed, 306 insertions(+), 171 deletions(-) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index 76038d77c..686fe0bad 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -466,10 +466,10 @@ std::optional AnalogController::StaticGetButtonCodeByName(std::string_view Controller::AxisList AnalogController::StaticGetAxisNames() { - return {{TRANSLATABLE("AnalogController", "LeftX"), static_cast(Axis::LeftX)}, - {TRANSLATABLE("AnalogController", "LeftY"), static_cast(Axis::LeftY)}, - {TRANSLATABLE("AnalogController", "RightX"), static_cast(Axis::RightX)}, - {TRANSLATABLE("AnalogController", "RightY"), static_cast(Axis::RightY)}}; + return {{TRANSLATABLE("AnalogController", "LeftX"), static_cast(Axis::LeftX), AxisType::Full}, + {TRANSLATABLE("AnalogController", "LeftY"), static_cast(Axis::LeftY), AxisType::Full}, + {TRANSLATABLE("AnalogController", "RightX"), static_cast(Axis::RightX), AxisType::Full}, + {TRANSLATABLE("AnalogController", "RightY"), static_cast(Axis::RightY), AxisType::Full}}; } Controller::ButtonList AnalogController::StaticGetButtonNames() diff --git a/src/core/controller.h b/src/core/controller.h index 3c63c3900..2f2a83eb4 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -14,8 +14,14 @@ class HostInterface; class Controller { public: + enum class AxisType : u8 + { + Full, + Half + }; + using ButtonList = std::vector>; - using AxisList = std::vector>; + using AxisList = std::vector>; using SettingList = std::vector; Controller(); diff --git a/src/core/negcon.cpp b/src/core/negcon.cpp index 85d235c41..d1c8e16c5 100644 --- a/src/core/negcon.cpp +++ b/src/core/negcon.cpp @@ -219,12 +219,12 @@ std::optional NeGcon::StaticGetButtonCodeByName(std::string_view button_nam Controller::AxisList NeGcon::StaticGetAxisNames() { -#define A(n) \ +#define A(n, t) \ { \ - #n, static_cast (Axis::n) \ + #n, static_cast (Axis::n), Controller::AxisType::t \ } - return {A(Steering), A(I), A(II), A(L)}; + return {A(Steering, Full), A(I, Half), A(II, Half), A(L, Half)}; #undef A } diff --git a/src/duckstation-qt/controllersettingswidget.cpp b/src/duckstation-qt/controllersettingswidget.cpp index 76783c03f..a66014163 100644 --- a/src/duckstation-qt/controllersettingswidget.cpp +++ b/src/duckstation-qt/controllersettingswidget.cpp @@ -224,7 +224,7 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin const int num_rows = (static_cast(axises.size()) + 1) / 2; int current_row = 0; int current_column = 0; - for (const auto& [axis_name, axis_code] : axises) + for (const auto& [axis_name, axis_code, axis_type] : axises) { if (current_row == num_rows) { @@ -235,8 +235,8 @@ void ControllerSettingsWidget::createPortBindingSettingsUi(int index, PortSettin std::string section_name = StringUtil::StdStringFromFormat("Controller%d", index + 1); std::string key_name = StringUtil::StdStringFromFormat("Axis%s", axis_name.c_str()); QLabel* label = new QLabel(qApp->translate(cname, axis_name.c_str()), ui->bindings_container); - InputAxisBindingWidget* button = new InputAxisBindingWidget(m_host_interface, std::move(section_name), - std::move(key_name), ui->bindings_container); + InputAxisBindingWidget* button = new InputAxisBindingWidget( + m_host_interface, std::move(section_name), std::move(key_name), axis_type, ui->bindings_container); layout->addWidget(label, start_row + current_row, current_column); layout->addWidget(button, start_row + current_row, current_column + 1); diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp index 56b261152..0edded305 100644 --- a/src/duckstation-qt/inputbindingdialog.cpp +++ b/src/duckstation-qt/inputbindingdialog.cpp @@ -36,8 +36,32 @@ bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event) { const QEvent::Type event_type = event->type(); - if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || - event_type == QEvent::MouseButtonDblClick) + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease) + { + addNewBinding(std::move(m_new_binding_value)); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + QString binding = QtUtils::KeyEventToString(static_cast(event)); + if (!binding.isEmpty()) + m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); + + return true; + } + else if (event_type == QEvent::MouseButtonRelease) + { + const u32 button_mask = static_cast(static_cast(event)->button()); + const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); + m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); + addNewBinding(std::move(m_new_binding_value)); + stopListeningForInput(); + return true; + } + + if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) { return true; } @@ -103,6 +127,27 @@ void InputBindingDialog::addNewBinding(std::string new_binding) saveListToSettings(); } +void InputBindingDialog::bindToControllerAxis(int controller_index, int axis_index, std::optional positive) +{ + const char* sign_char = ""; + if (positive) + { + sign_char = *positive ? "+" : "-"; + } + + std::string binding = + StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index); + addNewBinding(std::move(binding)); + stopListeningForInput(); +} + +void InputBindingDialog::bindToControllerButton(int controller_index, int button_index) +{ + std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); + addNewBinding(std::move(binding)); + stopListeningForInput(); +} + void InputBindingDialog::onAddBindingButtonClicked() { if (isListeningForInput()) @@ -159,38 +204,6 @@ InputButtonBindingDialog::~InputButtonBindingDialog() InputButtonBindingDialog::stopListeningForInput(); } -bool InputButtonBindingDialog::eventFilter(QObject* watched, QEvent* event) -{ - const QEvent::Type event_type = event->type(); - - // if the key is being released, set the input - if (event_type == QEvent::KeyRelease) - { - addNewBinding(std::move(m_new_binding_value)); - stopListeningForInput(); - return true; - } - else if (event_type == QEvent::KeyPress) - { - QString binding = QtUtils::KeyEventToString(static_cast(event)); - if (!binding.isEmpty()) - m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); - - return true; - } - else if (event_type == QEvent::MouseButtonRelease) - { - const u32 button_mask = static_cast(static_cast(event)->button()); - const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); - m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); - addNewBinding(std::move(m_new_binding_value)); - stopListeningForInput(); - return true; - } - - return InputBindingDialog::eventFilter(watched, event); -} - void InputButtonBindingDialog::hookControllerInput() { ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); @@ -206,7 +219,7 @@ void InputButtonBindingDialog::hookControllerInput() // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), - Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, ei.value > 0)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) @@ -229,21 +242,6 @@ void InputButtonBindingDialog::unhookControllerInput() controller_interface->ClearHook(); } -void InputButtonBindingDialog::bindToControllerAxis(int controller_index, int axis_index, bool positive) -{ - std::string binding = - StringUtil::StdStringFromFormat("Controller%d/%cAxis%d", controller_index, positive ? '+' : '-', axis_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); -} - -void InputButtonBindingDialog::bindToControllerButton(int controller_index, int button_index) -{ - std::string binding = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); -} - void InputButtonBindingDialog::startListeningForInput(u32 timeout_in_seconds) { InputBindingDialog::startListeningForInput(timeout_in_seconds); @@ -257,8 +255,10 @@ void InputButtonBindingDialog::stopListeningForInput() } InputAxisBindingDialog::InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, - std::string key_name, std::vector bindings, QWidget* parent) - : InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent) + std::string key_name, std::vector bindings, + Controller::AxisType axis_type, QWidget* parent) + : InputBindingDialog(host_interface, std::move(section_name), std::move(key_name), std::move(bindings), parent), + m_axis_type(axis_type) { } @@ -282,6 +282,13 @@ void InputAxisBindingDialog::hookControllerInput() return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, std::nullopt)); + return ControllerInterface::Hook::CallbackResult::StopMonitoring; + } + else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half && + ei.value > 0.0f) + { + QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } @@ -299,11 +306,19 @@ void InputAxisBindingDialog::unhookControllerInput() controller_interface->ClearHook(); } -void InputAxisBindingDialog::bindToControllerAxis(int controller_index, int axis_index) +bool InputAxisBindingDialog::eventFilter(QObject* watched, QEvent* event) { - std::string binding = StringUtil::StdStringFromFormat("Controller%d/Axis%d", controller_index, axis_index); - addNewBinding(std::move(binding)); - stopListeningForInput(); + if (m_axis_type != Controller::AxisType::Half) + { + const QEvent::Type event_type = event->type(); + + if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease) + { + return true; + } + } + + return InputBindingDialog::eventFilter(watched, event); } void InputAxisBindingDialog::startListeningForInput(u32 timeout_in_seconds) diff --git a/src/duckstation-qt/inputbindingdialog.h b/src/duckstation-qt/inputbindingdialog.h index 84ee7efe0..ff26b3776 100644 --- a/src/duckstation-qt/inputbindingdialog.h +++ b/src/duckstation-qt/inputbindingdialog.h @@ -1,7 +1,9 @@ #pragma once #include "common/types.h" +#include "core/controller.h" #include "ui_inputbindingdialog.h" #include +#include #include #include @@ -17,6 +19,8 @@ public: ~InputBindingDialog(); protected Q_SLOTS: + void bindToControllerAxis(int controller_index, int axis_index, std::optional positive); + void bindToControllerButton(int controller_index, int button_index); void onAddBindingButtonClicked(); void onRemoveBindingButtonClicked(); void onClearBindingsButtonClicked(); @@ -52,7 +56,7 @@ protected: u32 m_input_listen_remaining_seconds = 0; }; -class InputButtonBindingDialog : public InputBindingDialog +class InputButtonBindingDialog final : public InputBindingDialog { Q_OBJECT @@ -61,13 +65,6 @@ public: std::vector bindings, QWidget* parent); ~InputButtonBindingDialog(); -protected: - bool eventFilter(QObject* watched, QEvent* event) override; - -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index, bool positive); - void bindToControllerButton(int controller_index, int button_index); - protected: void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; @@ -75,21 +72,22 @@ protected: void unhookControllerInput(); }; -class InputAxisBindingDialog : public InputBindingDialog +class InputAxisBindingDialog final : public InputBindingDialog { Q_OBJECT public: InputAxisBindingDialog(QtHostInterface* host_interface, std::string section_name, std::string key_name, - std::vector bindings, QWidget* parent); + std::vector bindings, Controller::AxisType axis_type, QWidget* parent); ~InputAxisBindingDialog(); -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index); - protected: + bool eventFilter(QObject* watched, QEvent* event) override; void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void hookControllerInput(); void unhookControllerInput(); + +private: + Controller::AxisType m_axis_type; }; diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index ec97936dc..9d55dc16e 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -40,6 +40,27 @@ void InputBindingWidget::updateText() setText(QString::fromStdString(m_bindings[0])); } +void InputBindingWidget::bindToControllerAxis(int controller_index, int axis_index, std::optional positive) +{ + const char* sign_char = ""; + if (positive) + { + sign_char = *positive ? "+" : "-"; + } + + m_new_binding_value = + StringUtil::StdStringFromFormat("Controller%d/%sAxis%d", controller_index, sign_char, axis_index); + setNewBinding(); + stopListeningForInput(); +} + +void InputBindingWidget::bindToControllerButton(int controller_index, int button_index) +{ + m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); + setNewBinding(); + stopListeningForInput(); +} + void InputBindingWidget::beginRebindAll() { m_is_binding_all = true; @@ -53,8 +74,32 @@ bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event) { const QEvent::Type event_type = event->type(); - if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonRelease || - event_type == QEvent::MouseButtonDblClick) + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease) + { + setNewBinding(); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + QString binding = QtUtils::KeyEventToString(static_cast(event)); + if (!binding.isEmpty()) + m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); + + return true; + } + else if (event_type == QEvent::MouseButtonRelease) + { + const u32 button_mask = static_cast(static_cast(event)->button()); + const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); + m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); + setNewBinding(); + stopListeningForInput(); + return true; + } + + if (event_type == QEvent::MouseButtonPress || event_type == QEvent::MouseButtonDblClick) { return true; } @@ -185,38 +230,6 @@ InputButtonBindingWidget::~InputButtonBindingWidget() InputButtonBindingWidget::stopListeningForInput(); } -bool InputButtonBindingWidget::eventFilter(QObject* watched, QEvent* event) -{ - const QEvent::Type event_type = event->type(); - - // if the key is being released, set the input - if (event_type == QEvent::KeyRelease) - { - setNewBinding(); - stopListeningForInput(); - return true; - } - else if (event_type == QEvent::KeyPress) - { - QString binding = QtUtils::KeyEventToString(static_cast(event)); - if (!binding.isEmpty()) - m_new_binding_value = QStringLiteral("Keyboard/%1").arg(binding).toStdString(); - - return true; - } - else if (event_type == QEvent::MouseButtonRelease) - { - const u32 button_mask = static_cast(static_cast(event)->button()); - const u32 button_index = (button_mask == 0u) ? 0 : CountTrailingZeros(button_mask); - m_new_binding_value = StringUtil::StdStringFromFormat("Mouse/Button%d", button_index + 1); - setNewBinding(); - stopListeningForInput(); - return true; - } - - return InputBindingWidget::eventFilter(watched, event); -} - void InputButtonBindingWidget::hookControllerInput() { ControllerInterface* controller_interface = m_host_interface->getControllerInterface(); @@ -232,7 +245,7 @@ void InputButtonBindingWidget::hookControllerInput() // TODO: this probably should consider the "last value" QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), - Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0)); + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, ei.value > 0)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } else if (ei.type == ControllerInterface::Hook::Type::Button && ei.value > 0.0f) @@ -255,21 +268,6 @@ void InputButtonBindingWidget::unhookControllerInput() controller_interface->ClearHook(); } -void InputButtonBindingWidget::bindToControllerAxis(int controller_index, int axis_index, bool positive) -{ - m_new_binding_value = - StringUtil::StdStringFromFormat("Controller%d/%cAxis%d", controller_index, positive ? '+' : '-', axis_index); - setNewBinding(); - stopListeningForInput(); -} - -void InputButtonBindingWidget::bindToControllerButton(int controller_index, int button_index) -{ - m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Button%d", controller_index, button_index); - setNewBinding(); - stopListeningForInput(); -} - void InputButtonBindingWidget::startListeningForInput(u32 timeout_in_seconds) { InputBindingWidget::startListeningForInput(timeout_in_seconds); @@ -291,8 +289,8 @@ void InputButtonBindingWidget::openDialog() } InputAxisBindingWidget::InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, - std::string key_name, QWidget* parent) - : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent) + std::string key_name, Controller::AxisType axis_type, QWidget* parent) + : InputBindingWidget(host_interface, std::move(section_name), std::move(key_name), parent), m_axis_type(axis_type) { } @@ -316,6 +314,13 @@ void InputAxisBindingWidget::hookControllerInput() return ControllerInterface::Hook::CallbackResult::ContinueMonitoring; QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index), + Q_ARG(int, ei.button_or_axis_number), Q_ARG(std::optional, std::nullopt)); + return ControllerInterface::Hook::CallbackResult::StopMonitoring; + } + else if (ei.type == ControllerInterface::Hook::Type::Button && m_axis_type == Controller::AxisType::Half && + ei.value > 0.0f) + { + QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index), Q_ARG(int, ei.button_or_axis_number)); return ControllerInterface::Hook::CallbackResult::StopMonitoring; } @@ -333,11 +338,19 @@ void InputAxisBindingWidget::unhookControllerInput() controller_interface->ClearHook(); } -void InputAxisBindingWidget::bindToControllerAxis(int controller_index, int axis_index) +bool InputAxisBindingWidget::eventFilter(QObject* watched, QEvent* event) { - m_new_binding_value = StringUtil::StdStringFromFormat("Controller%d/Axis%d", controller_index, axis_index); - setNewBinding(); - stopListeningForInput(); + if (m_axis_type != Controller::AxisType::Half) + { + const QEvent::Type event_type = event->type(); + + if (event_type == QEvent::KeyRelease || event_type == QEvent::KeyPress || event_type == QEvent::MouseButtonRelease) + { + return true; + } + } + + return InputBindingWidget::eventFilter(watched, event); } void InputAxisBindingWidget::startListeningForInput(u32 timeout_in_seconds) @@ -354,7 +367,7 @@ void InputAxisBindingWidget::stopListeningForInput() void InputAxisBindingWidget::openDialog() { - InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings, + InputAxisBindingDialog binding_dialog(m_host_interface, m_section_name, m_key_name, m_bindings, m_axis_type, QtUtils::GetRootWidget(this)); binding_dialog.exec(); reloadBinding(); diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h index 9c3bc338d..e34f44d8d 100644 --- a/src/duckstation-qt/inputbindingwidgets.h +++ b/src/duckstation-qt/inputbindingwidgets.h @@ -1,6 +1,8 @@ #pragma once +#include "core/controller.h" #include "core/types.h" #include +#include class QTimer; @@ -18,6 +20,8 @@ public: ALWAYS_INLINE void setNextWidget(InputBindingWidget* widget) { m_next_widget = widget; } public Q_SLOTS: + void bindToControllerAxis(int controller_index, int axis_index, std::optional positive); + void bindToControllerButton(int controller_index, int button_index); void beginRebindAll(); void clearBinding(); void reloadBinding(); @@ -66,13 +70,6 @@ public: QWidget* parent); ~InputButtonBindingWidget(); -protected: - bool eventFilter(QObject* watched, QEvent* event) override; - -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index, bool positive); - void bindToControllerButton(int controller_index, int button_index); - protected: void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; @@ -87,18 +84,19 @@ class InputAxisBindingWidget : public InputBindingWidget public: InputAxisBindingWidget(QtHostInterface* host_interface, std::string section_name, std::string key_name, - QWidget* parent); + Controller::AxisType axis_type, QWidget* parent); ~InputAxisBindingWidget(); -private Q_SLOTS: - void bindToControllerAxis(int controller_index, int axis_index); - protected: + bool eventFilter(QObject* watched, QEvent* event) override; void startListeningForInput(u32 timeout_in_seconds) override; void stopListeningForInput() override; void openDialog() override; void hookControllerInput(); void unhookControllerInput(); + +private: + Controller::AxisType m_axis_type; }; class InputRumbleBindingWidget : public InputBindingWidget diff --git a/src/duckstation-qt/main.cpp b/src/duckstation-qt/main.cpp index 8eb740aaf..cfad70bea 100644 --- a/src/duckstation-qt/main.cpp +++ b/src/duckstation-qt/main.cpp @@ -1,6 +1,7 @@ #include "common/log.h" #include "mainwindow.h" #include "qthostinterface.h" +#include "qtutils.h" #include #include #include @@ -8,6 +9,9 @@ int main(int argc, char* argv[]) { + // Register any standard types we need elsewhere + qRegisterMetaType>(); + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index ef9975616..5eb42093a 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -1,9 +1,12 @@ #pragma once #include +#include #include #include #include +Q_DECLARE_METATYPE(std::optional); + class ByteStream; class QFrame; diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 73b7a87d3..30c913def 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -7,7 +7,6 @@ #include "common/string_util.h" #include "controller_interface.h" #include "core/cdrom.h" -#include "core/controller.h" #include "core/cpu_code_cache.h" #include "core/dma.h" #include "core/game_list.h" @@ -1055,8 +1054,9 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) const auto axis_names = Controller::GetAxisNames(ctype); for (const auto& it : axis_names) { - const std::string& axis_name = it.first; - const s32 axis_code = it.second; + const std::string& axis_name = std::get(it); + const s32 axis_code = std::get(it); + const auto axis_type = std::get(it); const std::vector bindings = si.GetStringList(category, TinyString::FromFormat("Axis%s", axis_name.c_str())); @@ -1066,7 +1066,7 @@ void CommonHostInterface::UpdateControllerInputMap(SettingsInterface& si) if (!SplitBinding(binding, &device, &axis)) continue; - AddAxisToInputMap(binding, device, axis, [this, controller_index, axis_code](float value) { + AddAxisToInputMap(binding, device, axis, axis_type, [this, controller_index, axis_code](float value) { if (System::IsShutdown()) return; @@ -1198,8 +1198,44 @@ bool CommonHostInterface::AddButtonToInputMap(const std::string& binding, const } bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const std::string_view& device, - const std::string_view& axis, InputAxisHandler handler) + const std::string_view& axis, Controller::AxisType axis_type, + InputAxisHandler handler) { + if (axis_type == Controller::AxisType::Half) + { + if (device == "Keyboard") + { + std::optional key_id = GetHostKeyCode(axis); + if (!key_id.has_value()) + { + Log_WarningPrintf("Unknown keyboard key in binding '%s'", binding.c_str()); + return false; + } + + m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler)); + return true; + } + + if (device == "Mouse") + { + if (StringUtil::StartsWith(axis, "Button")) + { + const std::optional button_index = StringUtil::FromChars(axis.substr(6)); + if (!button_index.has_value()) + { + Log_WarningPrintf("Invalid button in mouse binding '%s'", binding.c_str()); + return false; + } + + m_mouse_input_handlers.emplace(static_cast(button_index.value()), std::move(handler)); + return true; + } + + Log_WarningPrintf("Malformed mouse binding '%s'", binding.c_str()); + return false; + } + } + if (StringUtil::StartsWith(device, "Controller")) { if (!m_controller_interface) @@ -1227,6 +1263,18 @@ bool CommonHostInterface::AddAxisToInputMap(const std::string& binding, const st return true; } + else if (StringUtil::StartsWith(axis, "Button") && axis_type == Controller::AxisType::Half) + { + const std::optional button_index = StringUtil::FromChars(axis.substr(6)); + if (!button_index || + !m_controller_interface->BindControllerButtonToAxis(*controller_index, *button_index, std::move(handler))) + { + Log_WarningPrintf("Failed to bind controller button '%s' to axis", binding.c_str()); + return false; + } + + return true; + } Log_WarningPrintf("Malformed controller binding '%s' in button", binding.c_str()); return false; @@ -1533,7 +1581,7 @@ void CommonHostInterface::ClearAllControllerBindings(SettingsInterface& si) si.DeleteValue(section_name, button.first.c_str()); for (const auto& axis : Controller::GetAxisNames(ctype)) - si.DeleteValue(section_name, axis.first.c_str()); + si.DeleteValue(section_name, std::get(axis).c_str()); if (Controller::GetVibrationMotorCount(ctype) > 0) si.DeleteValue(section_name, "Rumble"); @@ -1577,8 +1625,8 @@ void CommonHostInterface::ApplyInputProfile(const char* profile_path, SettingsIn for (const auto& axis : Controller::GetAxisNames(*ctype)) { - const auto key_name = TinyString::FromFormat("Axis%s", axis.first.c_str()); - si.DeleteValue(section_name, axis.first.c_str()); + const auto key_name = TinyString::FromFormat("Axis%s", std::get(axis).c_str()); + si.DeleteValue(section_name, std::get(axis).c_str()); const std::vector bindings = profile.GetStringList(section_name, key_name); for (const std::string& binding : bindings) si.AddToStringList(section_name, key_name, binding.c_str()); @@ -1636,7 +1684,7 @@ bool CommonHostInterface::SaveInputProfile(const char* profile_path, SettingsInt for (const auto& axis : Controller::GetAxisNames(ctype)) { - const auto key_name = TinyString::FromFormat("Axis%s", axis.first.c_str()); + const auto key_name = TinyString::FromFormat("Axis%s", std::get(axis).c_str()); const std::vector bindings = si.GetStringList(section_name, key_name); for (const std::string& binding : bindings) profile.AddToStringList(section_name, key_name, binding.c_str()); diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 78007c3f3..7353365ad 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -1,5 +1,6 @@ #pragma once #include "common/string.h" +#include "core/controller.h" #include "core/host_interface.h" #include #include @@ -195,7 +196,8 @@ protected: virtual bool AddButtonToInputMap(const std::string& binding, const std::string_view& device, const std::string_view& button, InputButtonHandler handler); virtual bool AddAxisToInputMap(const std::string& binding, const std::string_view& device, - const std::string_view& axis, InputAxisHandler handler); + const std::string_view& axis, Controller::AxisType axis_type, + InputAxisHandler handler); virtual bool AddRumbleToInputMap(const std::string& binding, u32 controller_index, u32 num_motors); /// Reloads the input map from config. Callable from controller interface. diff --git a/src/frontend-common/controller_interface.h b/src/frontend-common/controller_interface.h index ab877a72a..60e287f11 100644 --- a/src/frontend-common/controller_interface.h +++ b/src/frontend-common/controller_interface.h @@ -3,9 +3,9 @@ #include "core/types.h" #include #include -#include #include #include +#include class HostInterface; class Controller; @@ -54,6 +54,7 @@ public: virtual bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) = 0; virtual bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) = 0; + virtual bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) = 0; virtual void PollEvents() = 0; diff --git a/src/frontend-common/sdl_controller_interface.cpp b/src/frontend-common/sdl_controller_interface.cpp index 919228d49..7d3818758 100644 --- a/src/frontend-common/sdl_controller_interface.cpp +++ b/src/frontend-common/sdl_controller_interface.cpp @@ -35,8 +35,7 @@ bool SDLControllerInterface::Initialize(CommonHostInterface* host_interface) Log_InfoPrintf("Loading game controller mappings from '%s'", gcdb_file_name.c_str()); if (SDL_GameControllerAddMappingsFromFile(gcdb_file_name.c_str()) < 0) { - Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s", - gcdb_file_name.c_str(), SDL_GetError()); + Log_ErrorPrintf("SDL_GameControllerAddMappingsFromFile(%s) failed: %s", gcdb_file_name.c_str(), SDL_GetError()); } } @@ -293,6 +292,19 @@ bool SDLControllerInterface::BindControllerAxisToButton(int controller_index, in return true; } +bool SDLControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) +{ + auto it = GetControllerDataForPlayerId(controller_index); + if (it == m_controllers.end()) + return false; + + if (button_number < 0 || button_number >= MAX_NUM_BUTTONS) + return false; + + it->button_axis_mapping[button_number] = std::move(callback); + return true; +} + bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev) { const float value = static_cast(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f); @@ -350,11 +362,20 @@ bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev) return true; const ButtonCallback& cb = it->button_mapping[ev->cbutton.button]; - if (!cb) - return false; + if (cb) + { + cb(pressed); + return true; + } - cb(pressed); - return true; + // Assume a half-axis, i.e. in 0..1 range + const AxisCallback& axis_cb = it->button_axis_mapping[ev->cbutton.button]; + if (axis_cb) + { + axis_cb(pressed ? 1.0f : 0.0f); + } + + return false; } u32 SDLControllerInterface::GetControllerRumbleMotorCount(int controller_index) diff --git a/src/frontend-common/sdl_controller_interface.h b/src/frontend-common/sdl_controller_interface.h index 4fa43e556..d839b73a0 100644 --- a/src/frontend-common/sdl_controller_interface.h +++ b/src/frontend-common/sdl_controller_interface.h @@ -1,10 +1,10 @@ #pragma once -#include "core/types.h" #include "controller_interface.h" +#include "core/types.h" #include #include -#include #include +#include union SDL_Event; @@ -27,7 +27,9 @@ public: // Binding to events. If a binding for this axis/button already exists, returns false. bool BindControllerAxis(int controller_index, int axis_number, AxisCallback callback) override; bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; - bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override; + bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, + ButtonCallback callback) override; + bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; // Changing rumble strength. u32 GetControllerRumbleMotorCount(int controller_index) override; @@ -59,6 +61,7 @@ private: std::array axis_mapping; std::array button_mapping; std::array, MAX_NUM_AXISES> axis_button_mapping; + std::array button_axis_mapping; }; using ControllerDataVector = std::vector; diff --git a/src/frontend-common/xinput_controller_interface.cpp b/src/frontend-common/xinput_controller_interface.cpp index 076746f3b..3ef93d90d 100644 --- a/src/frontend-common/xinput_controller_interface.cpp +++ b/src/frontend-common/xinput_controller_interface.cpp @@ -204,6 +204,19 @@ bool XInputControllerInterface::BindControllerAxisToButton(int controller_index, return true; } +bool XInputControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, + AxisCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size() || !m_controllers[controller_index].connected) + return false; + + if (button_number < 0 || button_number >= MAX_NUM_BUTTONS) + return false; + + m_controllers[controller_index].button_axis_mapping[button_number] = std::move(callback); + return true; +} + bool XInputControllerInterface::HandleAxisEvent(u32 index, Axis axis, s32 value) { const float f_value = static_cast(value) / (value < 0 ? 32768.0f : 32767.0f); @@ -255,10 +268,18 @@ bool XInputControllerInterface::HandleButtonEvent(u32 index, u32 button, bool pr return true; const ButtonCallback& cb = m_controllers[index].button_mapping[button]; - if (!cb) - return false; + if (cb) + { + cb(pressed); + return true; + } - cb(pressed); + // Assume a half-axis, i.e. in 0..1 range + const AxisCallback& axis_cb = m_controllers[index].button_axis_mapping[button]; + if (axis_cb) + { + axis_cb(pressed ? 1.0f : 0.0f); + } return true; } diff --git a/src/frontend-common/xinput_controller_interface.h b/src/frontend-common/xinput_controller_interface.h index 5d02e1262..21802ff40 100644 --- a/src/frontend-common/xinput_controller_interface.h +++ b/src/frontend-common/xinput_controller_interface.h @@ -26,6 +26,7 @@ public: bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback) override; + bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; // Changing rumble strength. u32 GetControllerRumbleMotorCount(int controller_index) override; @@ -68,6 +69,7 @@ private: std::array axis_mapping; std::array button_mapping; std::array, MAX_NUM_AXISES> axis_button_mapping; + std::array button_axis_mapping; }; using ControllerDataArray = std::array;