diff --git a/src/common/config.cpp b/src/common/config.cpp index c3f25b4b5..6e3377199 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -57,6 +57,7 @@ static int specialPadClass = 1; static bool isMotionControlsEnabled = true; static bool useUnifiedInputConfig = true; static std::string micDevice = "Default Device"; +static std::string defaultControllerID = ""; // These two entries aren't stored in the config static bool overrideControllerColor = false; @@ -612,6 +613,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; @@ -656,6 +665,7 @@ void load(const std::filesystem::path& path) { isConnectedToNetwork = toml::find_or(general, "isConnectedToNetwork", isConnectedToNetwork); chooseHomeTab = toml::find_or(general, "chooseHomeTab", chooseHomeTab); + defaultControllerID = toml::find_or(general, "defaultControllerID", ""); } if (data.contains("Input")) { @@ -837,6 +847,7 @@ void save(const std::filesystem::path& path) { data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["General"]["isConnectedToNetwork"] = isConnectedToNetwork; + 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 d3e693a2b..c36caeead 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -107,6 +107,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..84b5dea91 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -6,7 +6,9 @@ #include #include "common/config.h" #include "core/debug_state.h" +#include "core/memory.h" #include "imgui_impl_sdl3.h" +#include "input/controller.h" // SDL #include @@ -730,18 +732,29 @@ 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; - } + auto memory = Core::Memory::Instance(); + auto controller = Common::Singleton::Instance(); + auto engine = controller->GetEngine(); + SDL_Gamepad* SDLGamepad = engine->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/input/controller.cpp b/src/input/controller.cpp index 42cabb837..6657f4036 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -8,6 +8,8 @@ #include "core/libraries/pad/pad.h" #include "input/controller.h" +static std::string SelectedGamepad = ""; + namespace Input { using Libraries::Pad::OrbisPadButtonDataOffset; @@ -324,3 +326,48 @@ u32 GameController::Poll() { } } // namespace Input + +namespace GamepadSelect { + +int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) { + char GUIDbuf[33]; + if (Config::getDefaultControllerID() != "") { + for (int i = 0; i < gamepadCount; i++) { + SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33); + std::string currentGUID = std::string(GUIDbuf); + if (currentGUID == Config::getDefaultControllerID()) { + return i; + } + } + } + return -1; +} + +int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) { + char GUIDbuf[33]; + for (int i = 0; i < gamepadCount; i++) { + SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33); + std::string currentGUID = std::string(GUIDbuf); + if (currentGUID == GUID) { + return i; + } + } + return -1; +} + +std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index) { + char GUIDbuf[33]; + SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[index]), GUIDbuf, 33); + std::string GUID = std::string(GUIDbuf); + return GUID; +} + +std::string GetSelectedGamepad() { + return SelectedGamepad; +} + +void SetSelectedGamepad(std::string GUID) { + SelectedGamepad = GUID; +} + +} // namespace GamepadSelect diff --git a/src/input/controller.h b/src/input/controller.h index f427a55ec..dfde521be 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/types.h" #include "core/libraries/pad/pad.h" @@ -55,6 +56,7 @@ public: virtual State ReadState() = 0; virtual float GetAccelPollRate() const = 0; virtual float GetGyroPollRate() const = 0; + SDL_Gamepad* m_gamepad; }; inline int GetAxis(int min, int max, int value) { @@ -127,3 +129,12 @@ private: }; } // namespace Input + +namespace GamepadSelect { + +int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID); +std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index); +std::string GetSelectedGamepad(); +void SetSelectedGamepad(std::string GUID); + +} // namespace GamepadSelect diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 319daecdd..6aa79ea1a 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -9,6 +9,7 @@ #include "common/path_util.h" #include "control_settings.h" #include "input/input_handler.h" +#include "sdl_window.h" #include "ui_control_settings.h" ControlSettings::ControlSettings(std::shared_ptr game_info_get, bool isGameRunning, @@ -21,7 +22,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 +29,7 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, b AddBoxItems(); SetUIValuestoMappings(); UpdateLightbarColor(); + CheckGamePad(); installEventFilter(this); ButtonsList = {ui->CrossButton, @@ -118,6 +119,28 @@ 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]() { + ui->DefaultGamepadName->setText(ui->ActiveGamepadBox->currentText()); + std::string GUID = + GamepadSelect::GetGUIDString(gamepads, ui->ActiveGamepadBox->currentIndex()); + ui->DefaultGamepadLabel->setText(tr("ID: ") + QString::fromStdString(GUID).right(16)); + Config::setDefaultControllerID(GUID); + 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]() { + 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("Default controller setting removed")); + }); RemapWrapper = SdlEventWrapper::Wrapper::GetInstance(); SdlEventWrapper::Wrapper::wrapperActive = true; @@ -639,39 +662,91 @@ void ControlSettings::UpdateLightbarColor() { ui->LightbarColorFrame->setStyleSheet(colorstring); } -void ControlSettings::CheckGamePad() { - if (GameRunning) - return; +void ControlSettings::ActiveControllerChanged(int value) { + GamepadSelect::SetSelectedGamepad(GamepadSelect::GetGUIDString(gamepads, value)); + QString GUID = QString::fromStdString(GamepadSelect::GetSelectedGamepad()).right(16); + ui->ActiveGamepadLabel->setText("ID: " + GUID); 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]); + gamepad = SDL_OpenGamepad(gamepads[value]); if (!gamepad) { - LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); - SDL_free(gamepads); - return; + LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); + } +} + +void ControlSettings::CheckGamePad() { + if (gamepad) { + SDL_CloseGamepad(gamepad); + gamepad = nullptr; } SDL_free(gamepads); + gamepads = SDL_GetGamepads(&gamepad_count); + + if (!gamepads) { + LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); + } + + QString defaultGUID = ""; + int defaultIndex = + GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID()); + int activeIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, + GamepadSelect::GetSelectedGamepad()); + + if (!GameRunning) { + if (activeIndex != -1) { + gamepad = SDL_OpenGamepad(gamepads[activeIndex]); + } else if (defaultIndex != -1) { + gamepad = SDL_OpenGamepad(gamepads[defaultIndex]); + } 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: {}", SDL_GetError()); + } + } + + if (!gamepads || gamepad_count == 0) { + ui->ActiveGamepadBox->addItem("No gamepads detected"); + ui->ActiveGamepadBox->setCurrentIndex(0); + 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)); + } + } + + if (defaultIndex != -1) { + defaultGUID = + QString::fromStdString(GamepadSelect::GetGUIDString(gamepads, defaultIndex)).right(16); + ui->DefaultGamepadName->setText(SDL_GetGamepadNameForID(gamepads[defaultIndex])); + ui->DefaultGamepadLabel->setText(tr("ID: ") + defaultGUID); + } else { + ui->DefaultGamepadName->setText("Default controller not connected"); + ui->DefaultGamepadLabel->setText(tr("n/a")); + } + + if (activeIndex != -1) { + QString GUID = + QString::fromStdString(GamepadSelect::GetGUIDString(gamepads, activeIndex)).right(16); + ui->ActiveGamepadLabel->setText(tr("ID: ") + GUID); + ui->ActiveGamepadBox->setCurrentIndex(activeIndex); + } else if (defaultIndex != -1) { + ui->ActiveGamepadLabel->setText(defaultGUID); + ui->ActiveGamepadBox->setCurrentIndex(defaultIndex); + } else { + QString GUID = QString::fromStdString(GamepadSelect::GetGUIDString(gamepads, 0)).right(16); + ui->ActiveGamepadLabel->setText("ID: " + GUID); + ui->ActiveGamepadBox->setCurrentIndex(0); + } } void ControlSettings::DisableMappingButtons() { @@ -914,6 +989,7 @@ void ControlSettings::pollSDLEvents() { } if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + ui->ActiveGamepadBox->clear(); CheckGamePad(); } @@ -923,8 +999,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 +1017,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..17f12f803 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; @@ -59,9 +60,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; 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..69aa5f4c3 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -114,12 +114,31 @@ void SDLInputEngine::Init() { return; } - LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); - m_gamepad = SDL_OpenGamepad(gamepads[0]); + int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, + GamepadSelect::GetSelectedGamepad()); + int defaultIndex = + GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID()); + + // If user selects a gamepad in the GUI, use that, otherwise try the default if (!m_gamepad) { - LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); - SDL_free(gamepads); - return; + if (selectedIndex != -1) { + m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]); + LOG_INFO(Input, "Opening gamepad selected in GUI."); + } else if (defaultIndex != -1) { + m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]); + LOG_INFO(Input, "Opening default gamepad."); + } else { + m_gamepad = SDL_OpenGamepad(gamepads[0]); + LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); + } + } + + if (!m_gamepad) { + 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 +445,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..83713af57 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; @@ -27,8 +28,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; };