diff --git a/src/common/config.cpp b/src/common/config.cpp index 4a764a4c6..d717fb585 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -75,6 +75,7 @@ static bool compatibilityData = false; static bool checkCompatibilityOnStartup = false; static std::string trophyKey; static bool isPSNSignedIn = false; +static std::string defaultControllerID = ""; // Gui static bool load_game_size = true; @@ -546,6 +547,14 @@ void setPSNSignedIn(bool sign) { isPSNSignedIn = sign; } +std::string getDefaultControllerID() { + return defaultControllerID; +} + +void setDefaultControllerID(std::string id) { + defaultControllerID = id; +} + void load(const std::filesystem::path& path) { // If the configuration file does not exist, create it and return std::error_code error; @@ -584,6 +593,7 @@ void load(const std::filesystem::path& path) { checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", false); chooseHomeTab = toml::find_or(general, "chooseHomeTab", "Release"); + defaultControllerID = toml::find_or(general, "defaultControllerID", ""); } if (data.contains("Input")) { @@ -745,6 +755,7 @@ void save(const std::filesystem::path& path) { data["General"]["sideTrophy"] = isSideTrophy; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; + data["General"]["defaultControllerID"] = defaultControllerID; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["useSpecialPad"] = useSpecialPad; diff --git a/src/common/config.h b/src/common/config.h index 931fa68e2..8afe39e55 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -97,6 +97,8 @@ bool isDevKitConsole(); // no set bool vkValidationGpuEnabled(); // no set bool getIsMotionControlsEnabled(); void setIsMotionControlsEnabled(bool use); +std::string getDefaultControllerID(); +void setDefaultControllerID(std::string id); // TODO bool GetLoadGameSizeEnabled(); diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index ccd31d03a..99f3b662e 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -7,6 +7,7 @@ #include "common/config.h" #include "core/debug_state.h" #include "imgui_impl_sdl3.h" +#include "sdl_window.h" // SDL #include @@ -730,18 +731,25 @@ static void UpdateGamepads() { ImGuiIO& io = ImGui::GetIO(); SdlData* bd = GetBackendData(); - // Update list of gamepads to use - if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { - CloseGamepads(); - int sdl_gamepads_count = 0; - const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count); - for (int n = 0; n < sdl_gamepads_count; n++) - if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) { - bd->gamepads.push_back(gamepad); - if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst) - break; - } + SDL_Gamepad* SDLGamepad = Input::m_gamepad; + if (SDLGamepad) { + bd->gamepads.push_back(SDLGamepad); bd->want_update_gamepads_list = false; + } else { + // Update list of gamepads to use + if (bd->want_update_gamepads_list && + bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { + CloseGamepads(); + int sdl_gamepads_count = 0; + const SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count); + for (int n = 0; n < sdl_gamepads_count; n++) + if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n])) { + bd->gamepads.push_back(gamepad); + if (bd->gamepad_mode == ImGui_ImplSDL3_GamepadMode_AutoFirst) + break; + } + bd->want_update_gamepads_list = false; + } } // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 319daecdd..44c3d2e3d 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -9,8 +9,11 @@ #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), @@ -21,7 +24,6 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, b if (!GameRunning) { SDL_InitSubSystem(SDL_INIT_GAMEPAD); SDL_InitSubSystem(SDL_INIT_EVENTS); - CheckGamePad(); } else { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); } @@ -29,6 +31,8 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, b AddBoxItems(); SetUIValuestoMappings(); UpdateLightbarColor(); + CheckGamePad(); + ResetActiveControllerBox(); installEventFilter(this); ButtonsList = {ui->CrossButton, @@ -118,6 +122,33 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, b [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; @@ -129,6 +160,110 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, b } } +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 @@ -640,38 +775,42 @@ void ControlSettings::UpdateLightbarColor() { } void ControlSettings::CheckGamePad() { - if (GameRunning) - return; - - if (gamepad) { + if ((gamepad)) { SDL_CloseGamepad(gamepad); gamepad = nullptr; } - int gamepad_count; - SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); - + 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; + 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; + } } - - 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() { @@ -914,6 +1053,8 @@ void ControlSettings::pollSDLEvents() { } if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + ui->ActiveGamepadBox->clear(); + ResetActiveControllerBox(); CheckGamePad(); } @@ -923,8 +1064,12 @@ void ControlSettings::pollSDLEvents() { void ControlSettings::Cleanup() { SdlEventWrapper::Wrapper::wrapperActive = false; - if (gamepad) + if (gamepad) { SDL_CloseGamepad(gamepad); + gamepad = nullptr; + } + + SDL_free(gamepads); if (!GameRunning) { SDL_Event quitLoop{}; @@ -937,6 +1082,9 @@ void ControlSettings::Cleanup() { SDL_Quit(); } else { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0"); + SDL_Event checkGamepad{}; + checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER; + SDL_PushEvent(&checkGamepad); } } diff --git a/src/qt_gui/control_settings.h b/src/qt_gui/control_settings.h index 76d16b84e..235485ed5 100644 --- a/src/qt_gui/control_settings.h +++ b/src/qt_gui/control_settings.h @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later - +#pragma once #include #include #include @@ -29,6 +29,7 @@ private Q_SLOTS: void CheckMapping(QPushButton*& button); void StartTimer(QPushButton*& button, bool isButton); void ConnectAxisInputs(QPushButton*& button); + void ActiveControllerChanged(int value); private: std::unique_ptr ui; @@ -45,6 +46,7 @@ private: void DisableMappingButtons(); void EnableMappingButtons(); void Cleanup(); + void ResetActiveControllerBox(); QList ButtonsList; QList AxisList; @@ -59,9 +61,11 @@ private: bool MappingCompleted = false; QString mapping; int MappingTimer; + int gamepad_count; QTimer* timer; QPushButton* MappingButton; SDL_Gamepad* gamepad = nullptr; + SDL_JoystickID* gamepads; SdlEventWrapper::Wrapper* RemapWrapper; QFuture Polling; @@ -81,3 +85,7 @@ protected: Cleanup(); } }; + +namespace ControllerSelect { +extern std::string ActiveGamepad; +} // namespace ControllerSelect diff --git a/src/qt_gui/control_settings.ui b/src/qt_gui/control_settings.ui index 2eb4c754c..efab94e2c 100644 --- a/src/qt_gui/control_settings.ui +++ b/src/qt_gui/control_settings.ui @@ -11,8 +11,8 @@ 0 0 - 1114 - 794 + 1124 + 847 @@ -33,8 +33,8 @@ 0 0 - 1094 - 744 + 1104 + 797 @@ -42,8 +42,8 @@ 0 0 - 1091 - 741 + 1101 + 791 @@ -246,14 +246,82 @@ + + + + L1 and L2 + + + + + + L2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + + 0 + 0 + + + + L1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + Qt::Orientation::Vertical - - QSizePolicy::Policy::Maximum - 20 @@ -512,7 +580,7 @@ - + 0 @@ -598,199 +666,117 @@ - - - - - - - 0 - - - - - + + + + + 9 + true + + + + Active Gamepad + + - - - - - - 0 - 0 - - - - L1 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - unmapped - - - - - - - - - - Qt::Orientation::Horizontal - - - QSizePolicy::Policy::Fixed - - - - 133 - 20 - - - - - - - - - 0 - 0 - - - - R1 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - unmapped - - - - - - - + + + + 9 + false + + + - - - - - L2 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - unmapped - - - - - - - - - - Options - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - unmapped - - - - - - - - - - R2 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - unmapped - - - - - - - + + + + 9 + false + + + + Gamepad ID + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + - - - - + + + + + + + 9 + true + + + + Default Gamepad + + + + + + No default selected + + + + + + + + 9 + false + + + + n/a + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + + + + + + 9 + true + + + + Set Active Gamepad as Default + + + + + + + + 9 + true + + + + Remove Default Gamepad + + + + + + + @@ -880,20 +866,32 @@ - - - Qt::Orientation::Horizontal + + + Options - - QSizePolicy::Policy::Fixed - - - - 133 - 20 - - - + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + @@ -1338,13 +1336,84 @@ + + + + R1 and R2 + + + + + + R2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + + 0 + 0 + + + + R1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + Qt::Orientation::Vertical - QSizePolicy::Policy::Maximum + QSizePolicy::Policy::Expanding diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 69819a00f..34f570626 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -21,6 +21,7 @@ #include "video_core/renderdoc.h" #ifdef ENABLE_QT_GUI +#include "qt_gui/control_settings.h" #include "qt_gui/sdl_event_wrapper.h" #endif @@ -30,6 +31,8 @@ namespace Input { +SDL_Gamepad* m_gamepad = nullptr; + using Libraries::Pad::OrbisPadButtonDataOffset; static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { @@ -100,6 +103,7 @@ void SDLInputEngine::Init() { if (m_gamepad) { SDL_CloseGamepad(m_gamepad); m_gamepad = nullptr; + LOG_WARNING(Input, "closed gamepad"); } int gamepad_count; @@ -114,12 +118,51 @@ void SDLInputEngine::Init() { return; } - LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); - m_gamepad = SDL_OpenGamepad(gamepads[0]); + std::string activeGamepad = ""; + std::string defaultGamepad = Config::getDefaultControllerID(); +#ifdef ENABLE_QT_GUI + activeGamepad = ControllerSelect::ActiveGamepad; +#endif + + // If user selects an active gamepad, use that, otherwise, try the default if (!m_gamepad) { - LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); - SDL_free(gamepads); - return; + if (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 == activeGamepad) { + m_gamepad = SDL_OpenGamepad(gamepads[i]); + if (!m_gamepad) { + LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); + } + break; + } + } + } else if (Config::getDefaultControllerID() != "") { + 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 == Config::getDefaultControllerID()) { + m_gamepad = SDL_OpenGamepad(gamepads[i]); + if (!m_gamepad) { + LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); + } + break; + } + } + } + } + + if (!m_gamepad) { + LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); + m_gamepad = SDL_OpenGamepad(gamepads[0]); + if (!m_gamepad) { + LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); + SDL_free(gamepads); + return; + } } SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad); @@ -426,6 +469,9 @@ void WindowSDL::WaitEvent() { DebugState.PauseGuestThreads(); } break; + case SDL_EVENT_CHANGE_CONTROLLER: + controller->GetEngine()->Init(); + break; default: break; } diff --git a/src/sdl_window.h b/src/sdl_window.h index 48a9be58c..3ea96c734 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -9,6 +9,7 @@ #include "string" #define SDL_EVENT_TOGGLE_FULLSCREEN (SDL_EVENT_USER + 1) #define SDL_EVENT_TOGGLE_PAUSE (SDL_EVENT_USER + 2) +#define SDL_EVENT_CHANGE_CONTROLLER (SDL_EVENT_USER + 3) struct SDL_Window; struct SDL_Gamepad; @@ -16,6 +17,8 @@ union SDL_Event; namespace Input { +extern SDL_Gamepad* m_gamepad; + class SDLInputEngine : public Engine { public: ~SDLInputEngine() override; @@ -27,8 +30,6 @@ public: State ReadState() override; private: - SDL_Gamepad* m_gamepad = nullptr; - float m_gyro_poll_rate = 0.0f; float m_accel_poll_rate = 0.0f; };