diff --git a/src/common/config.cpp b/src/common/config.cpp index 6997f7276..4e2298d2a 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -136,10 +136,14 @@ static ConfigEntry useSpecialPad(false); static ConfigEntry specialPadClass(1); static ConfigEntry isMotionControlsEnabled(true); static ConfigEntry useUnifiedInputConfig(true); -static ConfigEntry micDevice("Default Device"); static ConfigEntry defaultControllerID(""); static ConfigEntry backgroundControllerInput(false); +// Audio +static ConfigEntry micDevice("Default Device"); +static ConfigEntry mainOutputDevice("Default Device"); +static ConfigEntry padSpkOutputDevice("Default Device"); + // GPU static ConfigEntry windowWidth(1280); static ConfigEntry windowHeight(720); @@ -302,6 +306,14 @@ string getMicDevice() { return micDevice.get(); } +std::string getMainOutputDevice() { + return mainOutputDevice.get(); +} + +std::string getPadSpkOutputDevice() { + return padSpkOutputDevice.get(); +} + double getTrophyNotificationDuration() { return trophyNotificationDuration.get(); } @@ -581,10 +593,18 @@ void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific) { cursorHideTimeout.set(newcursorHideTimeout, is_game_specific); } -void setMicDevice(string device, bool is_game_specific) { +void setMicDevice(std::string device, bool is_game_specific) { micDevice.set(device, is_game_specific); } +void setMainOutputDevice(std::string device, bool is_game_specific) { + mainOutputDevice.set(device, is_game_specific); +} + +void setPadSpkOutputDevice(std::string device, bool is_game_specific) { + padSpkOutputDevice.set(device, is_game_specific); +} + void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific) { trophyNotificationDuration.set(newTrophyNotificationDuration, is_game_specific); } @@ -826,10 +846,17 @@ void load(const std::filesystem::path& path, bool is_game_specific) { specialPadClass.setFromToml(input, "specialPadClass", is_game_specific); isMotionControlsEnabled.setFromToml(input, "isMotionControlsEnabled", is_game_specific); useUnifiedInputConfig.setFromToml(input, "useUnifiedInputConfig", is_game_specific); - micDevice.setFromToml(input, "micDevice", is_game_specific); backgroundControllerInput.setFromToml(input, "backgroundControllerInput", is_game_specific); } + if (data.contains("Audio")) { + const toml::value& audio = data.at("Audio"); + + micDevice.setFromToml(audio, "micDevice", is_game_specific); + mainOutputDevice.setFromToml(audio, "mainOutputDevice", is_game_specific); + padSpkOutputDevice.setFromToml(audio, "padSpkOutputDevice", is_game_specific); + } + if (data.contains("GPU")) { const toml::value& gpu = data.at("GPU"); @@ -999,10 +1026,13 @@ void save(const std::filesystem::path& path, bool is_game_specific) { cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); isMotionControlsEnabled.setTomlValue(data, "Input", "isMotionControlsEnabled", is_game_specific); - micDevice.setTomlValue(data, "Input", "micDevice", is_game_specific); backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput", is_game_specific); + micDevice.setTomlValue(data, "Audio", "micDevice", is_game_specific); + mainOutputDevice.setTomlValue(data, "Audio", "mainOutputDevice", is_game_specific); + padSpkOutputDevice.setTomlValue(data, "Audio", "padSpkOutputDevice", is_game_specific); + windowWidth.setTomlValue(data, "GPU", "screenWidth", is_game_specific); windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific); isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific); @@ -1036,9 +1066,8 @@ void save(const std::filesystem::path& path, bool is_game_specific) { m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); - // All other entries if (!is_game_specific) { - std::vector install_dirs; + std::vector install_dirs; std::vector install_dirs_enabled; // temporary structure for ordering @@ -1077,23 +1106,18 @@ void save(const std::filesystem::path& path, bool is_game_specific) { data["GUI"]["loadGameSizeEnabled"] = load_game_size; data["GUI"]["addonInstallDir"] = string{fmt::UTF(settings_addon_install_dir.u8string()).data}; - data["Debug"]["ConfigVersion"] = config_version; data["Keys"]["TrophyKey"] = trophyKey; // Do not save these entries in the game-specific dialog since they are not in the GUI data["General"]["defaultControllerID"] = defaultControllerID.base_value; - data["Input"]["useSpecialPad"] = useSpecialPad.base_value; data["Input"]["specialPadClass"] = specialPadClass.base_value; data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig.base_value; - data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value; data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value; data["GPU"]["patchShaders"] = shouldPatchShaders.base_value; - data["Vulkan"]["validation_gpu"] = vkValidationGpu.base_value; - data["Debug"]["FPSColor"] = isFpsColor.base_value; } @@ -1135,9 +1159,11 @@ void setDefaultValues(bool is_game_specific) { cursorState.set(HideCursorState::Idle, is_game_specific); cursorHideTimeout.set(5, is_game_specific); isMotionControlsEnabled.set(true, is_game_specific); - micDevice.set("Default Device", is_game_specific); backgroundControllerInput.set(false, is_game_specific); + // GS - Audio + micDevice.set("Default Device", is_game_specific); + // GS - GPU windowWidth.set(1280, is_game_specific); windowHeight.set(720, is_game_specific); @@ -1184,11 +1210,14 @@ void setDefaultValues(bool is_game_specific) { useSpecialPad.base_value = false; specialPadClass.base_value = 1; useUnifiedInputConfig.base_value = true; - overrideControllerColor = false; controllerCustomColorRGB[0] = 0; controllerCustomColorRGB[1] = 0; controllerCustomColorRGB[2] = 255; + // TODO: Change to be game specific + mainOutputDevice = "Default Device"; + padSpkOutputDevice = "Default Device"; + // GPU shouldPatchShaders.base_value = false; internalScreenWidth.base_value = 1280; diff --git a/src/common/config.h b/src/common/config.h index 1e285a13b..c4c35560f 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -90,6 +90,11 @@ double getTrophyNotificationDuration(); void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific = false); int getCursorHideTimeout(); +void setCursorHideTimeout(int newcursorHideTimeout); +std::string getMainOutputDevice(); +void setMainOutputDevice(std::string device); +std::string getPadSpkOutputDevice(); +void setPadSpkOutputDevice(std::string device); std::string getMicDevice(); void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false); void setMicDevice(std::string device, bool is_game_specific = false); diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 78d77ccf0..6f026ab0b 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -208,7 +208,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels; break; case OrbisAudioOutPort::Personal: - case OrbisAudioOutPort::Padspk: + case OrbisAudioOutPort::PadSpk: state->output = 4; state->channel = 1; break; @@ -328,7 +328,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, LOG_ERROR(Lib_AudioOut, "Audio out not initialized"); return ORBIS_AUDIO_OUT_ERROR_NOT_INIT; } - if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) && + if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::PadSpk) && (port_type != OrbisAudioOutPort::Audio3d && port_type != OrbisAudioOutPort::Aux)) { LOG_ERROR(Lib_AudioOut, "Invalid port type"); return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT_TYPE; diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index 6d3a6399b..c993582d3 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -25,7 +25,7 @@ enum class OrbisAudioOutPort { Bgm = 1, Voice = 2, Personal = 3, - Padspk = 4, + PadSpk = 4, Audio3d = 126, Aux = 127, }; diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 06ebebe7b..46dd33d73 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -1,15 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include -#include +#include "common/config.h" #include "common/logging/log.h" #include "core/libraries/audio/audioout.h" #include "core/libraries/audio/audioout_backend.h" +#define SDL_INVALID_AUDIODEVICEID 0 // Defined in SDL_audio.h but not made a macro namespace Libraries::AudioOut { class SDLPortBackend : public PortBackend { @@ -21,8 +23,42 @@ public: .channels = port.format_info.num_channels, .freq = static_cast(port.sample_rate), }; - stream = - SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); + + // Determine port type + std::string port_name = port.type == OrbisAudioOutPort::PadSpk + ? Config::getPadSpkOutputDevice() + : Config::getMainOutputDevice(); + SDL_AudioDeviceID dev_id = SDL_INVALID_AUDIODEVICEID; + if (port_name == "None") { + stream = nullptr; + return; + } else if (port_name == "Default Device") { + dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } else { + try { + SDL_AudioDeviceID* dev_array = SDL_GetAudioPlaybackDevices(nullptr); + for (; dev_array != 0;) { + std::string dev_name(SDL_GetAudioDeviceName(*dev_array)); + if (dev_name == port_name) { + dev_id = *dev_array; + break; + } else { + dev_array++; + } + } + if (dev_id == SDL_INVALID_AUDIODEVICEID) { + LOG_WARNING(Lib_AudioOut, "Audio device not found: {}", port_name); + dev_id = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + } catch (const std::exception& e) { + LOG_ERROR(Lib_AudioOut, "Invalid audio output device: {}", port_name); + stream = nullptr; + return; + } + } + + // Open the audio stream + stream = SDL_OpenAudioDeviceStream(dev_id, &fmt, nullptr, nullptr); if (stream == nullptr) { LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); return; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index f5426bd54..17053790b 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -408,8 +408,8 @@ public: } if (selected == &gameConfigConfigure || selected == &gameConfigCreate) { - auto settingsWindow = new SettingsDialog(m_gui_settings, m_compat_info, widget, true, - serialStr.toStdString()); + auto settingsWindow = new SettingsDialog(m_gui_settings, m_compat_info, widget, false, + true, serialStr.toStdString()); settingsWindow->exec(); } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index ad3ab688e..8dc21731c 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -407,7 +407,8 @@ void MainWindow::CreateConnects() { &MainWindow::StartGame); connect(ui->configureAct, &QAction::triggered, this, [this]() { - auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this); + auto settingsDialog = + new SettingsDialog(m_gui_settings, m_compat_info, this, isGameRunning); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); @@ -441,7 +442,8 @@ void MainWindow::CreateConnects() { }); connect(ui->settingsButton, &QPushButton::clicked, this, [this]() { - auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this); + auto settingsDialog = + new SettingsDialog(m_gui_settings, m_compat_info, this, isGameRunning); connect(settingsDialog, &SettingsDialog::LanguageChanged, this, &MainWindow::OnLanguageChanged); diff --git a/src/qt_gui/sdl_event_wrapper.cpp b/src/qt_gui/sdl_event_wrapper.cpp index 052321233..e160dec4e 100644 --- a/src/qt_gui/sdl_event_wrapper.cpp +++ b/src/qt_gui/sdl_event_wrapper.cpp @@ -41,6 +41,14 @@ bool Wrapper::ProcessEvent(SDL_Event* event) { case SDL_EVENT_GAMEPAD_AXIS_MOTION: emit SDLEvent(SDL_EVENT_GAMEPAD_AXIS_MOTION, event->gaxis.axis, event->gaxis.value); return true; + case SDL_EVENT_AUDIO_DEVICE_ADDED: + if (event->adevice.recording == 0) + emit audioDeviceChanged(true); + return true; + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + if (event->adevice.recording == 0) + emit audioDeviceChanged(false); + return true; // block all other SDL events while wrapper is active default: return true; diff --git a/src/qt_gui/sdl_event_wrapper.h b/src/qt_gui/sdl_event_wrapper.h index 54d8c9cd1..4cb918c79 100644 --- a/src/qt_gui/sdl_event_wrapper.h +++ b/src/qt_gui/sdl_event_wrapper.h @@ -20,6 +20,7 @@ public: signals: void SDLEvent(int Type, int Input, int Value); + void audioDeviceChanged(bool isAdd); }; } // namespace SdlEventWrapper diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index b551387ce..40ed5017a 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -11,6 +11,7 @@ #include #include "common/config.h" +#include "common/logging/log.h" #include "common/scm_rev.h" #include "core/libraries/audio/audioout.h" #include "qt_gui/compatibility_info.h" @@ -27,6 +28,7 @@ #include "common/logging/backend.h" #include "common/logging/filter.h" #include "log_presets_dialog.h" +#include "sdl_event_wrapper.h" #include "settings_dialog.h" #include "ui_settings_dialog.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -82,18 +84,20 @@ static std::vector m_physical_devices; SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, std::shared_ptr m_compat_info, - QWidget* parent, bool is_game_specific, std::string gsc_serial) + QWidget* parent, bool is_running, bool is_specific, + std::string gsc_serial) : QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)), - game_specific(is_game_specific), gs_serial(gsc_serial) { + is_game_running(is_running), is_game_specific(is_specific), gs_serial(gsc_serial) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); + initialHeight = this->height(); // Add a small clear "x" button inside the Log Filter input ui->logFilterLineEdit->setClearButtonEnabled(true); - if (game_specific) { + if (is_game_specific) { // Paths tab ui->tabWidgetSettings->setTabVisible(5, false); ui->chooseHomeTabComboBox->removeItem(5); @@ -109,7 +113,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, } std::filesystem::path config_file = - game_specific + is_game_specific ? Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (gs_serial + ".toml") : Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"; @@ -184,6 +188,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, } InitializeEmulatorLanguages(); + onAudioDeviceChange(true); LoadValuesFromConfig(); defaultTextEdit = tr("Point your mouse at an option to display its description."); @@ -194,17 +199,17 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this, config_file](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { - is_saving = true; - UpdateSettings(game_specific); - Config::save(config_file, game_specific); + is_game_saving = true; + UpdateSettings(is_game_specific); + Config::save(config_file, is_game_specific); QWidget::close(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { - UpdateSettings(game_specific); - Config::save(config_file, game_specific); + UpdateSettings(is_game_specific); + Config::save(config_file, is_game_specific); } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { setDefaultValues(); - Config::setDefaultValues(game_specific); - Config::save(config_file, game_specific); + Config::setDefaultValues(is_game_specific); + Config::save(config_file, is_game_specific); LoadValuesFromConfig(); } else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) { ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); @@ -232,7 +237,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, { connect(ui->horizontalVolumeSlider, &QSlider::valueChanged, this, [this](int value) { VolumeSliderChange(value); - Config::setVolumeSlider(value, game_specific); + Config::setVolumeSlider(value, is_game_specific); Libraries::AudioOut::AdjustVol(); }); @@ -526,30 +531,52 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, ui->networkConnectedCheckBox->installEventFilter(this); ui->psnSignInCheckBox->installEventFilter(this); } + + SdlEventWrapper::Wrapper::wrapperActive = true; + if (!is_game_running) { + SDL_InitSubSystem(SDL_INIT_EVENTS); + Polling = QtConcurrent::run(&SettingsDialog::pollSDLevents, this); + } else { + SdlEventWrapper::Wrapper* DeviceEventWrapper = SdlEventWrapper::Wrapper::GetInstance(); + QObject::connect(DeviceEventWrapper, &SdlEventWrapper::Wrapper::audioDeviceChanged, this, + &SettingsDialog::onAudioDeviceChange); + } } void SettingsDialog::closeEvent(QCloseEvent* event) { - if (!is_saving) { + if (!is_game_saving) { ui->backgroundImageOpacitySlider->setValue(backgroundImageOpacitySlider_backup); emit BackgroundOpacityChanged(backgroundImageOpacitySlider_backup); ui->BGMVolumeSlider->setValue(bgm_volume_backup); BackgroundMusicPlayer::getInstance().setVolume(bgm_volume_backup); SyncRealTimeWidgetstoConfig(); } + + SdlEventWrapper::Wrapper::wrapperActive = false; + if (!is_game_running) { + SDL_Event quitLoop{}; + quitLoop.type = SDL_EVENT_QUIT; + SDL_PushEvent(&quitLoop); + Polling.waitForFinished(); + + SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + SDL_Quit(); + } QDialog::closeEvent(event); } void SettingsDialog::LoadValuesFromConfig() { std::filesystem::path config_file; config_file = - game_specific + is_game_specific ? Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (gs_serial + ".toml") : Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"; std::error_code error; bool is_newly_created = false; if (!std::filesystem::exists(config_file, error)) { - Config::save(config_file, game_specific); + Config::save(config_file, is_game_specific); is_newly_created = true; } @@ -567,7 +594,7 @@ void SettingsDialog::LoadValuesFromConfig() { 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 30, 28}; // Entries with no game-specific settings - if (!game_specific) { + if (!is_game_specific) { const auto save_data_path = Config::GetSaveDataPath(); QString save_data_path_string; Common::FS::PathToQString(save_data_path_string, save_data_path); @@ -728,6 +755,11 @@ void SettingsDialog::LoadValuesFromConfig() { toml::find_or(data, "Debug", "CollectShader", false)); ui->enableLoggingCheckBox->setChecked(toml::find_or(data, "Debug", "logEnabled", true)); + ui->GenAudioComboBox->setCurrentText(QString::fromStdString( + toml::find_or(data, "General", "mainOutputDevice", ""))); + ui->DsAudioComboBox->setCurrentText(QString::fromStdString( + toml::find_or(data, "General", "padSpkOutputDevice", ""))); + std::string chooseHomeTab = toml::find_or(data, "General", "chooseHomeTab", "General"); QString translatedText = chooseHomeTabMap.key(QString::fromStdString(chooseHomeTab)); @@ -979,76 +1011,75 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { return QDialog::eventFilter(obj, event); } -void SettingsDialog::UpdateSettings(bool game_specific) { +void SettingsDialog::UpdateSettings(bool is_specific) { // Entries with game-specific settings, needs the game-specific arg - Config::setReadbacks(ui->readbacksCheckBox->isChecked(), game_specific); - Config::setReadbackLinearImages(ui->readbackLinearImagesCheckBox->isChecked(), game_specific); - Config::setDirectMemoryAccess(ui->dmaCheckBox->isChecked(), game_specific); - Config::setDevKitConsole(ui->devkitCheckBox->isChecked(), game_specific); - Config::setNeoMode(ui->neoCheckBox->isChecked(), game_specific); - Config::setConnectedToNetwork(ui->networkConnectedCheckBox->isChecked(), game_specific); - Config::setPSNSignedIn(ui->psnSignInCheckBox->isChecked(), game_specific); + Config::setReadbacks(ui->readbacksCheckBox->isChecked(), is_specific); + Config::setReadbackLinearImages(ui->readbackLinearImagesCheckBox->isChecked(), is_specific); + Config::setDirectMemoryAccess(ui->dmaCheckBox->isChecked(), is_specific); + Config::setDevKitConsole(ui->devkitCheckBox->isChecked(), is_specific); + Config::setNeoMode(ui->neoCheckBox->isChecked(), is_specific); + Config::setConnectedToNetwork(ui->networkConnectedCheckBox->isChecked(), is_specific); + Config::setPSNSignedIn(ui->psnSignInCheckBox->isChecked(), is_specific); Config::setIsFullscreen( - screenModeMap.value(ui->displayModeComboBox->currentText()) != "Windowed", game_specific); + screenModeMap.value(ui->displayModeComboBox->currentText()) != "Windowed", is_specific); Config::setFullscreenMode( - screenModeMap.value(ui->displayModeComboBox->currentText()).toStdString(), game_specific); + screenModeMap.value(ui->displayModeComboBox->currentText()).toStdString(), is_specific); Config::setPresentMode( - presentModeMap.value(ui->presentModeComboBox->currentText()).toStdString(), game_specific); - Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked(), game_specific); + presentModeMap.value(ui->presentModeComboBox->currentText()).toStdString(), is_specific); + Config::setIsMotionControlsEnabled(ui->motionControlsCheckBox->isChecked(), is_specific); Config::setBackgroundControllerInput(ui->backgroundControllerCheckBox->isChecked(), - game_specific); - Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked(), game_specific); - Config::setTrophyNotificationDuration(ui->popUpDurationSpinBox->value(), game_specific); + is_specific); + Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked(), is_specific); + Config::setTrophyNotificationDuration(ui->popUpDurationSpinBox->value(), is_specific); if (ui->radioButton_Top->isChecked()) { - Config::setSideTrophy("top", game_specific); + Config::setSideTrophy("top", is_specific); } else if (ui->radioButton_Left->isChecked()) { - Config::setSideTrophy("left", game_specific); + Config::setSideTrophy("left", is_specific); } else if (ui->radioButton_Right->isChecked()) { - Config::setSideTrophy("right", game_specific); + Config::setSideTrophy("right", is_specific); } else if (ui->radioButton_Bottom->isChecked()) { - Config::setSideTrophy("bottom", game_specific); + Config::setSideTrophy("bottom", is_specific); } - Config::setLoggingEnabled(ui->enableLoggingCheckBox->isChecked(), game_specific); - Config::setAllowHDR(ui->enableHDRCheckBox->isChecked(), game_specific); + Config::setLoggingEnabled(ui->enableLoggingCheckBox->isChecked(), is_specific); + Config::setAllowHDR(ui->enableHDRCheckBox->isChecked(), is_specific); Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString(), - game_specific); - Config::setMicDevice(ui->micComboBox->currentData().toString().toStdString(), game_specific); - Config::setLogFilter(ui->logFilterLineEdit->text().toStdString(), game_specific); - Config::setUserName(ui->userNameLineEdit->text().toStdString(), game_specific); - Config::setCursorState(ui->hideCursorComboBox->currentIndex(), game_specific); - Config::setCursorHideTimeout(ui->hideCursorComboBox->currentIndex(), game_specific); - Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1, game_specific); - Config::setVolumeSlider(ui->horizontalVolumeSlider->value(), game_specific); - Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()], - game_specific); - Config::setWindowWidth(ui->widthSpinBox->value(), game_specific); - Config::setWindowHeight(ui->heightSpinBox->value(), game_specific); - Config::setVblankFreq(ui->vblankSpinBox->value(), game_specific); - Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked(), game_specific); - Config::setNullGpu(ui->nullGpuCheckBox->isChecked(), game_specific); - Config::setFsrEnabled(ui->FSRCheckBox->isChecked(), game_specific); - Config::setRcasEnabled(ui->RCASCheckBox->isChecked(), game_specific); - Config::setRcasAttenuation(ui->RCASSlider->value(), game_specific); - Config::setShowSplash(ui->showSplashCheckBox->isChecked(), game_specific); - Config::setDebugDump(ui->debugDump->isChecked(), game_specific); - Config::setSeparateLogFilesEnabled(ui->separateLogFilesCheckbox->isChecked(), game_specific); - Config::setVkValidation(ui->vkValidationCheckBox->isChecked(), game_specific); - Config::setVkSyncValidation(ui->vkSyncValidationCheckBox->isChecked(), game_specific); - Config::setRdocEnabled(ui->rdocCheckBox->isChecked(), game_specific); - Config::setVkHostMarkersEnabled(ui->hostMarkersCheckBox->isChecked(), game_specific); - Config::setVkGuestMarkersEnabled(ui->guestMarkersCheckBox->isChecked(), game_specific); - Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked(), game_specific); - Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked(), game_specific); - Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked(), game_specific); + is_specific); + Config::setMicDevice(ui->micComboBox->currentData().toString().toStdString(), is_specific); + Config::setLogFilter(ui->logFilterLineEdit->text().toStdString(), is_specific); + Config::setUserName(ui->userNameLineEdit->text().toStdString(), is_specific); + Config::setCursorState(ui->hideCursorComboBox->currentIndex(), is_specific); + Config::setCursorHideTimeout(ui->hideCursorComboBox->currentIndex(), is_specific); + Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1, is_specific); + Config::setVolumeSlider(ui->horizontalVolumeSlider->value(), is_specific); + Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()], is_specific); + Config::setWindowWidth(ui->widthSpinBox->value(), is_specific); + Config::setWindowHeight(ui->heightSpinBox->value(), is_specific); + Config::setVblankFreq(ui->vblankSpinBox->value(), is_specific); + Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked(), is_specific); + Config::setNullGpu(ui->nullGpuCheckBox->isChecked(), is_specific); + Config::setFsrEnabled(ui->FSRCheckBox->isChecked(), is_specific); + Config::setRcasEnabled(ui->RCASCheckBox->isChecked(), is_specific); + Config::setRcasAttenuation(ui->RCASSlider->value(), is_specific); + Config::setShowSplash(ui->showSplashCheckBox->isChecked(), is_specific); + Config::setDebugDump(ui->debugDump->isChecked(), is_specific); + Config::setSeparateLogFilesEnabled(ui->separateLogFilesCheckbox->isChecked(), is_specific); + Config::setVkValidation(ui->vkValidationCheckBox->isChecked(), is_specific); + Config::setVkSyncValidation(ui->vkSyncValidationCheckBox->isChecked(), is_specific); + Config::setRdocEnabled(ui->rdocCheckBox->isChecked(), is_specific); + Config::setVkHostMarkersEnabled(ui->hostMarkersCheckBox->isChecked(), is_specific); + Config::setVkGuestMarkersEnabled(ui->guestMarkersCheckBox->isChecked(), is_specific); + Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked(), is_specific); + Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked(), is_specific); + Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked(), is_specific); Config::setChooseHomeTab( chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString(), - game_specific); + is_specific); // Entries with no game-specific settings - if (!game_specific) { + if (!is_specific) { std::vector dirs_with_states; for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { QListWidgetItem* item = ui->gameFoldersListWidget->item(i); @@ -1096,7 +1127,7 @@ void SettingsDialog::SyncRealTimeWidgetstoConfig() { std::filesystem::path userdir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const toml::value data = toml::parse(userdir / "config.toml"); - if (!game_specific) { + if (!is_game_specific) { ui->gameFoldersListWidget->clear(); if (data.contains("GUI")) { const toml::value& gui = data.at("GUI"); @@ -1137,7 +1168,7 @@ void SettingsDialog::SyncRealTimeWidgetstoConfig() { } toml::value gs_data; - game_specific + is_game_specific ? gs_data = toml::parse(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (gs_serial + ".toml")) : gs_data = data; @@ -1147,8 +1178,8 @@ void SettingsDialog::SyncRealTimeWidgetstoConfig() { // Since config::set can be called for volume slider (connected to the widget) outside the save // function, need to null it out if GS GUI is closed without saving - game_specific ? Config::resetGameSpecificValue("volumeSlider") - : Config::setVolumeSlider(sliderValue); + is_game_specific ? Config::resetGameSpecificValue("volumeSlider") + : Config::setVolumeSlider(sliderValue); if (presenter) { presenter->GetFsrSettingsRef().enable = @@ -1161,7 +1192,7 @@ void SettingsDialog::SyncRealTimeWidgetstoConfig() { } void SettingsDialog::setDefaultValues() { - if (!game_specific) { + if (!is_game_specific) { m_gui_settings->SetValue(gui::gl_showBackgroundImage, true); m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50); m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false); @@ -1176,3 +1207,59 @@ void SettingsDialog::setDefaultValues() { m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US"); } } + +void SettingsDialog::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_AUDIO_DEVICE_ADDED) { + onAudioDeviceChange(true); + } + + if (event.type == SDL_EVENT_AUDIO_DEVICE_REMOVED) { + onAudioDeviceChange(false); + } + } +} + +void SettingsDialog::onAudioDeviceChange(bool isAdd) { + ui->GenAudioComboBox->clear(); + ui->DsAudioComboBox->clear(); + + // prevent device list from refreshing too fast when game not running + if (!is_game_running && isAdd == false) + QThread::msleep(100); + + int deviceCount; + QStringList deviceList; + SDL_AudioDeviceID* devices = SDL_GetAudioPlaybackDevices(&deviceCount); + + if (!devices) { + LOG_ERROR(Lib_AudioOut, "Unable to retrieve audio device list {}", SDL_GetError()); + return; + } + + for (int i = 0; i < deviceCount; ++i) { + const char* name = SDL_GetAudioDeviceName(devices[i]); + std::string name_string = std::string(name); + deviceList.append(QString::fromStdString(name_string)); + } + + ui->GenAudioComboBox->addItem(tr("Default Device"), "Default Device"); + ui->GenAudioComboBox->addItems(deviceList); + ui->GenAudioComboBox->setCurrentText(QString::fromStdString(Config::getMainOutputDevice())); + + ui->DsAudioComboBox->addItem(tr("Default Device"), "Default Device"); + ui->DsAudioComboBox->addItems(deviceList); + ui->DsAudioComboBox->setCurrentText(QString::fromStdString(Config::getPadSpkOutputDevice())); + + SDL_free(devices); +} diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index ace401258..25ea1507e 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -23,8 +23,8 @@ class SettingsDialog : public QDialog { public: explicit SettingsDialog(std::shared_ptr gui_settings, std::shared_ptr m_compat_info, - QWidget* parent = nullptr, bool is_game_specific = false, - std::string gsc_serial = ""); + QWidget* parent = nullptr, bool is_game_running = false, + bool is_game_specific = false, std::string gsc_serial = ""); ~SettingsDialog(); bool eventFilter(QObject* obj, QEvent* event) override; @@ -47,6 +47,8 @@ private: void closeEvent(QCloseEvent* event) override; void setDefaultValues(); void VolumeSliderChange(int value); + void onAudioDeviceChange(bool isAdd); + void pollSDLevents(); std::unique_ptr ui; @@ -55,9 +57,13 @@ private: QString defaultTextEdit; int initialHeight; - bool is_saving = false; - bool game_specific; + std::string gs_serial; + bool is_game_running = false; + bool is_game_specific = false; + bool is_game_saving = false; + std::shared_ptr m_gui_settings; + QFuture Polling; }; diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c80335c40..bff53a737 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -77,7 +77,7 @@ 0 0 946 - 501 + 536 @@ -381,6 +381,30 @@ + + + + Audio Device (general) + + + + + + + + + + + + Audio Device (DS4 speaker) + + + + + + + + @@ -1010,7 +1034,7 @@ 0 0 946 - 501 + 536 @@ -1417,7 +1441,7 @@ 0 0 946 - 501 + 536 @@ -1632,7 +1656,7 @@ 0 0 946 - 501 + 536 @@ -1918,7 +1942,7 @@ 0 0 946 - 501 + 536 @@ -2087,7 +2111,7 @@ 0 0 946 - 299 + 536