diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index 766db2621..c0d868ae4 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -6,12 +6,14 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW { m_ui.setupUi(this); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.region, &Settings::region); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.biosPath, &Settings::bios_path); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.enableTTYOutput, &Settings::bios_patch_tty_enable); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.fastBoot, &Settings::bios_patch_fast_boot); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.enableSpeedLimiter, &Settings::speed_limiter_enabled); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.pauseOnStart, &Settings::start_paused); + SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console/Region", + &Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName); + SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_ui.biosPath, "BIOS/Path"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableTTYOutput, "BIOS/PatchTTYEnable"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fastBoot, "BIOS/PatchFastBoot"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter, + "General/SpeedLimiterEnabled"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "General/StartPaused"); } ConsoleSettingsWidget::~ConsoleSettingsWidget() = default; diff --git a/src/duckstation-qt/gamelistsettingswidget.cpp b/src/duckstation-qt/gamelistsettingswidget.cpp index 51e104012..f75911aea 100644 --- a/src/duckstation-qt/gamelistsettingswidget.cpp +++ b/src/duckstation-qt/gamelistsettingswidget.cpp @@ -138,13 +138,11 @@ public: void loadFromSettings() { - const QSettings& qsettings = m_host_interface->getQSettings(); - - QStringList path_list = qsettings.value(QStringLiteral("GameList/Paths")).toStringList(); + QStringList path_list = m_host_interface->getSettingValue(QStringLiteral("GameList/Paths")).toStringList(); for (QString& entry : path_list) m_entries.push_back({std::move(entry), false}); - path_list = qsettings.value(QStringLiteral("GameList/RecursivePaths")).toStringList(); + path_list = m_host_interface->getSettingValue(QStringLiteral("GameList/RecursivePaths")).toStringList(); for (QString& entry : path_list) m_entries.push_back({std::move(entry), true}); } @@ -162,17 +160,15 @@ public: paths.push_back(entry.path); } - QSettings& qsettings = m_host_interface->getQSettings(); - if (paths.empty()) - qsettings.remove(QStringLiteral("GameList/Paths")); + m_host_interface->removeSettingValue(QStringLiteral("GameList/Paths")); else - qsettings.setValue(QStringLiteral("GameList/Paths"), paths); + m_host_interface->putSettingValue(QStringLiteral("GameList/Paths"), paths); if (recursive_paths.empty()) - qsettings.remove(QStringLiteral("GameList/RecursivePaths")); + m_host_interface->removeSettingValue(QStringLiteral("GameList/RecursivePaths")); else - qsettings.setValue(QStringLiteral("GameList/RecursivePaths"), recursive_paths); + m_host_interface->putSettingValue(QStringLiteral("GameList/RecursivePaths"), recursive_paths); } private: @@ -191,10 +187,8 @@ GameListSettingsWidget::GameListSettingsWidget(QtHostInterface* host_interface, { m_ui.setupUi(this); - QSettings& qsettings = host_interface->getQSettings(); - m_search_directories_model = new GameListSearchDirectoriesModel(host_interface); - m_ui.redumpDatabasePath->setText(qsettings.value("GameList/RedumpDatabasePath").toString()); + m_ui.redumpDatabasePath->setText(host_interface->getSettingValue("GameList/RedumpDatabasePath").toString()); m_ui.searchDirectoryList->setModel(m_search_directories_model); m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection); m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -280,7 +274,7 @@ void GameListSettingsWidget::onBrowseRedumpPathButtonPressed() return; m_ui.redumpDatabasePath->setText(filename); - m_host_interface->getQSettings().setValue("GameList/RedumpDatabasePath", filename); + m_host_interface->putSettingValue(QStringLiteral("GameList/RedumpDatabasePath"), filename); m_host_interface->refreshGameList(true, true); } diff --git a/src/duckstation-qt/gpusettingswidget.cpp b/src/duckstation-qt/gpusettingswidget.cpp index c2bf7512f..41da423dd 100644 --- a/src/duckstation-qt/gpusettingswidget.cpp +++ b/src/duckstation-qt/gpusettingswidget.cpp @@ -9,17 +9,16 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p m_ui.setupUi(this); setupAdditionalUi(); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.renderer, &Settings::gpu_renderer); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.fullscreen, &Settings::display_fullscreen); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.displayLinearFiltering, - &Settings::display_linear_filtering); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.vsync, &Settings::video_sync_enabled); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.resolutionScale, &Settings::gpu_resolution_scale); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.trueColor, &Settings::gpu_true_color); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.linearTextureFiltering, - &Settings::gpu_texture_filtering); - SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.forceProgressiveScan, - &Settings::gpu_force_progressive_scan); + SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.renderer, "GPU/Renderer", + &Settings::ParseRendererName, &Settings::GetRendererName); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fullscreen, "Display/Fullscreen"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayLinearFiltering, + "Display/LinearFiltering"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display/VSync"); + SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.resolutionScale, "GPU/ResolutionScale"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.trueColor, "GPU/TrueColor"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.linearTextureFiltering, "GPU/TextureFiltering"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.forceProgressiveScan, "GPU/ForceProgressiveScan"); } GPUSettingsWidget::~GPUSettingsWidget() = default; diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 99cf3fb2d..2dbca4e89 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -9,7 +9,7 @@ InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interfa QWidget* parent) : QPushButton(parent), m_host_interface(host_interface), m_setting_name(std::move(setting_name)) { - m_current_binding_value = m_host_interface->getQSettings().value(m_setting_name).toString(); + m_current_binding_value = m_host_interface->getSettingValue(m_setting_name).toString(); setText(m_current_binding_value); connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed); @@ -54,7 +54,7 @@ void InputButtonBindingWidget::setNewBinding() if (m_new_binding_value.isEmpty()) return; - m_host_interface->getQSettings().setValue(m_setting_name, m_new_binding_value); + m_host_interface->putSettingValue(m_setting_name, m_new_binding_value); m_host_interface->updateInputMap(); m_current_binding_value = std::move(m_new_binding_value); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 6c5f483fb..ec7b519e5 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -216,9 +216,8 @@ void MainWindow::setupAdditionalUi() action->setCheckable(true); action->setChecked(m_host_interface->GetCoreSettings().gpu_renderer == renderer); connect(action, &QAction::triggered, [this, action, renderer]() { - m_host_interface->getQSettings().setValue(QStringLiteral("GPU/Renderer"), - QString(Settings::GetRendererName(renderer))); - m_host_interface->GetCoreSettings().gpu_renderer = renderer; + m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer))); + m_host_interface->applySettings(); action->setChecked(true); switchRenderer(); }); diff --git a/src/duckstation-qt/portsettingswidget.cpp b/src/duckstation-qt/portsettingswidget.cpp index 203e52bdf..880247cdb 100644 --- a/src/duckstation-qt/portsettingswidget.cpp +++ b/src/duckstation-qt/portsettingswidget.cpp @@ -30,13 +30,12 @@ void PortSettingsWidget::createUi() void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) { - const Settings& settings = m_host_interface->GetCoreSettings(); - ui->widget = new QWidget(m_tab_widget); ui->layout = new QVBoxLayout(ui->widget); QHBoxLayout* memory_card_layout = new QHBoxLayout(); - ui->memory_card_path = new QLineEdit(QString::fromStdString(settings.memory_card_paths[index]), ui->widget); + ui->memory_card_path = new QLineEdit( + m_host_interface->getSettingValue(QStringLiteral("MemoryCards/Card%1Path").arg(index + 1)).toString(), ui->widget); memory_card_layout->addWidget(ui->memory_card_path); ui->memory_card_path_browse = new QPushButton(tr("Browse..."), ui->widget); memory_card_layout->addWidget(ui->memory_card_path_browse); @@ -49,13 +48,19 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) ui->controller_type->addItem( QString::fromLocal8Bit(Settings::GetControllerTypeDisplayName(static_cast(i)))); } - ui->controller_type->setCurrentIndex(static_cast(settings.controller_types[index])); + ControllerType ctype = Settings::ParseControllerTypeName( + m_host_interface->getSettingValue(QStringLiteral("Controller%1/Type").arg(index + 1)) + .toString() + .toStdString() + .c_str()) + .value_or(ControllerType::None); + ui->controller_type->setCurrentIndex(static_cast(ctype)); connect(ui->controller_type, static_cast(&QComboBox::currentIndexChanged), [this, index]() { onControllerTypeChanged(index); }); ui->layout->addWidget(new QLabel(tr("Controller Type:"), ui->widget)); ui->layout->addWidget(ui->controller_type); - createPortBindingSettingsUi(index, ui); + createPortBindingSettingsUi(index, ui, ctype); ui->layout->addStretch(1); @@ -64,12 +69,11 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui) m_tab_widget->addTab(ui->widget, tr("Port %1").arg(index + 1)); } -void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* ui) +void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype) { QWidget* container = new QWidget(ui->widget); QGridLayout* layout = new QGridLayout(container); layout->setContentsMargins(0, 0, 0, 0); - const ControllerType ctype = m_host_interface->GetCoreSettings().controller_types[index]; const auto buttons = Controller::GetButtonNames(ctype); if (!buttons.empty()) @@ -124,9 +128,9 @@ void PortSettingsWidget::onControllerTypeChanged(int index) if (type_index < 0 || type_index >= static_cast(ControllerType::Count)) return; - m_host_interface->GetCoreSettings().controller_types[index] = static_cast(type_index); - m_host_interface->getQSettings().setValue( + m_host_interface->putSettingValue( QStringLiteral("Controller%1/Type").arg(index + 1), QString::fromStdString(Settings::GetControllerTypeName(static_cast(type_index)))); - createPortBindingSettingsUi(index, &m_port_ui[index]); + m_host_interface->applySettings(); + createPortBindingSettingsUi(index, &m_port_ui[index], static_cast(type_index)); } diff --git a/src/duckstation-qt/portsettingswidget.h b/src/duckstation-qt/portsettingswidget.h index 911f1766f..c50d59cb1 100644 --- a/src/duckstation-qt/portsettingswidget.h +++ b/src/duckstation-qt/portsettingswidget.h @@ -40,7 +40,7 @@ private: void createUi(); void createPortSettingsUi(int index, PortSettingsUI* ui); - void createPortBindingSettingsUi(int index, PortSettingsUI* ui); + void createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype); void onControllerTypeChanged(int index); std::array m_port_ui = {}; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 726e18b6b..9bd8b9301 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -48,6 +48,8 @@ void QtHostInterface::ReportMessage(const char* message) void QtHostInterface::setDefaultSettings() { + std::lock_guard guard(m_qsettings_mutex); + m_settings.SetDefaults(); // default input settings for Qt @@ -67,20 +69,57 @@ void QtHostInterface::setDefaultSettings() m_qsettings.setValue(QStringLiteral("Controller1/ButtonR1"), QStringLiteral("Keyboard/E")); m_qsettings.setValue(QStringLiteral("Controller1/ButtonR2"), QStringLiteral("Keyboard/3")); - updateQSettings(); + updateQSettingsFromCoreSettings(); } -void QtHostInterface::updateQSettings() +QVariant QtHostInterface::getSettingValue(const QString& name) +{ + std::lock_guard guard(m_qsettings_mutex); + return m_qsettings.value(name); +} + +void QtHostInterface::putSettingValue(const QString& name, const QVariant& value) +{ + std::lock_guard guard(m_qsettings_mutex); + m_qsettings.setValue(name, value); +} + +void QtHostInterface::removeSettingValue(const QString& name) +{ + std::lock_guard guard(m_qsettings_mutex); + m_qsettings.remove(name); +} + +void QtHostInterface::updateQSettingsFromCoreSettings() { QtSettingsInterface si(m_qsettings); m_settings.Save(si); - // m_qsettings.sync(); } void QtHostInterface::applySettings() { - QtSettingsInterface si(m_qsettings); - m_settings.Load(si); + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection); + return; + } + + // TODO: Should we move this to the base class? + const bool old_vsync_enabled = m_settings.video_sync_enabled; + const bool old_audio_sync_enabled = m_settings.audio_sync_enabled; + const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled; + + { + std::lock_guard guard(m_qsettings_mutex); + QtSettingsInterface si(m_qsettings); + m_settings.Load(si); + } + + if (m_settings.video_sync_enabled != old_vsync_enabled || m_settings.audio_sync_enabled != old_audio_sync_enabled || + m_settings.speed_limiter_enabled != old_speed_limiter_enabled) + { + UpdateSpeedLimiterState(); + } } void QtHostInterface::checkSettings() @@ -105,7 +144,9 @@ void QtHostInterface::checkSettings() setDefaultSettings(); } - applySettings(); + // initial setting init - we don't do this locked since the thread hasn't been created yet + QtSettingsInterface si(m_qsettings); + m_settings.Load(si); } void QtHostInterface::createGameList() @@ -446,6 +487,7 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav wakeThread(); m_audio_stream->PauseOutput(false); + UpdateSpeedLimiterState(); emit emulationStarted(); } diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index a1356eed7..cbc4bcf7b 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -30,11 +31,12 @@ public: void ReportError(const char* message) override; void ReportMessage(const char* message) override; - const QSettings& getQSettings() const { return m_qsettings; } - QSettings& getQSettings() { return m_qsettings; } void setDefaultSettings(); - void updateQSettings(); - void applySettings(); + + /// Thread-safe QSettings access. + QVariant getSettingValue(const QString& name); + void putSettingValue(const QString& name, const QVariant& value); + void removeSettingValue(const QString& name); const Settings& GetCoreSettings() const { return m_settings; } Settings& GetCoreSettings() { return m_settings; } @@ -74,6 +76,7 @@ Q_SIGNALS: void performanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time); public Q_SLOTS: + void applySettings(); void powerOffSystem(); void resetSystem(); void pauseSystem(bool paused); @@ -113,6 +116,8 @@ private: }; void checkSettings(); + void updateQSettingsFromCoreSettings(); + void createGameList(); void updateControllerInputMap(); void updateHotkeyInputMap(); @@ -124,6 +129,7 @@ private: void wakeThread(); QSettings m_qsettings; + std::mutex m_qsettings_mutex; std::unique_ptr m_game_list; diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h index b4ce89086..25dfb0091 100644 --- a/src/duckstation-qt/settingwidgetbinder.h +++ b/src/duckstation-qt/settingwidgetbinder.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include "core/settings.h" @@ -63,7 +64,7 @@ struct SettingAccessor template static void connectValueChanged(QComboBox* widget, F func) { - widget->connect(widget, static_cast(&QComboBox::currentIndexChanged), func); + widget->connect(widget, static_cast(&QComboBox::currentIndexChanged), func); } }; @@ -90,64 +91,68 @@ struct SettingAccessor }; /// Binds a widget's value to a setting, updating it when the value changes. -// template -// void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr); template -void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, bool Settings::*settings_ptr) +void BindWidgetToBoolSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name) { using Accessor = SettingAccessor; - Accessor::setBoolValue(widget, hi->GetCoreSettings().*settings_ptr); + Accessor::setBoolValue(widget, hi->getSettingValue(setting_name).toBool()); - Accessor::connectValueChanged(widget, [hi, widget, settings_ptr]() { - (hi->GetCoreSettings().*settings_ptr) = Accessor::getBoolValue(widget); - hi->updateQSettings(); + Accessor::connectValueChanged(widget, [hi, widget, setting_name]() { + const bool new_value = Accessor::getBoolValue(widget); + hi->putSettingValue(setting_name, new_value); + hi->applySettings(); }); } template -void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, std::string Settings::*settings_ptr) +void BindWidgetToIntSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name) { using Accessor = SettingAccessor; - Accessor::setStringValue(widget, QString::fromStdString(hi->GetCoreSettings().*settings_ptr)); + Accessor::setIntValue(widget, hi->getSettingValue(setting_name).toInt()); - Accessor::connectValueChanged(widget, [hi, widget, settings_ptr]() { - const QString value = Accessor::getStringValue(widget); - (hi->GetCoreSettings().*settings_ptr) = value.toStdString(); - hi->updateQSettings(); + Accessor::connectValueChanged(widget, [hi, widget, setting_name]() { + const int new_value = Accessor::getIntValue(widget); + hi->putSettingValue(setting_name, new_value); + hi->applySettings(); + }); +} + +template +void BindWidgetToStringSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name) +{ + using Accessor = SettingAccessor; + + Accessor::setStringValue(widget, hi->getSettingValue(setting_name).toString()); + + Accessor::connectValueChanged(widget, [hi, widget, setting_name]() { + const QString new_value = Accessor::getStringValue(widget); + hi->putSettingValue(setting_name, new_value); + hi->applySettings(); }); } template -void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr, - std::enable_if_t, int>* v = nullptr) +void BindWidgetToEnumSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name, + std::optional (*from_string_function)(const char* str), + const char* (*to_string_function)(DataType value)) { using Accessor = SettingAccessor; using UnderlyingType = std::underlying_type_t; - Accessor::setIntValue(widget, static_cast(static_cast(hi->GetCoreSettings().*settings_ptr))); + const QString old_setting_string_value = hi->getSettingValue(setting_name).toString(); + const std::optional old_setting_value = + from_string_function(old_setting_string_value.toStdString().c_str()); + if (old_setting_value.has_value()) + Accessor::setIntValue(widget, static_cast(static_cast(old_setting_value.value()))); - Accessor::connectValueChanged(widget, [hi, widget, settings_ptr](int) { - const int value = Accessor::getIntValue(widget); - (hi->GetCoreSettings().*settings_ptr) = static_cast(static_cast(value)); - hi->updateQSettings(); - }); -} - -template -void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr, - std::enable_if_t, int>* v = nullptr) -{ - using Accessor = SettingAccessor; - - Accessor::setIntValue(widget, static_cast(hi->GetCoreSettings().*settings_ptr)); - - Accessor::connectValueChanged(widget, [hi, widget, settings_ptr](int) { - const int value = Accessor::getIntValue(widget); - (hi->GetCoreSettings().*settings_ptr) = static_cast(value); - hi->updateQSettings(); + Accessor::connectValueChanged(widget, [hi, widget, setting_name, to_string_function]() { + const DataType value = static_cast(static_cast(Accessor::getIntValue(widget))); + const char* string_value = to_string_function(value); + hi->putSettingValue(setting_name, QString::fromLocal8Bit(string_value)); + hi->applySettings(); }); }