// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // 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 "sdl_window.h" #include "ui_control_settings.h" std::string ControllerSelect::ActiveGamepad = ""; 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); if (!GameRunning) { SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_InitSubSystem(SDL_INIT_EVENTS); } else { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); } AddBoxItems(); SetUIValuestoMappings(); UpdateLightbarColor(); CheckGamePad(); ResetActiveControllerBox(); 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)) { SaveControllerConfig(true); } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { SetDefault(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { SaveControllerConfig(false); } }); ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Apply")); 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] { GetGameTitle(); SetUIValuestoMappings(); }); connect(ui->LeftDeadzoneSlider, &QSlider::valueChanged, this, [this](int value) { ui->LeftDeadzoneValue->setText(QString::number(value)); }); connect(ui->RightDeadzoneSlider, &QSlider::valueChanged, this, [this](int value) { ui->RightDeadzoneValue->setText(QString::number(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; ui->RLabel->setText(RValue); UpdateLightbarColor(); }); connect(ui->GSlider, &QSlider::valueChanged, this, [this](int value) { QString GreenValue = QString("%1").arg(value, 3, 10, QChar('0')); QString GValue = tr("G:") + " " + GreenValue; ui->GLabel->setText(GValue); UpdateLightbarColor(); }); connect(ui->BSlider, &QSlider::valueChanged, this, [this](int value) { QString BlueValue = QString("%1").arg(value, 3, 10, QChar('0')); QString BValue = tr("B:") + " " + BlueValue; ui->BLabel->setText(BValue); UpdateLightbarColor(); }); connect(this, &ControlSettings::PushGamepadEvent, this, [this]() { CheckMapping(MappingButton); }); connect(this, &ControlSettings::AxisChanged, this, [this]() { ConnectAxisInputs(MappingButton); }); connect(ui->ActiveGamepadBox, &QComboBox::currentIndexChanged, this, &ControlSettings::ActiveControllerChanged); connect(ui->DefaultGamepadButton, &QPushButton::clicked, this, [this]() { char pszGUID[33]; SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[ui->ActiveGamepadBox->currentIndex()]), pszGUID, 33); ui->DefaultGamepadName->setText(ui->ActiveGamepadBox->currentText()); ui->DefaultGamepadLabel->setText(tr("ID: ") + QString::fromStdString(std::string(pszGUID)).right(16)); Config::setDefaultControllerID(std::string(pszGUID)); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); QMessageBox::information(this, tr("Default Controller Selected"), tr("Active controller set as default")); }); connect(ui->RemoveDefaultGamepadButton, &QPushButton::clicked, this, [this]() { char pszGUID[33]; SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[ui->ActiveGamepadBox->currentIndex()]), pszGUID, 33); ui->DefaultGamepadName->setText(tr("No default selected")); ui->DefaultGamepadLabel->setText(tr("n/a")); Config::setDefaultControllerID(""); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); QMessageBox::information(this, tr("Default Controller Removed"), tr("No default controller currently selected")); }); 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::ActiveControllerChanged(int value) { char pszGUID[33]; SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[value]), pszGUID, 33); QString GUID = QString::fromStdString(std::string(pszGUID)).right(16); ui->ActiveGamepadLabel->setText("ID: " + GUID); ControllerSelect::ActiveGamepad = std::string(pszGUID); if (!GameRunning) { if (gamepad) { SDL_CloseGamepad(gamepad); gamepad = nullptr; } gamepads = SDL_GetGamepads(&gamepad_count); if (!gamepads) { LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); return; } for (int i = 0; i < gamepad_count; i++) { char pszGUID[33]; SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[i]), pszGUID, 33); std::string currentGUID = std::string(pszGUID); if (currentGUID == ControllerSelect::ActiveGamepad) { gamepad = SDL_OpenGamepad(gamepads[i]); LOG_WARNING(Input, "Opened gamepad: {}", i); if (!gamepad) { LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); } break; } } if (!gamepad) { LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); return; } } } void ControlSettings::ResetActiveControllerBox() { SDL_free(gamepads); gamepads = SDL_GetGamepads(&gamepad_count); if (!gamepads) { LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); return; } if (gamepad_count == 0) { ui->ActiveGamepadBox->addItem("No gamepads detected"); ui->ActiveGamepadBox->setCurrentIndex(0); LOG_INFO(Input, "No gamepad found!"); return; } else { for (int i = 0; i < gamepad_count; i++) { QString name = SDL_GetGamepadNameForID(gamepads[i]); ui->ActiveGamepadBox->addItem(QString("%1: %2").arg(QString::number(i + 1), name)); } } char pszGUID[33]; int defaultIndex; QString defaultID = ""; if (Config::getDefaultControllerID() != "") { for (int i = 0; i < gamepad_count; i++) { SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[i]), pszGUID, 33); std::string currentGUID = std::string(pszGUID); if (currentGUID == Config::getDefaultControllerID()) { defaultIndex = i; defaultID = QString::fromStdString(std::string(pszGUID)).right(16); ui->DefaultGamepadName->setText(SDL_GetGamepadNameForID(gamepads[i])); ui->DefaultGamepadLabel->setText(tr("ID: ") + defaultID); break; } } if (defaultID == "") ui->DefaultGamepadName->setText("Default controller not connected"); } if (ControllerSelect::ActiveGamepad != "") { for (int i = 0; i < gamepad_count; i++) { SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[i]), pszGUID, 33); std::string currentGUID = std::string(pszGUID); if (currentGUID == ControllerSelect::ActiveGamepad) { SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[i]), pszGUID, 33); QString GUID = QString::fromStdString(std::string(pszGUID)).right(16); ui->ActiveGamepadLabel->setText(tr("ID: ") + GUID); ui->ActiveGamepadBox->setCurrentIndex(i); break; } } } else if (Config::getDefaultControllerID() != "") { ui->ActiveGamepadLabel->setText(defaultID); ui->ActiveGamepadBox->setCurrentIndex(defaultIndex); } else { SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[0]), pszGUID, 33); QString GUID = QString::fromStdString(std::string(pszGUID)).right(16); ui->ActiveGamepadLabel->setText("ID: " + GUID); ui->ActiveGamepadBox->setCurrentIndex(0); } } void ControlSettings::SaveControllerConfig(bool CloseOnSave) { 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->text() == "axis_left_x") { count_axis_left_x = count_axis_left_x + 1; } else if (i->text() == "axis_left_y") { count_axis_left_y = count_axis_left_y + 1; } else if (i->text() == "axis_right_x") { count_axis_right_x = count_axis_right_x + 1; } 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::information(this, tr("Unable to Save"), tr("Cannot bind axis values more than once")); return; } std::string config_id; config_id = (ui->ProfileComboBox->currentText() == "Common Config") ? "default" : ui->ProfileComboBox->currentText().toStdString(); const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); int lineCount = 0; std::string line; std::vector lines, inputs; std::string output_string = "", input_string = ""; std::fstream file(config_file); while (std::getline(file, line)) { lineCount++; std::size_t comment_pos = line.find('#'); if (comment_pos != std::string::npos) { if (!line.contains("Range of deadzones")) lines.push_back(line); continue; } std::size_t equal_pos = line.find('='); if (equal_pos == std::string::npos) { lines.push_back(line); continue; } output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); 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; } lines.push_back(line); } file.close(); // 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); } }; 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(""); 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(""); 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(""); 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(""); output_string = "axis_left_x"; input_string = ui->LStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); output_string = "axis_left_y"; input_string = ui->LStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); output_string = "axis_right_x"; input_string = ui->RStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); output_string = "axis_right_y"; input_string = ui->RStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); lines.push_back(""); lines.push_back("# Range of deadzones: 1 (almost none) to 127 (max)"); std::string deadzonevalue = std::to_string(ui->LeftDeadzoneSlider->value()); lines.push_back("analog_deadzone = leftjoystick, " + deadzonevalue + ", 127"); deadzonevalue = std::to_string(ui->RightDeadzoneSlider->value()); lines.push_back("analog_deadzone = rightjoystick, " + deadzonevalue + ", 127"); lines.push_back(""); std::string OverrideLB = ui->LightbarCheckBox->isChecked() ? "true" : "false"; std::string LightBarR = std::to_string(ui->RSlider->value()); std::string LightBarG = std::to_string(ui->GSlider->value()); std::string LightBarB = std::to_string(ui->BSlider->value()); 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) { LastLineEmpty = CurrentLineEmpty ? true : false; CurrentLineEmpty = line.empty() ? true : false; if (!CurrentLineEmpty || !LastLineEmpty) save.push_back(line); } std::ofstream output_file(config_file); for (auto const& line : save) { output_file << line << '\n'; } output_file.close(); Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); Config::SetOverrideControllerColor(ui->LightbarCheckBox->isChecked()); Config::SetControllerCustomColor(ui->RSlider->value(), ui->GSlider->value(), 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->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->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); ui->RSlider->setValue(0); ui->GSlider->setValue(0); ui->BSlider->setValue(255); ui->LightbarCheckBox->setChecked(false); ui->PerGameCheckBox->setChecked(false); } void ControlSettings::AddBoxItems() { 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)); } ui->ProfileComboBox->setCurrentText("Common Config"); ui->TitleLabel->setText("Common Config"); } void ControlSettings::SetUIValuestoMappings() { std::string config_id; config_id = (ui->ProfileComboBox->currentText() == "Common Config") ? "default" : ui->ProfileComboBox->currentText().toStdString(); const auto config_file = Config::GetFoolproofKbmConfigFile(config_id); std::ifstream file(config_file); 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, OptionsExists = false, TouchpadLeftExists = false, TouchpadCenterExists = false, TouchpadRightExists = false, LStickXExists = false, LStickYExists = false, RStickXExists = false, RStickYExists = false; int lineCount = 0; std::string line = ""; while (std::getline(file, line)) { lineCount++; line.erase(std::remove(line.begin(), line.end(), ' '), line.end()); if (line.empty()) continue; std::size_t comment_pos = line.find('#'); if (comment_pos != std::string::npos) line = line.substr(0, comment_pos); std::size_t equal_pos = line.find('='); if (equal_pos == std::string::npos) continue; std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); 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 (output_string == "circle") { ui->CircleButton->setText(QString::fromStdString(input_string)); CircleExists = true; } else if (output_string == "square") { ui->SquareButton->setText(QString::fromStdString(input_string)); SquareExists = true; } else if (output_string == "triangle") { ui->TriangleButton->setText(QString::fromStdString(input_string)); TriangleExists = true; } else if (output_string == "l1") { ui->L1Button->setText(QString::fromStdString(input_string)); L1Exists = true; } else if (output_string == "l2") { ui->L2Button->setText(QString::fromStdString(input_string)); L2Exists = true; } else if (output_string == "r1") { ui->R1Button->setText(QString::fromStdString(input_string)); R1Exists = true; } else if (output_string == "r2") { ui->R2Button->setText(QString::fromStdString(input_string)); R2Exists = true; } else if (output_string == "l3") { ui->L3Button->setText(QString::fromStdString(input_string)); L3Exists = true; } else if (output_string == "r3") { ui->R3Button->setText(QString::fromStdString(input_string)); R3Exists = true; } else if (output_string == "pad_up") { ui->DpadUpButton->setText(QString::fromStdString(input_string)); DPadUpExists = true; } else if (output_string == "pad_down") { ui->DpadDownButton->setText(QString::fromStdString(input_string)); DPadDownExists = true; } else if (output_string == "pad_left") { ui->DpadLeftButton->setText(QString::fromStdString(input_string)); DPadLeftExists = true; } else if (output_string == "pad_right") { ui->DpadRightButton->setText(QString::fromStdString(input_string)); DPadRightExists = true; } 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 (output_string == "axis_left_y") { ui->LStickUpButton->setText(QString::fromStdString(input_string)); ui->LStickDownButton->setText(QString::fromStdString(input_string)); LStickYExists = true; } 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 (output_string == "axis_right_y") { ui->RStickUpButton->setText(QString::fromStdString(input_string)); ui->RStickDownButton->setText(QString::fromStdString(input_string)); RStickYExists = true; } } 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); } 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); } } } } file.close(); // If an entry does not exist in the config file, we assume the user wants it unmapped if (!CrossExists) ui->CrossButton->setText("unmapped"); if (!CircleExists) ui->CircleButton->setText("unmapped"); if (!SquareExists) ui->SquareButton->setText("unmapped"); if (!TriangleExists) ui->TriangleButton->setText("unmapped"); if (!L1Exists) ui->L1Button->setText("unmapped"); if (!L2Exists) ui->L2Button->setText("unmapped"); if (!L3Exists) ui->L3Button->setText("unmapped"); if (!R1Exists) ui->R1Button->setText("unmapped"); if (!R2Exists) ui->R2Button->setText("unmapped"); if (!R3Exists) ui->R3Button->setText("unmapped"); if (!DPadUpExists) ui->DpadUpButton->setText("unmapped"); if (!DPadDownExists) ui->DpadDownButton->setText("unmapped"); if (!DPadLeftExists) ui->DpadLeftButton->setText("unmapped"); if (!DPadRightExists) 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->LStickRightButton->setText("unmapped"); ui->LStickLeftButton->setText("unmapped"); } if (!LStickYExists) { ui->LStickUpButton->setText("unmapped"); ui->LStickDownButton->setText("unmapped"); } if (!RStickXExists) { ui->RStickRightButton->setText("unmapped"); ui->RStickLeftButton->setText("unmapped"); } if (!RStickYExists) { ui->RStickUpButton->setText("unmapped"); ui->RStickDownButton->setText("unmapped"); } } void ControlSettings::GetGameTitle() { if (ui->ProfileComboBox->currentText() == "Common Config") { ui->TitleLabel->setText("Common Config"); } else { for (int i = 0; i < m_game_info->m_games.size(); i++) { if (m_game_info->m_games[i].serial == ui->ProfileComboBox->currentText().toStdString()) { ui->TitleLabel->setText(QString::fromStdString(m_game_info->m_games[i].name)); } } } } void ControlSettings::UpdateLightbarColor() { ui->LightbarColorFrame->setStyleSheet(""); QString RValue = QString::number(ui->RSlider->value()); QString GValue = QString::number(ui->GSlider->value()); QString BValue = QString::number(ui->BSlider->value()); QString colorstring = "background-color: rgb(" + RValue + "," + GValue + "," + BValue + ")"; ui->LightbarColorFrame->setStyleSheet(colorstring); } void ControlSettings::CheckGamePad() { if ((gamepad)) { SDL_CloseGamepad(gamepad); gamepad = nullptr; } gamepads = SDL_GetGamepads(&gamepad_count); if (!gamepads) { LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); return; } if (!GameRunning) { if (ControllerSelect::ActiveGamepad != "") { for (int i = 0; i < gamepad_count; i++) { char pszGUID[33]; SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepads[i]), pszGUID, 33); std::string currentGUID = std::string(pszGUID); if (currentGUID == ControllerSelect::ActiveGamepad) { gamepad = SDL_OpenGamepad(gamepads[i]); LOG_WARNING(Input, "Opened gamepad: {}", i); if (!gamepad) { LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); } break; } } } else { 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()); return; } } } 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) { ui->ActiveGamepadBox->clear(); ResetActiveControllerBox(); CheckGamePad(); } SdlEventWrapper::Wrapper::GetInstance()->Wrapper::ProcessEvent(&event); } } void ControlSettings::Cleanup() { SdlEventWrapper::Wrapper::wrapperActive = false; if (gamepad) { SDL_CloseGamepad(gamepad); gamepad = nullptr; } SDL_free(gamepads); 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"); SDL_Event checkGamepad{}; checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER; SDL_PushEvent(&checkGamepad); } } ControlSettings::~ControlSettings() {}