From 7a032e883ae023d0091d8adcc0d64dc1ef6ef560 Mon Sep 17 00:00:00 2001 From: David Griswold Date: Wed, 10 Jun 2026 16:25:25 -0500 Subject: [PATCH] bug fixes and refactors --- .../configuration/configure_hotkeys.cpp | 18 +- .../configuration/configure_input.cpp | 229 +++++++++--------- src/citra_qt/configuration/configure_input.h | 16 +- 3 files changed, 143 insertions(+), 120 deletions(-) diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp index 50185417a..9d920937b 100644 --- a/src/citra_qt/configuration/configure_hotkeys.cpp +++ b/src/citra_qt/configuration/configure_hotkeys.cpp @@ -52,8 +52,8 @@ QMap ConfigureHotkeys::GetUsedKeyLis auto seq = QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText); if (seq.count() == 1 && seq[0].keyboardModifiers() == Qt::KeyboardModifier::NoModifier) { - auto binding = - ConfigureInput::InputBinding("Hotkey", parent->child(r2, 0)->text(), r2); + auto binding = ConfigureInput::InputBinding( + ConfigureInput::InputBindingType::Hotkey, parent->child(r2, 0)->text(), r2); list[seq] = binding; } } @@ -147,7 +147,7 @@ void ConfigureHotkeys::Configure(QModelIndex index) { if (response == QMessageBox::No) { return; } else { - if (current_binding.binding_type == "Hotkey") { + if (current_binding.binding_type == ConfigureInput::InputBindingType::Hotkey) { model->setData(index.sibling(current_binding.index, hotkey_column), QStringLiteral("")); } else { @@ -162,7 +162,7 @@ void ConfigureHotkeys::Configure(QModelIndex index) { void ConfigureHotkeys::OnClearBinding(ConfigureInput::InputBinding hotkey_to_clear) { const auto index = ui->hotkey_list->currentIndex(); - if (hotkey_to_clear.binding_type == "Hotkey") { + if (hotkey_to_clear.binding_type == ConfigureInput::InputBindingType::Hotkey) { model->setData(index.sibling(hotkey_to_clear.index, hotkey_column), QStringLiteral("")); } EmitHotkeysChanged(); @@ -171,7 +171,8 @@ void ConfigureHotkeys::OnClearBinding(ConfigureInput::InputBinding hotkey_to_cle std::pair ConfigureHotkeys::IsUsedKey( QKeySequence key_sequence) const { if (key_sequence == QKeySequence::fromString(QStringLiteral(""), QKeySequence::NativeText)) { - return std::make_pair(false, ConfigureInput::InputBinding{"", QStringLiteral(""), -1}); + return std::make_pair(false, ConfigureInput::InputBinding{ + ConfigureInput::InputBindingType::Empty, QString(), -1}); } if (input_keys_list.contains(key_sequence)) { @@ -188,12 +189,15 @@ std::pair ConfigureHotkeys::IsUsedKey( if (key_sequence == key_seq) { return std::make_pair( - true, ConfigureInput::InputBinding{"Hotkey", parent->child(r2, 0)->text(), r2}); + true, ConfigureInput::InputBinding{ConfigureInput::InputBindingType::Hotkey, + parent->child(r2, 0)->text(), r2}); } } } - return std::make_pair(false, ConfigureInput::InputBinding{"", QStringLiteral(""), -1}); + return std::make_pair(false, + ConfigureInput::InputBinding{ConfigureInput::InputBindingType::Empty, + QStringLiteral(""), -1}); } void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 7122b710e..11508ec85 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -46,16 +46,6 @@ static QString GetKeyName(int key_code) { } } -static void SetAnalogButton(const Common::ParamPackage& input_param, - Common::ParamPackage& analog_param, const std::string& button_name) { - if (analog_param.Get("engine", "") != "analog_from_button") { - analog_param = { - {"engine", "analog_from_button"}, - }; - } - analog_param.Set(button_name, input_param.Serialize()); -} - static QString ButtonToText(const Common::ParamPackage& param) { if (!param.Has("engine")) { return QObject::tr("[not set]"); @@ -223,24 +213,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) HandleClick( button_map[button_id], [this, button_id](Common::ParamPackage params) { - // Workaround for ZL & ZR for analog triggers like on XBOX controllors. - // Analog triggers (from controllers like the XBOX controller) would not - // work due to a different range of their signals (from 0 to 255 on - // analog triggers instead of -32768 to 32768 on analog joysticks). The - // SDL driver misinterprets analog triggers as analog joysticks. - // TODO: reinterpret the signal range for analog triggers to map the - // values correctly. This is required for the correct emulation of the - // analog triggers of the GameCube controller. - if (button_id == Settings::NativeButton::ZL || - button_id == Settings::NativeButton::ZR) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } - buttons_param[button_id] = std::move(params); - // If the user closes the dialog, the changes are reverted in - // `GMainWindow::OnConfigure()` - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::NativeButton, QString(), button_id}, params); }, InputCommon::Polling::DeviceType::Button); }); @@ -248,18 +221,13 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) [this, button_id](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), this, [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + ClearBinding({InputBindingType::NativeButton, QString(), button_id}); }); context_menu.addAction(tr("Restore Default"), this, [&] { - buttons_param[button_id] = + Common::ParamPackage def = Common::ParamPackage{InputCommon::GenerateKeyboardParam( QtConfig::default_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::NativeButton, QString(), button_id}, def); }); context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); }); @@ -276,10 +244,9 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) HandleClick( analog_map_buttons[analog_id][sub_button_id], [this, analog_id, sub_button_id](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::AnalogFromButton, QString(), + analog_id, sub_button_id}, + params); }, InputCommon::Polling::DeviceType::Button); }); @@ -288,20 +255,15 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) [this, analog_id, sub_button_id](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), this, [&] { - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + ClearBinding({InputBindingType::AnalogFromButton, QString(), analog_id, + sub_button_id}); }); context_menu.addAction(tr("Restore Default"), this, [&] { Common::ParamPackage params{InputCommon::GenerateKeyboardParam( QtConfig::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( - analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::AnalogFromButton, QString(), analog_id, + sub_button_id}, + params); }); context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( menu_location)); @@ -316,9 +278,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) HandleClick( analog_map_stick[analog_id], [this, analog_id](const Common::ParamPackage& params) { - analogs_param[analog_id] = params; - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::NativeAnalog, QString(), analog_id}, params); }, InputCommon::Polling::DeviceType::Analog); } @@ -348,40 +308,22 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) HandleClick( ui->buttonCircleMod, [this](const Common::ParamPackage& params) { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - SetAnalogButton(params, analogs_param[analog_id], "modifier"); - } - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + SetBinding({InputBindingType::CModButton, QString()}, params); }, InputCommon::Polling::DeviceType::Button); }); + ui->buttonCircleMod->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->buttonCircleMod, &QPushButton::customContextMenuRequested, this, [&](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), this, [&] { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - analogs_param[analog_id].Erase("modifier"); - } - ui->buttonCircleMod->setText(tr("[not set]")); - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + ClearBinding({InputBindingType::CModButton, QString()}); }); context_menu.addAction(tr("Restore Default"), this, [&] { - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; - analog_id++) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - QtConfig::default_analogs[analog_id][static_cast( - AnalogSubButtons::modifier)])}; - SetAnalogButton(params, analogs_param[analog_id], "modifier"); - ui->buttonCircleMod->setText( - AnalogToText(analogs_param[analog_id], "modifier")); - } - ApplyConfiguration(); - Settings::SaveProfile(ui->profile->currentIndex()); + Common::ParamPackage params{ + InputCommon::GenerateKeyboardParam(QtConfig::default_analogs[0][4])}; + SetBinding({InputBindingType::CModButton, QString()}, params); }); context_menu.exec(ui->buttonCircleMod->mapToGlobal(menu_location)); }); @@ -429,6 +371,80 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) ConfigureInput::~ConfigureInput() = default; +/** + * Returns true if we find a conflict and should stop, otherwise it clears the map and returns true. + */ +bool ConfigureInput::CheckForDuplicateMap(const Common::ParamPackage& params, + InputBinding this_binding) { + auto const current_binding = GetMapping(params); + bool abort = false; + if (current_binding.binding_type != InputBindingType::Empty && + !(current_binding.binding_type == this_binding.binding_type && + current_binding.index == this_binding.index && + current_binding.sub_index == this_binding.sub_index)) { + auto response = + QMessageBox::information(this, tr("Key Already Bound"), + tr("This key is already bound to the '%1' input.\n\n" + "Continuing will unbind the previous input. Proceed?") + .arg(current_binding.name), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (response == QMessageBox::No) { + abort = true; + } else { + ClearBinding(current_binding); + } + } + return abort; +} + +void ConfigureInput::SetBinding(InputBinding binding, const Common::ParamPackage& params) { + bool abort = CheckForDuplicateMap(params, binding); + if (!abort) { + if (binding.binding_type == InputBindingType::NativeButton) { + const auto button_id = binding.index; + // Workaround for ZL & ZR for analog triggers like on XBOX controllors. + // Analog triggers (from controllers like the XBOX controller) would not + // work due to a different range of their signals (from 0 to 255 on + // analog triggers instead of -32768 to 32768 on analog joysticks). The + // SDL driver misinterprets analog triggers as analog joysticks. + // TODO: reinterpret the signal range for analog triggers to map the + // values correctly. This is required for the correct emulation of the + // analog triggers of the GameCube controller. + Common::ParamPackage new_params = params; + if (button_id == Settings::NativeButton::ZL || + button_id == Settings::NativeButton::ZR) { + new_params.Set("direction", "+"); + new_params.Set("threshold", "0.5"); + } + buttons_param[button_id] = std::move(new_params); + } else if (binding.binding_type == InputBindingType::AnalogFromButton) { + const auto button_name = analog_sub_buttons[binding.sub_index]; + if (analogs_param[binding.index].Get("engine", "") != "analog_from_button") { + analogs_param[binding.index] = { + {"engine", "analog_from_button"}, + }; + } + analogs_param[binding.index].Set(button_name, params.Serialize()); + } else if (binding.binding_type == InputBindingType::NativeAnalog) { + const auto analog_id = binding.index; + analogs_param[analog_id] = params; + } else if (binding.binding_type == InputBindingType::CModButton) { + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + if (analogs_param[analog_id].Get("engine", "") != "analog_from_button") { + analogs_param[analog_id] = { + {"engine", "analog_from_button"}, + }; + } + analogs_param[analog_id].Set("modifier", params.Serialize()); + } + } + } + + ApplyConfiguration(); + UpdateButtonLabels(); + Settings::SaveProfile(ui->profile->currentIndex()); +} + void ConfigureInput::ApplyConfiguration() { Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked(); @@ -481,12 +497,12 @@ bool sameInput(const Common::ParamPackage& param1, const Common::ParamPackage& p ConfigureInput::InputBinding ConfigureInput::GetMapping(const Common::ParamPackage& param) { if (!param.Has("engine")) - return {"", QStringLiteral(""), 0}; + return {InputBindingType::Empty, QString(), 0}; // check for a button map for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) { const auto& button_param = buttons_param[button]; if (sameInput(param, button_param)) { - return {"NativeButton", button_names[button], button}; + return {InputBindingType::NativeButton, button_names[button], button}; } } // check for an analog map @@ -501,13 +517,14 @@ ConfigureInput::InputBinding ConfigureInput::GetMapping(const Common::ParamPacka analog_names[analog_id], analog_sub_button_names[sub_button_id]); if (analog_sub_buttons[sub_button_id] == "modifier") { - return {"CircleModButton", input_ui_string, 0}; + return {InputBindingType::CModButton, input_ui_string, 0}; } - return {"AnalogButton", input_ui_string, analog_id, sub_button_id}; + return {InputBindingType::AnalogFromButton, input_ui_string, analog_id, + sub_button_id}; } } } else if (sameInput(param, analog_param)) { - return {"Analog", analog_names[analog_id], analog_id}; + return {InputBindingType::NativeAnalog, analog_names[analog_id], analog_id}; } } @@ -516,7 +533,7 @@ ConfigureInput::InputBinding ConfigureInput::GetMapping(const Common::ParamPacka return hotkey_list[QKeySequence(param.Get("code", 0))]; } - return {"", QStringLiteral(""), 0}; + return {InputBindingType::Empty, QStringLiteral(""), 0}; } QMap ConfigureInput::GetUsedKeyboardKeys() { @@ -524,23 +541,31 @@ QMap ConfigureInput::GetUsedKeyboard for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { const auto& button_param = buttons_param[button]; if (button_param.Get("engine", "") == "keyboard") { - list[QKeySequence(button_param.Get("code", 0))] = - ConfigureInput::InputBinding{"NativeButton", button_names[button], button}; + list[QKeySequence(button_param.Get("code", 0))] = ConfigureInput::InputBinding{ + InputBindingType::NativeButton, button_names[button], button}; } } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { const auto& analog_param = analogs_param[analog_id]; if (analog_param.Get("engine", "") == "analog_from_button") { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + // stop one early so as not to include the "modifier" option + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM - 1; + ++sub_button_id) { const Common::ParamPackage sub_button{ analog_param.Get(analog_sub_buttons[sub_button_id], "")}; list[QKeySequence(sub_button.Get("code", 0))] = ConfigureInput::InputBinding{ - "AnalogButton", + InputBindingType::AnalogFromButton, QStringLiteral("%1 (%2)").arg(analog_names[analog_id], analog_sub_button_names[sub_button_id]), analog_id, sub_button_id}; } + // add the circle mod button if it exist + const Common::ParamPackage modButton{analog_param.Get("modifier", "")}; + if (modButton.Get("code", 0) != 0) { + list[QKeySequence(modButton.Get("code", 0))] = + InputBinding{InputBindingType::CModButton, QStringLiteral("Circle Mod")}; + } } } @@ -580,21 +605,21 @@ void ConfigureInput::RestoreDefaults() { } void ConfigureInput::ClearBinding(InputBinding binding) { - if (binding.binding_type == "NativeButton") { + if (binding.binding_type == InputBindingType::NativeButton) { buttons_param[binding.index].Clear(); button_map[binding.index]->setText(tr("[not set]")); - } else if (binding.binding_type == "AnalogButton") { + } else if (binding.binding_type == InputBindingType::AnalogFromButton) { const auto analog_id = binding.index; const auto sub_button_id = binding.sub_index; analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); - } else if (binding.binding_type == "Analog") { + } else if (binding.binding_type == InputBindingType::NativeAnalog) { const auto analog_id = binding.index; analogs_param[analog_id].Clear(); UpdateButtonLabels(); - } else if (binding.binding_type == "Hotkey") { + } else if (binding.binding_type == InputBindingType::Hotkey) { emit ClearHotkey(binding); - } else if (binding.binding_type == "CircleModButton") { + } else if (binding.binding_type == InputBindingType::CModButton) { for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { analogs_param[analog_id].Erase("modifier"); } @@ -744,26 +769,10 @@ void ConfigureInput::StopPolling() { } void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool abort) { - auto const current_binding = GetMapping(params); StopPolling(); - if (!abort && current_binding.binding_type != "") { - auto response = - QMessageBox::information(this, tr("Key Already Bound"), - tr("This key is already bound to the '%1' input.\n\n" - "Continuing will remove the previous binding. Proceed?") - .arg(current_binding.name), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - if (response == QMessageBox::No) { - abort = true; - return; - } else { - ClearBinding(current_binding); - } - } if (!abort && input_setter) { (*input_setter)(params); } - UpdateButtonLabels(); input_setter.reset(); } @@ -778,8 +787,8 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) { previous_key_code = 0; SetPollingResult(param, false); } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop - // polling + // Escape key wasn't pressed and we don't want any keyboard keys, so don't + // stop polling return; } } diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index c1aea6621..da8c4f645 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -30,12 +30,21 @@ class ConfigureInput : public QWidget { Q_OBJECT public: + enum class InputBindingType { + NativeButton, + AnalogFromButton, + NativeAnalog, + Hotkey, + CModButton, + Empty + }; struct InputBinding { - std::string binding_type; + ConfigureInput::InputBindingType binding_type; QString name; - int index; + int index = -1; // used for anything there are multiple of int sub_index = -1; // used for sub-buttons }; + explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr); ~ConfigureInput() override; @@ -111,7 +120,8 @@ private: InputBinding GetMapping(const Common::ParamPackage& param); void ClearBinding(InputBinding binding); - + void SetBinding(InputBinding binding, const Common::ParamPackage& params); + bool CheckForDuplicateMap(const Common::ParamPackage& params, InputBinding this_binding); void MapFromButton(const Common::ParamPackage& params); void AutoMap();