From fa32537f402b0ab84c612ff7fdf0c5e73b533016 Mon Sep 17 00:00:00 2001 From: rainmakerv2 <30595646+rainmakerv3@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:55:52 +0800 Subject: [PATCH] Controller Remapping GUI v2 (#3144) * Remapping GUI V2 - initial commit * Unmap button with escape key * Allow combination inputs * Use separate class for SDL event signals so that i can work with the SDL window event loop * Automatically pause game when GUI open to better manage event queue * Move sd;_gamepad_added event from remap object to GUI object to avoid conflicts with sdl window * Use signals on button/trigger to release to make GUI more responsive * pause game while KBM window is open for consistency * don't check gamepad when game is running to avoid conflicts * Block all other sdl events instead of pausing game, automatic parse inputs after saving * Don't block window restored or window exposed cases * Properly exit event loop thread on exit --- CMakeLists.txt | 2 + src/qt_gui/control_settings.cpp | 907 +++++++++++++++++++++---------- src/qt_gui/control_settings.h | 70 ++- src/qt_gui/control_settings.ui | 676 +++++++++++++---------- src/qt_gui/kbm_gui.cpp | 40 +- src/qt_gui/kbm_gui.h | 6 +- src/qt_gui/main_window.cpp | 9 +- src/qt_gui/main_window.h | 2 + src/qt_gui/sdl_event_wrapper.cpp | 47 ++ src/qt_gui/sdl_event_wrapper.h | 25 + src/sdl_window.cpp | 11 + 11 files changed, 1194 insertions(+), 601 deletions(-) create mode 100644 src/qt_gui/sdl_event_wrapper.cpp create mode 100644 src/qt_gui/sdl_event_wrapper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab846fa9d..466933608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1068,6 +1068,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/gui_settings.h src/qt_gui/settings.cpp src/qt_gui/settings.h + src/qt_gui/sdl_event_wrapper.cpp + src/qt_gui/sdl_event_wrapper.h ${EMULATOR} ${RESOURCE_FILES} ${TRANSLATIONS} diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 4206e45b8..319daecdd 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -2,21 +2,67 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include +#include "common/logging/log.h" #include "common/path_util.h" #include "control_settings.h" +#include "input/input_handler.h" #include "ui_control_settings.h" -ControlSettings::ControlSettings(std::shared_ptr game_info_get, QWidget* parent) - : QDialog(parent), m_game_info(game_info_get), ui(new Ui::ControlSettings) { +ControlSettings::ControlSettings(std::shared_ptr game_info_get, bool isGameRunning, + std::string GameRunningSerial, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), GameRunning(isGameRunning), + RunningGameSerial(GameRunningSerial), ui(new Ui::ControlSettings) { ui->setupUi(this); - ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + + if (!GameRunning) { + SDL_InitSubSystem(SDL_INIT_GAMEPAD); + SDL_InitSubSystem(SDL_INIT_EVENTS); + CheckGamePad(); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + } AddBoxItems(); SetUIValuestoMappings(); UpdateLightbarColor(); + installEventFilter(this); + + ButtonsList = {ui->CrossButton, + ui->CircleButton, + ui->TriangleButton, + ui->SquareButton, + ui->L1Button, + ui->R1Button, + ui->L2Button, + ui->R2Button, + ui->L3Button, + ui->R3Button, + ui->OptionsButton, + ui->TouchpadLeftButton, + ui->TouchpadCenterButton, + ui->TouchpadRightButton, + ui->DpadUpButton, + ui->DpadDownButton, + ui->DpadLeftButton, + ui->DpadRightButton}; + + AxisList = {ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, + ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, + ui->RStickLeftButton, ui->RStickRightButton}; + + for (auto& button : ButtonsList) { + connect(button, &QPushButton::clicked, this, + [this, &button]() { StartTimer(button, true); }); + } + + for (auto& button : AxisList) { + connect(button, &QPushButton::clicked, this, + [this, &button]() { StartTimer(button, false); }); + } connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { @@ -33,6 +79,8 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { @@ -45,24 +93,6 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q connect(ui->RightDeadzoneSlider, &QSlider::valueChanged, this, [this](int value) { ui->RightDeadzoneValue->setText(QString::number(value)); }); - connect(ui->LStickUpBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickDownBox->setCurrentIndex(value); }); - connect(ui->LStickDownBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickUpBox->setCurrentIndex(value); }); - connect(ui->LStickRightBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickLeftBox->setCurrentIndex(value); }); - connect(ui->LStickLeftBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickRightBox->setCurrentIndex(value); }); - - connect(ui->RStickUpBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickDownBox->setCurrentIndex(value); }); - connect(ui->RStickDownBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickUpBox->setCurrentIndex(value); }); - connect(ui->RStickRightBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickLeftBox->setCurrentIndex(value); }); - connect(ui->RStickLeftBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickRightBox->setCurrentIndex(value); }); - connect(ui->RSlider, &QSlider::valueChanged, this, [this](int value) { QString RedValue = QString("%1").arg(value, 3, 10, QChar('0')); QString RValue = tr("R:") + " " + RedValue; @@ -83,30 +113,44 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q ui->BLabel->setText(BValue); UpdateLightbarColor(); }); + + connect(this, &ControlSettings::PushGamepadEvent, this, + [this]() { CheckMapping(MappingButton); }); + connect(this, &ControlSettings::AxisChanged, this, + [this]() { ConnectAxisInputs(MappingButton); }); + + RemapWrapper = SdlEventWrapper::Wrapper::GetInstance(); + SdlEventWrapper::Wrapper::wrapperActive = true; + QObject::connect(RemapWrapper, &SdlEventWrapper::Wrapper::SDLEvent, this, + &ControlSettings::processSDLEvents); + + if (!GameRunning) { + Polling = QtConcurrent::run(&ControlSettings::pollSDLEvents, this); + } } void ControlSettings::SaveControllerConfig(bool CloseOnSave) { - QList list; - list << ui->RStickUpBox << ui->RStickRightBox << ui->LStickUpBox << ui->LStickRightBox; + QList list; + list << ui->RStickUpButton << ui->RStickRightButton << ui->LStickUpButton + << ui->LStickRightButton; int count_axis_left_x = 0, count_axis_left_y = 0, count_axis_right_x = 0, count_axis_right_y = 0; for (const auto& i : list) { - if (i->currentText() == "axis_left_x") { + if (i->text() == "axis_left_x") { count_axis_left_x = count_axis_left_x + 1; - } else if (i->currentText() == "axis_left_y") { + } else if (i->text() == "axis_left_y") { count_axis_left_y = count_axis_left_y + 1; - } else if (i->currentText() == "axis_right_x") { + } else if (i->text() == "axis_right_x") { count_axis_right_x = count_axis_right_x + 1; - } else if (i->currentText() == "axis_right_y") { + } else if (i->text() == "axis_right_y") { count_axis_right_y = count_axis_right_y + 1; } } if (count_axis_left_x > 1 | count_axis_left_y > 1 | count_axis_right_x > 1 | count_axis_right_y > 1) { - QMessageBox::StandardButton nosave; - nosave = QMessageBox::information(this, tr("Unable to Save"), - tr("Cannot bind axis values more than once")); + QMessageBox::information(this, tr("Unable to Save"), + tr("Cannot bind axis values more than once")); return; } @@ -118,7 +162,7 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { int lineCount = 0; std::string line; - std::vector lines; + std::vector lines, inputs; std::string output_string = "", input_string = ""; std::fstream file(config_file); @@ -141,9 +185,17 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected || output_string == "analog_deadzone" || + output_string == "override_controller_color") { line.erase(); continue; } @@ -152,92 +204,60 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { file.close(); - input_string = "cross"; - output_string = ui->ABox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + // Lambda to reduce repetitive code for mapping buttons to config lines + auto add_mapping = [&](const QString& buttonText, const std::string& output_name) { + input_string = buttonText.toStdString(); + output_string = output_name; + if (input_string != "unmapped") { + lines.push_back(output_string + " = " + input_string); + inputs.push_back(input_string); + } + }; - input_string = "circle"; - output_string = ui->BBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "square"; - output_string = ui->XBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "triangle"; - output_string = ui->YBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->CrossButton->text(), "cross"); + add_mapping(ui->CircleButton->text(), "circle"); + add_mapping(ui->SquareButton->text(), "square"); + add_mapping(ui->TriangleButton->text(), "triangle"); lines.push_back(""); - input_string = "l1"; - output_string = ui->LBBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r1"; - output_string = ui->RBBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "l2"; - output_string = ui->LTBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r2"; - output_string = ui->RTBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "l3"; - output_string = ui->LClickBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r3"; - output_string = ui->RClickBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->L1Button->text(), "l1"); + add_mapping(ui->R1Button->text(), "r1"); + add_mapping(ui->L2Button->text(), "l2"); + add_mapping(ui->R2Button->text(), "r2"); + add_mapping(ui->L3Button->text(), "l3"); + add_mapping(ui->R3Button->text(), "r3"); lines.push_back(""); - input_string = "back"; - output_string = ui->BackBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "options"; - output_string = ui->StartBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->TouchpadLeftButton->text(), "touchpad_left"); + add_mapping(ui->TouchpadCenterButton->text(), "touchpad_center"); + add_mapping(ui->TouchpadRightButton->text(), "touchpad_right"); + add_mapping(ui->OptionsButton->text(), "options"); lines.push_back(""); - input_string = "pad_up"; - output_string = ui->DpadUpBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_down"; - output_string = ui->DpadDownBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_left"; - output_string = ui->DpadLeftBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_right"; - output_string = ui->DpadRightBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->DpadUpButton->text(), "pad_up"); + add_mapping(ui->DpadDownButton->text(), "pad_down"); + add_mapping(ui->DpadLeftButton->text(), "pad_left"); + add_mapping(ui->DpadRightButton->text(), "pad_right"); lines.push_back(""); - input_string = "axis_left_x"; - output_string = ui->LStickRightBox->currentText().toStdString(); + output_string = "axis_left_x"; + input_string = ui->LStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_left_y"; - output_string = ui->LStickUpBox->currentText().toStdString(); + output_string = "axis_left_y"; + input_string = ui->LStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_right_x"; - output_string = ui->RStickRightBox->currentText().toStdString(); + output_string = "axis_right_x"; + input_string = ui->RStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_right_y"; - output_string = ui->RStickUpBox->currentText().toStdString(); + output_string = "axis_right_y"; + input_string = ui->RStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); lines.push_back(""); @@ -257,6 +277,33 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { lines.push_back("override_controller_color = " + OverrideLB + ", " + LightBarR + ", " + LightBarG + ", " + LightBarB); + // Prevent duplicate inputs that break the input engine + bool duplicateFound = false; + QSet duplicateMappings; + + for (auto it = inputs.begin(); it != inputs.end(); ++it) { + if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { + duplicateFound = true; + duplicateMappings.insert(QString::fromStdString(*it)); + } + } + + if (duplicateFound) { + QStringList duplicatesList; + for (const QString mapping : duplicateMappings) { + for (const auto& button : ButtonsList) { + if (button->text() == mapping) + duplicatesList.append(button->objectName() + " - " + mapping); + } + } + QMessageBox::information( + this, tr("Unable to Save"), + // clang-format off + QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n")))); + // clang-format on + return; + } + std::vector save; bool CurrentLineEmpty = false, LastLineEmpty = false; for (auto const& line : lines) { @@ -278,36 +325,43 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { ui->BSlider->value()); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + if (GameRunning) { + Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default") + : Input::ParseInputConfig(RunningGameSerial); + } + if (CloseOnSave) QWidget::close(); } void ControlSettings::SetDefault() { - ui->ABox->setCurrentIndex(0); - ui->BBox->setCurrentIndex(1); - ui->XBox->setCurrentIndex(2); - ui->YBox->setCurrentIndex(3); - ui->DpadUpBox->setCurrentIndex(11); - ui->DpadDownBox->setCurrentIndex(12); - ui->DpadLeftBox->setCurrentIndex(13); - ui->DpadRightBox->setCurrentIndex(14); - ui->LClickBox->setCurrentIndex(8); - ui->RClickBox->setCurrentIndex(9); - ui->LBBox->setCurrentIndex(4); - ui->RBBox->setCurrentIndex(5); - ui->LTBox->setCurrentIndex(6); - ui->RTBox->setCurrentIndex(7); - ui->StartBox->setCurrentIndex(10); - ui->BackBox->setCurrentIndex(15); + ui->CrossButton->setText("cross"); + ui->CircleButton->setText("circle"); + ui->SquareButton->setText("square"); + ui->TriangleButton->setText("triangle"); + ui->DpadUpButton->setText("pad_up"); + ui->DpadDownButton->setText("pad_down"); + ui->DpadLeftButton->setText("pad_left"); + ui->DpadRightButton->setText("pad_right"); + ui->L3Button->setText("l3"); + ui->R3Button->setText("r3"); + ui->L1Button->setText("l1"); + ui->R1Button->setText("r1"); + ui->L2Button->setText("l2"); + ui->R2Button->setText("r2"); + ui->OptionsButton->setText("options"); + ui->TouchpadLeftButton->setText("back"); + ui->TouchpadCenterButton->setText("unmapped"); + ui->TouchpadRightButton->setText("unmapped"); - ui->LStickUpBox->setCurrentIndex(1); - ui->LStickDownBox->setCurrentIndex(1); - ui->LStickLeftBox->setCurrentIndex(0); - ui->LStickRightBox->setCurrentIndex(0); - ui->RStickUpBox->setCurrentIndex(3); - ui->RStickDownBox->setCurrentIndex(3); - ui->RStickLeftBox->setCurrentIndex(2); - ui->RStickRightBox->setCurrentIndex(2); + ui->LStickUpButton->setText("axis_left_y"); + ui->LStickDownButton->setText("axis_left_y"); + ui->LStickLeftButton->setText("axis_left_x"); + ui->LStickRightButton->setText("axis_left_x"); + ui->RStickUpButton->setText("axis_right_y"); + ui->RStickDownButton->setText("axis_right_y"); + ui->RStickLeftButton->setText("axis_right_x"); + ui->RStickRightButton->setText("axis_right_x"); ui->LeftDeadzoneSlider->setValue(2); ui->RightDeadzoneSlider->setValue(2); @@ -320,32 +374,6 @@ void ControlSettings::SetDefault() { } void ControlSettings::AddBoxItems() { - ui->DpadUpBox->addItems(ButtonOutputs); - ui->DpadDownBox->addItems(ButtonOutputs); - ui->DpadLeftBox->addItems(ButtonOutputs); - ui->DpadRightBox->addItems(ButtonOutputs); - ui->LBBox->addItems(ButtonOutputs); - ui->RBBox->addItems(ButtonOutputs); - ui->LTBox->addItems(ButtonOutputs); - ui->RTBox->addItems(ButtonOutputs); - ui->RClickBox->addItems(ButtonOutputs); - ui->LClickBox->addItems(ButtonOutputs); - ui->StartBox->addItems(ButtonOutputs); - ui->ABox->addItems(ButtonOutputs); - ui->BBox->addItems(ButtonOutputs); - ui->XBox->addItems(ButtonOutputs); - ui->YBox->addItems(ButtonOutputs); - ui->BackBox->addItems(ButtonOutputs); - - ui->LStickUpBox->addItems(StickOutputs); - ui->LStickDownBox->addItems(StickOutputs); - ui->LStickLeftBox->addItems(StickOutputs); - ui->LStickRightBox->addItems(StickOutputs); - ui->RStickUpBox->addItems(StickOutputs); - ui->RStickDownBox->addItems(StickOutputs); - ui->RStickLeftBox->addItems(StickOutputs); - ui->RStickRightBox->addItems(StickOutputs); - ui->ProfileComboBox->addItem("Common Config"); for (int i = 0; i < m_game_info->m_games.size(); i++) { ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial)); @@ -366,7 +394,8 @@ void ControlSettings::SetUIValuestoMappings() { bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false, L1Exists = false, L2Exists = false, L3Exists = false, R1Exists = false, R2Exists = false, R3Exists = false, DPadUpExists = false, DPadDownExists = false, DPadLeftExists = false, - DPadRightExists = false, StartExists = false, BackExists = false, LStickXExists = false, + DPadRightExists = false, OptionsExists = false, TouchpadLeftExists = false, + TouchpadCenterExists = false, TouchpadRightExists = false, LStickXExists = false, LStickYExists = false, RStickXExists = false, RStickYExists = false; int lineCount = 0; std::string line = ""; @@ -388,127 +417,144 @@ void ControlSettings::SetUIValuestoMappings() { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { - if (input_string == "cross") { - ui->ABox->setCurrentText(QString::fromStdString(output_string)); + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected) { + if (output_string == "cross") { + ui->CrossButton->setText(QString::fromStdString(input_string)); CrossExists = true; - } else if (input_string == "circle") { - ui->BBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "circle") { + ui->CircleButton->setText(QString::fromStdString(input_string)); CircleExists = true; - } else if (input_string == "square") { - ui->XBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "square") { + ui->SquareButton->setText(QString::fromStdString(input_string)); SquareExists = true; - } else if (input_string == "triangle") { - ui->YBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "triangle") { + ui->TriangleButton->setText(QString::fromStdString(input_string)); TriangleExists = true; - } else if (input_string == "l1") { - ui->LBBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l1") { + ui->L1Button->setText(QString::fromStdString(input_string)); L1Exists = true; - } else if (input_string == "l2") { - ui->LTBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l2") { + ui->L2Button->setText(QString::fromStdString(input_string)); L2Exists = true; - } else if (input_string == "r1") { - ui->RBBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r1") { + ui->R1Button->setText(QString::fromStdString(input_string)); R1Exists = true; - } else if (input_string == "r2") { - ui->RTBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r2") { + ui->R2Button->setText(QString::fromStdString(input_string)); R2Exists = true; - } else if (input_string == "l3") { - ui->LClickBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l3") { + ui->L3Button->setText(QString::fromStdString(input_string)); L3Exists = true; - } else if (input_string == "r3") { - ui->RClickBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r3") { + ui->R3Button->setText(QString::fromStdString(input_string)); R3Exists = true; - } else if (input_string == "pad_up") { - ui->DpadUpBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_up") { + ui->DpadUpButton->setText(QString::fromStdString(input_string)); DPadUpExists = true; - } else if (input_string == "pad_down") { - ui->DpadDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_down") { + ui->DpadDownButton->setText(QString::fromStdString(input_string)); DPadDownExists = true; - } else if (input_string == "pad_left") { - ui->DpadLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_left") { + ui->DpadLeftButton->setText(QString::fromStdString(input_string)); DPadLeftExists = true; - } else if (input_string == "pad_right") { - ui->DpadRightBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_right") { + ui->DpadRightButton->setText(QString::fromStdString(input_string)); DPadRightExists = true; - } else if (input_string == "options") { - ui->StartBox->setCurrentText(QString::fromStdString(output_string)); - StartExists = true; - } else if (input_string == "back") { - ui->BackBox->setCurrentText(QString::fromStdString(output_string)); - BackExists = true; - } else if (input_string == "axis_left_x") { - ui->LStickRightBox->setCurrentText(QString::fromStdString(output_string)); - ui->LStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "options") { + ui->OptionsButton->setText(QString::fromStdString(input_string)); + OptionsExists = true; + } else if (output_string == "touchpad_left") { + ui->TouchpadLeftButton->setText(QString::fromStdString(input_string)); + TouchpadLeftExists = true; + } else if (output_string == "touchpad_center") { + ui->TouchpadCenterButton->setText(QString::fromStdString(input_string)); + TouchpadCenterExists = true; + } else if (output_string == "touchpad_right") { + ui->TouchpadRightButton->setText(QString::fromStdString(input_string)); + TouchpadRightExists = true; + } else if (output_string == "axis_left_x") { + ui->LStickRightButton->setText(QString::fromStdString(input_string)); + ui->LStickLeftButton->setText(QString::fromStdString(input_string)); LStickXExists = true; - } else if (input_string == "axis_left_y") { - ui->LStickUpBox->setCurrentText(QString::fromStdString(output_string)); - ui->LStickDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_left_y") { + ui->LStickUpButton->setText(QString::fromStdString(input_string)); + ui->LStickDownButton->setText(QString::fromStdString(input_string)); LStickYExists = true; - } else if (input_string == "axis_right_x") { - ui->RStickRightBox->setCurrentText(QString::fromStdString(output_string)); - ui->RStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_right_x") { + ui->RStickRightButton->setText(QString::fromStdString(input_string)); + ui->RStickLeftButton->setText(QString::fromStdString(input_string)); RStickXExists = true; - } else if (input_string == "axis_right_y") { - ui->RStickUpBox->setCurrentText(QString::fromStdString(output_string)); - ui->RStickDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_right_y") { + ui->RStickUpButton->setText(QString::fromStdString(input_string)); + ui->RStickDownButton->setText(QString::fromStdString(input_string)); RStickYExists = true; - } else if (input_string.contains("leftjoystick")) { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); - ui->LeftDeadzoneSlider->setValue(deadzonevalue); - ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); - } else { - ui->LeftDeadzoneSlider->setValue(2); - ui->LeftDeadzoneValue->setText("2"); + } + } + + if (input_string.contains("leftjoystick")) { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->LeftDeadzoneSlider->setValue(deadzonevalue); + ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->LeftDeadzoneSlider->setValue(2); + ui->LeftDeadzoneValue->setText("2"); + } + } + + if (input_string.contains("rightjoystick")) { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->RightDeadzoneSlider->setValue(deadzonevalue); + ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->RightDeadzoneSlider->setValue(2); + ui->RightDeadzoneValue->setText("2"); + } + } + + if (output_string == "override_controller_color") { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + std::string overridestring = line.substr(equal_pos + 1, comma_pos); + bool override = overridestring.contains("true") ? true : false; + ui->LightbarCheckBox->setChecked(override); + + std::string lightbarstring = line.substr(comma_pos + 1); + std::size_t comma_pos2 = lightbarstring.find(','); + if (comma_pos2 != std::string::npos) { + std::string Rstring = lightbarstring.substr(0, comma_pos2); + ui->RSlider->setValue(std::stoi(Rstring)); + QString RedValue = QString("%1").arg(std::stoi(Rstring), 3, 10, QChar('0')); + QString RValue = tr("R:") + " " + RedValue; + ui->RLabel->setText(RValue); } - } else if (input_string.contains("rightjoystick")) { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); - ui->RightDeadzoneSlider->setValue(deadzonevalue); - ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); - } else { - ui->RightDeadzoneSlider->setValue(2); - ui->RightDeadzoneValue->setText("2"); - } - } else if (output_string == "override_controller_color") { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - std::string overridestring = line.substr(equal_pos + 1, comma_pos); - bool override = overridestring.contains("true") ? true : false; - ui->LightbarCheckBox->setChecked(override); - std::string lightbarstring = line.substr(comma_pos + 1); - std::size_t comma_pos2 = lightbarstring.find(','); - if (comma_pos2 != std::string::npos) { - std::string Rstring = lightbarstring.substr(0, comma_pos2); - ui->RSlider->setValue(std::stoi(Rstring)); - QString RedValue = QString("%1").arg(std::stoi(Rstring), 3, 10, QChar('0')); - QString RValue = tr("R:") + " " + RedValue; - ui->RLabel->setText(RValue); - } + std::string GBstring = lightbarstring.substr(comma_pos2 + 1); + std::size_t comma_pos3 = GBstring.find(','); + if (comma_pos3 != std::string::npos) { + std::string Gstring = GBstring.substr(0, comma_pos3); + ui->GSlider->setValue(std::stoi(Gstring)); + QString GreenValue = QString("%1").arg(std::stoi(Gstring), 3, 10, QChar('0')); + QString GValue = tr("G:") + " " + GreenValue; + ui->GLabel->setText(GValue); - std::string GBstring = lightbarstring.substr(comma_pos2 + 1); - std::size_t comma_pos3 = GBstring.find(','); - if (comma_pos3 != std::string::npos) { - std::string Gstring = GBstring.substr(0, comma_pos3); - ui->GSlider->setValue(std::stoi(Gstring)); - QString GreenValue = - QString("%1").arg(std::stoi(Gstring), 3, 10, QChar('0')); - QString GValue = tr("G:") + " " + GreenValue; - ui->GLabel->setText(GValue); - - std::string Bstring = GBstring.substr(comma_pos3 + 1); - ui->BSlider->setValue(std::stoi(Bstring)); - QString BlueValue = - QString("%1").arg(std::stoi(Bstring), 3, 10, QChar('0')); - QString BValue = tr("B:") + " " + BlueValue; - ui->BLabel->setText(BValue); - } + std::string Bstring = GBstring.substr(comma_pos3 + 1); + ui->BSlider->setValue(std::stoi(Bstring)); + QString BlueValue = QString("%1").arg(std::stoi(Bstring), 3, 10, QChar('0')); + QString BValue = tr("B:") + " " + BlueValue; + ui->BLabel->setText(BValue); } } } @@ -517,53 +563,57 @@ void ControlSettings::SetUIValuestoMappings() { // If an entry does not exist in the config file, we assume the user wants it unmapped if (!CrossExists) - ui->ABox->setCurrentText("unmapped"); + ui->CrossButton->setText("unmapped"); if (!CircleExists) - ui->BBox->setCurrentText("unmapped"); + ui->CircleButton->setText("unmapped"); if (!SquareExists) - ui->XBox->setCurrentText("unmapped"); + ui->SquareButton->setText("unmapped"); if (!TriangleExists) - ui->YBox->setCurrentText("unmapped"); + ui->TriangleButton->setText("unmapped"); if (!L1Exists) - ui->LBBox->setCurrentText("unmapped"); + ui->L1Button->setText("unmapped"); if (!L2Exists) - ui->LTBox->setCurrentText("unmapped"); + ui->L2Button->setText("unmapped"); if (!L3Exists) - ui->LClickBox->setCurrentText("unmapped"); + ui->L3Button->setText("unmapped"); if (!R1Exists) - ui->RBBox->setCurrentText("unmapped"); + ui->R1Button->setText("unmapped"); if (!R2Exists) - ui->RTBox->setCurrentText("unmapped"); + ui->R2Button->setText("unmapped"); if (!R3Exists) - ui->RClickBox->setCurrentText("unmapped"); + ui->R3Button->setText("unmapped"); if (!DPadUpExists) - ui->DpadUpBox->setCurrentText("unmapped"); + ui->DpadUpButton->setText("unmapped"); if (!DPadDownExists) - ui->DpadDownBox->setCurrentText("unmapped"); + ui->DpadDownButton->setText("unmapped"); if (!DPadLeftExists) - ui->DpadLeftBox->setCurrentText("unmapped"); + ui->DpadLeftButton->setText("unmapped"); if (!DPadRightExists) - ui->DpadRightBox->setCurrentText("unmapped"); - if (!BackExists) - ui->BackBox->setCurrentText("unmapped"); - if (!StartExists) - ui->StartBox->setCurrentText("unmapped"); + ui->DpadRightButton->setText("unmapped"); + if (!TouchpadLeftExists) + ui->TouchpadLeftButton->setText("unmapped"); + if (!TouchpadCenterExists) + ui->TouchpadCenterButton->setText("unmapped"); + if (!TouchpadRightExists) + ui->TouchpadRightButton->setText("unmapped"); + if (!OptionsExists) + ui->OptionsButton->setText("unmapped"); if (!LStickXExists) { - ui->LStickRightBox->setCurrentText("unmapped"); - ui->LStickLeftBox->setCurrentText("unmapped"); + ui->LStickRightButton->setText("unmapped"); + ui->LStickLeftButton->setText("unmapped"); } if (!LStickYExists) { - ui->LStickUpBox->setCurrentText("unmapped"); - ui->LStickDownBox->setCurrentText("unmapped"); + ui->LStickUpButton->setText("unmapped"); + ui->LStickDownButton->setText("unmapped"); } if (!RStickXExists) { - ui->RStickRightBox->setCurrentText("unmapped"); - ui->RStickLeftBox->setCurrentText("unmapped"); + ui->RStickRightButton->setText("unmapped"); + ui->RStickLeftButton->setText("unmapped"); } if (!RStickYExists) { - ui->RStickUpBox->setCurrentText("unmapped"); - ui->RStickDownBox->setCurrentText("unmapped"); + ui->RStickUpButton->setText("unmapped"); + ui->RStickDownButton->setText("unmapped"); } } @@ -589,4 +639,305 @@ void ControlSettings::UpdateLightbarColor() { ui->LightbarColorFrame->setStyleSheet(colorstring); } +void ControlSettings::CheckGamePad() { + if (GameRunning) + return; + + if (gamepad) { + SDL_CloseGamepad(gamepad); + gamepad = nullptr; + } + + int gamepad_count; + SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); + + if (!gamepads) { + LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); + return; + } + + if (gamepad_count == 0) { + LOG_INFO(Input, "No gamepad found!"); + SDL_free(gamepads); + return; + } + + LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); + gamepad = SDL_OpenGamepad(gamepads[0]); + + if (!gamepad) { + LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); + SDL_free(gamepads); + return; + } + + SDL_free(gamepads); +} + +void ControlSettings::DisableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(false); + } + + for (const auto& i : AxisList) { + i->setEnabled(false); + } +} + +void ControlSettings::EnableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(true); + } + + for (const auto& i : AxisList) { + i->setEnabled(true); + } +} + +void ControlSettings::ConnectAxisInputs(QPushButton*& button) { + QString input = button->text(); + if (button == ui->LStickUpButton) { + ui->LStickDownButton->setText(input); + } else if (button == ui->LStickDownButton) { + ui->LStickUpButton->setText(input); + } else if (button == ui->LStickLeftButton) { + ui->LStickRightButton->setText(input); + } else if (button == ui->LStickRightButton) { + ui->LStickLeftButton->setText(input); + } else if (button == ui->RStickUpButton) { + ui->RStickDownButton->setText(input); + } else if (button == ui->RStickDownButton) { + ui->RStickUpButton->setText(input); + } else if (button == ui->RStickLeftButton) { + ui->RStickRightButton->setText(input); + } else if (button == ui->RStickRightButton) { + ui->RStickLeftButton->setText(input); + } +} + +void ControlSettings::StartTimer(QPushButton*& button, bool isButton) { + MappingTimer = 3; + isButton ? EnableButtonMapping = true : EnableAxisMapping = true; + MappingCompleted = false; + mapping = button->text(); + DisableMappingButtons(); + + EnableButtonMapping + ? button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]") + : button->setText(tr("Move analog stick") + " [" + QString::number(MappingTimer) + "]"); + + timer = new QTimer(this); + MappingButton = button; + timer->start(1000); + connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); }); +} + +void ControlSettings::CheckMapping(QPushButton*& button) { + MappingTimer -= 1; + EnableButtonMapping + ? button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]") + : button->setText(tr("Move analog stick") + " [" + QString::number(MappingTimer) + "]"); + + if (pressedButtons.size() > 0) { + QStringList keyStrings; + + for (const QString& buttonAction : pressedButtons) { + keyStrings << buttonAction; + } + + QString combo = keyStrings.join(","); + SetMapping(combo); + MappingButton->setText(combo); + pressedButtons.clear(); + } + + if (MappingCompleted || MappingTimer <= 0) { + button->setText(mapping); + EnableButtonMapping = false; + EnableAxisMapping = false; + L2Pressed = false; + R2Pressed = false; + EnableMappingButtons(); + timer->stop(); + } +} + +void ControlSettings::SetMapping(QString input) { + mapping = input; + MappingCompleted = true; + if (EnableAxisMapping) { + emit PushGamepadEvent(); + emit AxisChanged(); + } +} + +// use QT events instead of SDL to override default event closing the window with escape +bool ControlSettings::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::KeyPress && EnableButtonMapping) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + SetMapping("unmapped"); + return true; + } + } + return QDialog::eventFilter(obj, event); +} + +void ControlSettings::processSDLEvents(int Type, int Input, int Value) { + if (EnableButtonMapping) { + if (Type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + switch (Input) { + case SDL_GAMEPAD_BUTTON_SOUTH: + pressedButtons.insert("cross"); + break; + case SDL_GAMEPAD_BUTTON_EAST: + pressedButtons.insert("circle"); + break; + case SDL_GAMEPAD_BUTTON_NORTH: + pressedButtons.insert("triangle"); + break; + case SDL_GAMEPAD_BUTTON_WEST: + pressedButtons.insert("square"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + pressedButtons.insert("l1"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + pressedButtons.insert("r1"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + pressedButtons.insert("l3"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + pressedButtons.insert("r3"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + pressedButtons.insert("pad_up"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + pressedButtons.insert("pad_down"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + pressedButtons.insert("pad_left"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + pressedButtons.insert("pad_right"); + break; + case SDL_GAMEPAD_BUTTON_BACK: + pressedButtons.insert("back"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: + pressedButtons.insert("lpaddle_high"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: + pressedButtons.insert("rpaddle_high"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: + pressedButtons.insert("lpaddle_low"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: + pressedButtons.insert("rpaddle_low"); + break; + case SDL_GAMEPAD_BUTTON_START: + pressedButtons.insert("options"); + break; + default: + break; + } + } + + if (Type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + // SDL trigger axis values range from 0 to 32000, set mapping on half movement + // Set zone for trigger release signal arbitrarily at 5000 + switch (Input) { + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + if (Value > 16000) { + pressedButtons.insert("l2"); + L2Pressed = true; + } else if (Value < 5000) { + if (L2Pressed) + emit PushGamepadEvent(); + } + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + if (Value > 16000) { + pressedButtons.insert("r2"); + R2Pressed = true; + } else if (Value < 5000) { + if (R2Pressed) + emit PushGamepadEvent(); + } + break; + default: + break; + } + } + + if (Type == SDL_EVENT_GAMEPAD_BUTTON_UP) + emit PushGamepadEvent(); + + } else if (EnableAxisMapping) { + if (Type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + // SDL stick axis values range from -32000 to 32000, set mapping on half movement + if (Value > 16000 || Value < -16000) { + switch (Input) { + case SDL_GAMEPAD_AXIS_LEFTX: + SetMapping("axis_left_x"); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + SetMapping("axis_left_y"); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + SetMapping("axis_right_x"); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + SetMapping("axis_right_y"); + break; + default: + break; + } + } + } + } +} + +void ControlSettings::pollSDLEvents() { + SDL_Event event; + while (SdlEventWrapper::Wrapper::wrapperActive) { + + if (!SDL_WaitEvent(&event)) { + return; + } + + if (event.type == SDL_EVENT_QUIT) { + return; + } + + if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + CheckGamePad(); + } + + SdlEventWrapper::Wrapper::GetInstance()->Wrapper::ProcessEvent(&event); + } +} + +void ControlSettings::Cleanup() { + SdlEventWrapper::Wrapper::wrapperActive = false; + if (gamepad) + SDL_CloseGamepad(gamepad); + + if (!GameRunning) { + SDL_Event quitLoop{}; + quitLoop.type = SDL_EVENT_QUIT; + SDL_PushEvent(&quitLoop); + Polling.waitForFinished(); + + SDL_QuitSubSystem(SDL_INIT_GAMEPAD); + SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_Quit(); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0"); + } +} + ControlSettings::~ControlSettings() {} diff --git a/src/qt_gui/control_settings.h b/src/qt_gui/control_settings.h index b1fff1dad..76d16b84e 100644 --- a/src/qt_gui/control_settings.h +++ b/src/qt_gui/control_settings.h @@ -2,7 +2,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include "game_info.h" +#include "sdl_event_wrapper.h" namespace Ui { class ControlSettings; @@ -11,22 +14,56 @@ class ControlSettings; class ControlSettings : public QDialog { Q_OBJECT public: - explicit ControlSettings(std::shared_ptr game_info_get, - QWidget* parent = nullptr); + explicit ControlSettings(std::shared_ptr game_info_get, bool GameRunning, + std::string GameRunningSerial, QWidget* parent = nullptr); ~ControlSettings(); +signals: + void PushGamepadEvent(); + void AxisChanged(); + private Q_SLOTS: void SaveControllerConfig(bool CloseOnSave); void SetDefault(); void UpdateLightbarColor(); + void CheckMapping(QPushButton*& button); + void StartTimer(QPushButton*& button, bool isButton); + void ConnectAxisInputs(QPushButton*& button); private: std::unique_ptr ui; std::shared_ptr m_game_info; + bool eventFilter(QObject* obj, QEvent* event) override; void AddBoxItems(); void SetUIValuestoMappings(); void GetGameTitle(); + void CheckGamePad(); + void processSDLEvents(int Type, int Input, int Value); + void pollSDLEvents(); + void SetMapping(QString input); + void DisableMappingButtons(); + void EnableMappingButtons(); + void Cleanup(); + + QList ButtonsList; + QList AxisList; + QSet pressedButtons; + + std::string RunningGameSerial; + bool GameRunning; + bool L2Pressed = false; + bool R2Pressed = false; + bool EnableButtonMapping = false; + bool EnableAxisMapping = false; + bool MappingCompleted = false; + QString mapping; + int MappingTimer; + QTimer* timer; + QPushButton* MappingButton; + SDL_Gamepad* gamepad = nullptr; + SdlEventWrapper::Wrapper* RemapWrapper; + QFuture Polling; const std::vector ControllerInputs = { "cross", "circle", "square", "triangle", "l1", @@ -39,29 +76,8 @@ private: "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", "back"}; - const QStringList ButtonOutputs = {"cross", - "circle", - "square", - "triangle", - "l1", - "r1", - "l2", - "r2", - "l3", - - "r3", - "options", - "pad_up", - - "pad_down", - - "pad_left", - "pad_right", - "touchpad_left", - "touchpad_center", - "touchpad_right", - "unmapped"}; - - const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", - "unmapped"}; +protected: + void closeEvent(QCloseEvent* event) override { + Cleanup(); + } }; diff --git a/src/qt_gui/control_settings.ui b/src/qt_gui/control_settings.ui index 41fb005c6..2eb4c754c 100644 --- a/src/qt_gui/control_settings.ui +++ b/src/qt_gui/control_settings.ui @@ -11,8 +11,8 @@ 0 0 - 1043 - 792 + 1114 + 794 @@ -33,8 +33,8 @@ 0 0 - 1019 - 732 + 1094 + 744 @@ -42,8 +42,8 @@ 0 0 - 1021 - 731 + 1091 + 741 @@ -110,7 +110,7 @@ - 124 + 152 0 @@ -125,12 +125,9 @@ - - - false - - - QComboBox::SizeAdjustPolicy::AdjustToContents + + + unmapped @@ -161,7 +158,11 @@ 5 - + + + unmapped + + @@ -185,9 +186,9 @@ 5 - - - false + + + unmapped @@ -213,6 +214,12 @@ + + + 152 + 0 + + 124 @@ -224,21 +231,9 @@ - - - true - - - - 0 - 0 - - - - - 0 - 0 - + + + unmapped @@ -378,7 +373,7 @@ - 124 + 152 16777215 @@ -387,9 +382,9 @@ - - - true + + + unmapped @@ -420,9 +415,9 @@ 5 - - - true + + + unmapped @@ -454,9 +449,9 @@ 5 - - - true + + + unmapped @@ -484,7 +479,7 @@ - 124 + 152 0 @@ -499,15 +494,9 @@ - - - true - - - false - - - false + + + unmapped @@ -617,149 +606,190 @@ 0 - - - - - - L1 / LB - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - L2 / LT - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::Preferred - - - - 20 - 40 - - - - - - 10 - - - - Back - - - - - - - + + + + + + 0 + 0 + + + + L1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 133 + 20 + + + + + + + + + 0 + 0 + + + + R1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + + + + + L2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + R2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + - - - - - - R1 / RB - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - R2 / RT - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - @@ -806,7 +836,7 @@ - + 10 @@ -814,76 +844,144 @@ QLayout::SizeConstraint::SetDefaultConstraint - - - L3 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - + + + + + + 0 + 0 + + + + L3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 133 + 20 + + + + + + + + + 0 + 0 + + + + R3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + - - - Options / Start - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - R3 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - + + + + + Touchpad Left + + + + + + unmapped + + + + + + + + + + Touchpad Center + + + + + + unmapped + + + + + + + + + + Touchpad Right + + + + + + unmapped + + + + + + + @@ -1104,7 +1202,7 @@ - 124 + 152 0 @@ -1115,19 +1213,13 @@ - Triangle / Y + Triangle - - - true - - - - 0 - 0 - + + + unmapped @@ -1142,7 +1234,7 @@ - Square / X + Square @@ -1158,7 +1250,11 @@ 5 - + + + unmapped + + @@ -1166,7 +1262,7 @@ - Circle / B + Circle @@ -1182,7 +1278,11 @@ 5 - + + + unmapped + + @@ -1208,7 +1308,7 @@ - 124 + 152 0 @@ -1219,11 +1319,15 @@ - Cross / A + Cross - + + + unmapped + + @@ -1361,7 +1465,7 @@ - 124 + 152 1231321 @@ -1370,9 +1474,9 @@ - - - true + + + unmapped @@ -1403,9 +1507,9 @@ 5 - - - true + + + unmapped @@ -1431,7 +1535,11 @@ 5 - + + + unmapped + + @@ -1457,7 +1565,7 @@ - 124 + 152 0 @@ -1472,9 +1580,9 @@ - - - true + + + unmapped diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 3f41e1f08..ab1a7b845 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -7,16 +7,20 @@ #include #include #include +#include #include "common/path_util.h" +#include "input/input_handler.h" #include "kbm_config_dialog.h" #include "kbm_gui.h" #include "kbm_help_dialog.h" #include "ui_kbm_gui.h" HelpDialog* HelpWindow; -KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* parent) - : QDialog(parent), m_game_info(game_info_get), ui(new Ui::KBMSettings) { +KBMSettings::KBMSettings(std::shared_ptr game_info_get, bool isGameRunning, + std::string GameRunningSerial, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), GameRunning(isGameRunning), + RunningGameSerial(GameRunningSerial), ui(new Ui::KBMSettings) { ui->setupUi(this); ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); @@ -271,9 +275,17 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) { output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected || output_string == "analog_deadzone" || + output_string == "override_controller_color") { lines.push_back(line); } } @@ -324,6 +336,11 @@ QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + if (GameRunning) { + Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default") + : Input::ParseInputConfig(RunningGameSerial); + } + if (close_on_save) QWidget::close(); } @@ -390,8 +407,16 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::string output_string = line.substr(0, equal_pos - 1); std::string input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == - ControllerInputs.end()) { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (!controllerInputdetected) { if (output_string == "cross") { ui->CrossButton->setText(QString::fromStdString(input_string)); } else if (output_string == "circle") { @@ -1000,7 +1025,6 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyRelease || event->type() == QEvent::MouseButtonRelease) emit PushKBMEvent(); } - return QDialog::eventFilter(obj, event); } diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index b14b506bd..1161fc30d 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -23,7 +23,8 @@ class KBMSettings; class KBMSettings : public QDialog { Q_OBJECT public: - explicit KBMSettings(std::shared_ptr game_info_get, QWidget* parent = nullptr); + explicit KBMSettings(std::shared_ptr game_info_get, bool GameRunning, + std::string GameRunningSerial, QWidget* parent = nullptr); ~KBMSettings(); signals: @@ -47,8 +48,11 @@ private: void DisableMappingButtons(); void EnableMappingButtons(); void SetMapping(QString input); + void Cleanup(); + std::string RunningGameSerial; QSet pressedKeys; + bool GameRunning; bool EnableMapping = false; bool MappingCompleted = false; bool HelpWindowOpen = false; diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 166a31d72..f561bf392 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -473,12 +473,13 @@ void MainWindow::CreateConnects() { }); connect(ui->controllerButton, &QPushButton::clicked, this, [this]() { - auto configWindow = new ControlSettings(m_game_info, this); - configWindow->exec(); + ControlSettings* remapWindow = + new ControlSettings(m_game_info, isGameRunning, runningGameSerial, this); + remapWindow->exec(); }); connect(ui->keyboardButton, &QPushButton::clicked, this, [this]() { - auto kbmWindow = new KBMSettings(m_game_info, this); + auto kbmWindow = new KBMSettings(m_game_info, isGameRunning, runningGameSerial, this); kbmWindow->exec(); }); @@ -846,12 +847,14 @@ void MainWindow::StartGame() { if (m_game_list_frame->currentItem()) { int itemID = m_game_list_frame->currentItem()->row(); Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); + runningGameSerial = m_game_info->m_games[itemID].serial; } } else if (table_mode == 1) { if (m_game_grid_frame->cellClicked) { int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + m_game_grid_frame->crtColumn; Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); + runningGameSerial = m_game_info->m_games[itemID].serial; } } else { if (m_elf_viewer->currentItem()) { diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index eec1a65de..5b880c15e 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -75,11 +75,13 @@ private: void PlayBackgroundMusic(); QIcon RecolorIcon(const QIcon& icon, bool isWhite); void StartEmulator(std::filesystem::path); + bool isIconBlack = false; bool isTableList = true; bool isGameRunning = false; bool isWhite = false; bool is_paused = false; + std::string runningGameSerial = ""; QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; diff --git a/src/qt_gui/sdl_event_wrapper.cpp b/src/qt_gui/sdl_event_wrapper.cpp new file mode 100644 index 000000000..608acbbc5 --- /dev/null +++ b/src/qt_gui/sdl_event_wrapper.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "sdl_event_wrapper.h" + +using namespace SdlEventWrapper; + +Wrapper* Wrapper::WrapperInstance = nullptr; +bool Wrapper::wrapperActive = false; + +Wrapper::Wrapper(QObject* parent) : QObject(parent) {} + +Wrapper* Wrapper::GetInstance() { + if (WrapperInstance == nullptr) { + WrapperInstance = new Wrapper(); + } + return WrapperInstance; +} + +bool Wrapper::ProcessEvent(SDL_Event* event) { + switch (event->type) { + case SDL_EVENT_WINDOW_RESTORED: + return false; + case SDL_EVENT_WINDOW_EXPOSED: + return false; + case SDL_EVENT_GAMEPAD_ADDED: + return false; + case SDL_EVENT_GAMEPAD_REMOVED: + return false; + case SDL_EVENT_QUIT: + emit SDLEvent(SDL_EVENT_QUIT, 0, 0); + return true; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_DOWN, event->gbutton.button, 0); + return true; + case SDL_EVENT_GAMEPAD_BUTTON_UP: + emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_UP, event->gbutton.button, 0); + return true; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + emit SDLEvent(SDL_EVENT_GAMEPAD_AXIS_MOTION, event->gaxis.axis, event->gaxis.value); + return true; + // block all other SDL events while wrapper is active + default: + return true; + } +} +Wrapper::~Wrapper() {} diff --git a/src/qt_gui/sdl_event_wrapper.h b/src/qt_gui/sdl_event_wrapper.h new file mode 100644 index 000000000..54d8c9cd1 --- /dev/null +++ b/src/qt_gui/sdl_event_wrapper.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include + +namespace SdlEventWrapper { + +class Wrapper : public QObject { + Q_OBJECT + +public: + explicit Wrapper(QObject* parent = nullptr); + ~Wrapper(); + bool ProcessEvent(SDL_Event* event); + static Wrapper* GetInstance(); + static bool wrapperActive; + static Wrapper* WrapperInstance; + +signals: + void SDLEvent(int Type, int Input, int Value); +}; + +} // namespace SdlEventWrapper diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 735f14639..69819a00f 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -20,6 +20,10 @@ #include "sdl_window.h" #include "video_core/renderdoc.h" +#ifdef ENABLE_QT_GUI +#include "qt_gui/sdl_event_wrapper.h" +#endif + #ifdef __APPLE__ #include "SDL3/SDL_metal.h" #endif @@ -340,6 +344,13 @@ void WindowSDL::WaitEvent() { return; } +#ifdef ENABLE_QT_GUI + if (SdlEventWrapper::Wrapper::wrapperActive) { + if (SdlEventWrapper::Wrapper::GetInstance()->ProcessEvent(&event)) + return; + } +#endif + if (ImGui::Core::ProcessEvent(&event)) { return; }