From f1e66ee0c7fc2ed395d6bea6c2032df59eed39d9 Mon Sep 17 00:00:00 2001 From: flodavid Date: Mon, 12 Feb 2024 04:01:25 +0100 Subject: [PATCH] Add dark mode configuration setting in UI tab - Allows to choose "Auto", "Always On" or "Always Off". - If Auto is chosen, value is retrieved from OS - On Windows, the application needs a restart to apply the settings - On Windows, "Always On" is not available, the OS is responsible for selecting the dark palette --- src/common/settings.cpp | 1 + src/common/settings.h | 1 + src/common/settings_enums.h | 2 + src/yuzu/configuration/configure_ui.cpp | 18 ++++ src/yuzu/configuration/configure_ui.ui | 14 +++ src/yuzu/configuration/qt_config.cpp | 10 +- src/yuzu/main.cpp | 130 +++++++++++++----------- src/yuzu/startup_checks.cpp | 4 - src/yuzu/uisettings.h | 3 + 9 files changed, 119 insertions(+), 64 deletions(-) diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 80d388fe88..de0d9410f8 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -67,6 +67,7 @@ SWITCHABLE(u8, true); // Used in UISettings // TODO see if we can move this to uisettings.cpp SWITCHABLE(ConfirmStop, true); +SWITCHABLE(DarkModeState, true); #undef SETTING #undef SWITCHABLE diff --git a/src/common/settings.h b/src/common/settings.h index aa054dc24c..55945f5171 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -88,6 +88,7 @@ SWITCHABLE(u8, true); // Used in UISettings // TODO see if we can move this to uisettings.h SWITCHABLE(ConfirmStop, true); +SWITCHABLE(DarkModeState, true); #undef SETTING #undef SWITCHABLE diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index f42367e67e..d609a42f98 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -153,6 +153,8 @@ ENUM(ConsoleMode, Handheld, Docked); ENUM(AppletMode, HLE, LLE); +ENUM(DarkModeState, Off, On, Auto); + template inline std::string CanonicalizeEnum(Type id) { const auto group = EnumMetadata::Canonicalizations(); diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 9d99cb672c..207308b860 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "common/common_types.h" #include "common/fs/path_util.h" @@ -29,6 +30,8 @@ #include "ui_configure_ui.h" #include "yuzu/uisettings.h" +using Settings::DarkModeState; + namespace { constexpr std::array default_game_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), @@ -131,6 +134,17 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) ui->theme_combobox->addItem(theme_name, theme_dir); } + ui->dark_mode_combobox->addItem(tr("Auto"), QVariant::fromValue(DarkModeState::Auto)); +// Windows dark mode is based on palette swap made by OS, so the "Always On" option disabled. +// We could check if the dark mode state is "On" and force a dark palette like on Linux to support +// "Always On" +#ifdef _WIN32 + ui->dark_mode_label->setText(tr("Dark mode (needs restart)")); +#else + ui->dark_mode_combobox->addItem(tr("Always On"), QVariant::fromValue(DarkModeState::On)); +#endif + ui->dark_mode_combobox->addItem(tr("Always Off"), QVariant::fromValue(DarkModeState::Off)); + InitializeIconSizeComboBox(); InitializeRowComboBoxes(); @@ -185,6 +199,8 @@ ConfigureUi::~ConfigureUi() = default; void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); + UISettings::values.dark_mode_state = + static_cast(ui->dark_mode_combobox->currentData().toUInt()); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); UISettings::values.show_compat = ui->show_compat->isChecked(); UISettings::values.show_size = ui->show_size->isChecked(); @@ -212,6 +228,8 @@ void ConfigureUi::RequestGameListUpdate() { void ConfigureUi::SetConfiguration() { ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->dark_mode_combobox->setCurrentIndex( + ui->dark_mode_combobox->findData(QVariant::fromValue(UISettings::values.dark_mode_state))); ui->language_combobox->setCurrentIndex(ui->language_combobox->findData( QString::fromStdString(UISettings::values.language.GetValue()))); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index b8e6483814..cdd46005fd 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -63,6 +63,20 @@ + + + + + + Dark Mode: + + + + + + + + diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp index a52a6ef544..082934923f 100644 --- a/src/yuzu/configuration/qt_config.cpp +++ b/src/yuzu/configuration/qt_config.cpp @@ -259,8 +259,10 @@ void QtConfig::ReadShortcutValues() { void QtConfig::ReadUIValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Ui)); - UISettings::values.theme = QString::fromStdString( - ReadStringSetting(std::string("theme"), std::string(UISettings::default_theme))); + UISettings::values.theme = + QString::fromStdString(ReadStringSetting("theme", std::string(UISettings::default_theme))); + UISettings::values.dark_mode_state = static_cast( + ReadIntegerSetting("dark_mode_state", static_cast(DarkModeState::Auto))); ReadUIGamelistValues(); ReadUILayoutValues(); @@ -466,8 +468,10 @@ void QtConfig::SaveUIValues() { WriteCategory(Settings::Category::Ui); WriteCategory(Settings::Category::UiGeneral); - WriteStringSetting(std::string("theme"), UISettings::values.theme.toStdString(), + WriteStringSetting("theme", UISettings::values.theme.toStdString(), std::make_optional(std::string(UISettings::default_theme))); + WriteIntegerSetting("dark_mode_state", static_cast(UISettings::values.dark_mode_state), + std::make_optional(static_cast(DarkModeState::Auto))); SaveUIGamelistValues(); SaveUILayoutValues(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 142143727b..ed8abbddbd 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3721,6 +3721,7 @@ void GMainWindow::ResetWindowSize1080() { void GMainWindow::OnConfigure() { const QString old_theme = UISettings::values.theme; + DarkModeState old_dark_mode_state = UISettings::values.dark_mode_state; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); const auto old_language_index = Settings::values.language_index.GetValue(); #ifdef __unix__ @@ -3781,7 +3782,8 @@ void GMainWindow::OnConfigure() { } InitializeHotkeys(); - if (UISettings::values.theme != old_theme) { + if (UISettings::values.theme != old_theme || + UISettings::values.dark_mode_state != old_dark_mode_state) { UpdateUITheme(); } if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { @@ -4795,13 +4797,11 @@ void GMainWindow::filterBarSetChecked(bool state) { } void GMainWindow::UpdateUITheme() { - LOG_DEBUG(Frontend, "Updating UI"); - QString default_theme = QString::fromStdString(UISettings::default_theme.data()); + const QString default_theme = QString::fromStdString(UISettings::default_theme.data()); QString current_theme = UISettings::values.theme; if (current_theme.isEmpty()) { current_theme = default_theme; } - const bool current_dark_mode = CheckDarkMode(); UpdateIcons(current_theme); @@ -4873,7 +4873,7 @@ bool GMainWindow::TryLoadStylesheet(const QString& theme_uri) { style_path = theme_uri + QStringLiteral("light.qss"); } if (!QFile::exists(style_path)) { - LOG_INFO(Frontend, "Themed (light/dark) stylesheet could not be found, using default one"); + LOG_DEBUG(Frontend, "No themed (light/dark) stylesheet, using default one"); // Use common stylesheet if themed one does not exist style_path = theme_uri + QStringLiteral("style.qss"); } @@ -4884,7 +4884,7 @@ bool GMainWindow::TryLoadStylesheet(const QString& theme_uri) { // Update the color palette before applying the stylesheet UpdateThemePalette(); - LOG_INFO(Frontend, "Loading stylesheet in: {}", theme_uri.toStdString()); + LOG_DEBUG(Frontend, "Loading stylesheet in: {}", theme_uri.toStdString()); QTextStream ts_theme(&style_file); qApp->setStyleSheet(ts_theme.readAll()); setStyleSheet(ts_theme.readAll()); @@ -4947,7 +4947,6 @@ void GMainWindow::UpdateThemePalette() { #else if (CheckDarkMode()) { // Set Dark palette on non Windows platforms (that may not have a dark palette) - LOG_INFO(Frontend, "Using custom dark palette"); themePalette.setColor(QPalette::Window, QColor(53, 53, 53)); themePalette.setColor(QPalette::WindowText, Qt::white); themePalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); @@ -4969,7 +4968,6 @@ void GMainWindow::UpdateThemePalette() { themePalette.setColor(QPalette::HighlightedText, Qt::white); themePalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127)); } else { - LOG_INFO(Frontend, "Using standard palette"); // Reset light palette on non Windows platforms themePalette = this->style()->standardPalette(); } @@ -5031,61 +5029,72 @@ bool GMainWindow::ListenColorSchemeChange() { #endif bool GMainWindow::CheckDarkMode() { - const QPalette current_palette(qApp->palette()); + bool is_dark_mode_auto; +#ifdef _WIN32 + // Dark mode cannot be changed after the app started on Windows + is_dark_mode_auto = qgetenv("QT_QPA_PLATFORM").contains("darkmode=2"); +#else + is_dark_mode_auto = UISettings::values.dark_mode_state == DarkModeState::Auto; +#endif + if (!is_dark_mode_auto) { + return UISettings::values.dark_mode_state == DarkModeState::On; + } else { + const QPalette current_palette(qApp->palette()); #ifdef __unix__ - QProcess process; - QStringList gdbus_arguments; + QProcess process; - // Using the freedesktop specifications for checking dark mode - LOG_INFO(Frontend, "Retrieving theme from freedesktop color-scheme..."); - gdbus_arguments << QStringLiteral("--dest=org.freedesktop.portal.Desktop") - << QStringLiteral("--object-path /org/freedesktop/portal/desktop") - << QStringLiteral("--method org.freedesktop.portal.Settings.Read") - << QStringLiteral("org.freedesktop.appearance color-scheme"); - process.start(QStringLiteral("gdbus call --session"), gdbus_arguments); - process.waitForFinished(1000); - QByteArray dbus_output = process.readAllStandardOutput(); + // Using the freedesktop specifications for checking dark mode + LOG_DEBUG(Frontend, "Retrieving theme from freedesktop color-scheme..."); + QStringList gdbus_arguments; + gdbus_arguments << QStringLiteral("--dest=org.freedesktop.portal.Desktop") + << QStringLiteral("--object-path /org/freedesktop/portal/desktop") + << QStringLiteral("--method org.freedesktop.portal.Settings.Read") + << QStringLiteral("org.freedesktop.appearance color-scheme"); + process.start(QStringLiteral("gdbus call --session"), gdbus_arguments); + process.waitForFinished(1000); + QByteArray dbus_output = process.readAllStandardOutput(); - if (!dbus_output.isEmpty()) { - const int systemColorSchema = QString::fromUtf8(dbus_output).trimmed().right(1).toInt(); - return systemColorSchema == 1; - } + if (!dbus_output.isEmpty()) { + const int systemColorSchema = QString::fromUtf8(dbus_output).trimmed().right(1).toInt(); + return systemColorSchema == 1; + } - // Try alternative for Gnome if the previous one failed - QStringList gsettings_arguments; - gsettings_arguments << QStringLiteral("get") - << QStringLiteral("org.gnome.desktop.interface") - << QStringLiteral("color-scheme"); - - LOG_DEBUG(Frontend, "failed, retrieving theme from gsettings color-scheme..."); - process.start(QStringLiteral("gsettings"), gsettings_arguments); - process.waitForFinished(1000); - QByteArray gsettings_output = process.readAllStandardOutput(); - - // Try older gtk-theme method if the previous one failed - if (gsettings_output.isEmpty()) { - LOG_INFO(Frontend, "failed, retrieving theme from gtk-theme..."); - gsettings_arguments.takeLast(); - gsettings_arguments << QStringLiteral("gtk-theme"); + // Try alternative for Gnome if the previous one failed + QStringList gsettings_arguments; + gsettings_arguments << QStringLiteral("get") + << QStringLiteral("org.gnome.desktop.interface") + << QStringLiteral("color-scheme"); + LOG_DEBUG(Frontend, "failed, retrieving theme from gsettings color-scheme..."); process.start(QStringLiteral("gsettings"), gsettings_arguments); process.waitForFinished(1000); - gsettings_output = process.readAllStandardOutput(); - } + QByteArray gsettings_output = process.readAllStandardOutput(); - // Interpret gsettings value if it succeeded - if (!gsettings_output.isEmpty()) { - QString systeme_theme = QString::fromUtf8(gsettings_output); - LOG_DEBUG(Frontend, "Gsettings output: {}", systeme_theme.toStdString()); - return systeme_theme.contains(QStringLiteral("dark"), Qt::CaseInsensitive); - } - LOG_DEBUG(Frontend, "failed, retrieving theme from palette"); + // Try older gtk-theme method if the previous one failed + if (gsettings_output.isEmpty()) { + LOG_DEBUG(Frontend, "failed, retrieving theme from gtk-theme..."); + gsettings_arguments.takeLast(); + gsettings_arguments << QStringLiteral("gtk-theme"); + + process.start(QStringLiteral("gsettings"), gsettings_arguments); + process.waitForFinished(1000); + gsettings_output = process.readAllStandardOutput(); + } + + // Interpret gsettings value if it succeeded + if (!gsettings_output.isEmpty()) { + QString systeme_theme = QString::fromUtf8(gsettings_output); + LOG_DEBUG(Frontend, "Gsettings output: {}", systeme_theme.toStdString()); + return systeme_theme.contains(QStringLiteral("dark"), Qt::CaseInsensitive); + } + LOG_DEBUG(Frontend, "failed, retrieving theme from palette"); #endif - // Use default method based on palette swap by OS. - // It is the only method on Windows with Qt 5. - // Windows needs QT_QPA_PLATFORM env variable set to windows:darkmode=2 to force palette change - return (current_palette.color(QPalette::WindowText).lightness() > - current_palette.color(QPalette::Window).lightness()); + // Use default method based on palette swap by OS. It is the only method on Windows with + // Qt 5. Windows needs QT_QPA_PLATFORM env variable set to windows:darkmode=2 to force + // palette change + return (current_palette.color(QPalette::WindowText).lightness() > + current_palette.color(QPalette::Window).lightness()); + } } void GMainWindow::changeEvent(QEvent* event) { @@ -5093,9 +5102,9 @@ void GMainWindow::changeEvent(QEvent* event) { // UpdateUITheme is a decent work around if (event->type() == QEvent::PaletteChange || event->type() == QEvent::ApplicationPaletteChange) { - LOG_INFO(Frontend, - "Window color palette changed by event: {} (QEvent::PaletteChange is: {})", - event->type(), QEvent::PaletteChange); + LOG_DEBUG(Frontend, + "Window color palette changed by event: {} (QEvent::PaletteChange is: {})", + event->type(), QEvent::PaletteChange); const QPalette test_palette(qApp->palette()); // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); @@ -5285,6 +5294,13 @@ int main(int argc, char* argv[]) { QCoreApplication::setApplicationName(QStringLiteral("yuzu")); #ifdef _WIN32 + QByteArray current_qt_qpa = qgetenv("QT_QPA_PLATFORM"); + // Follow dark mode setting, if the "-platform" launch option is not set + if (UISettings::values.dark_mode_state == DarkModeState::Auto && current_qt_qpa.isEmpty()) { + // When setting is Auto, force adapting window decoration and stylesheet palette to use + // Windows theme. Default is darkmode:0, which always uses light palette + qputenv("QT_QPA_PLATFORM", QByteArray("windows:darkmode=2")); + } // Increases the maximum open file limit to 8192 _setmaxstdio(8192); #endif diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 56f7b59c6a..9ae3a9fe3e 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -6,7 +6,6 @@ #ifdef _WIN32 #include #include -#include #include #include #elif defined(YUZU_UNIX) @@ -38,9 +37,6 @@ void CheckVulkan() { bool CheckEnvVars(bool* is_child) { #ifdef _WIN32 - // Force adapting theme to follow Windows dark mode - qputenv("QT_QPA_PLATFORM", QByteArray("windows:darkmode=2")); - // Check environment variable to see if we are the child char variable_contents[8]; const DWORD startup_check_var = diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 0dc211c31a..cbcf898183 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -18,6 +18,7 @@ using Settings::Category; using Settings::ConfirmStop; +using Settings::DarkModeState; using Settings::Setting; using Settings::SwitchableSetting; @@ -144,6 +145,7 @@ struct Values { Setting language{linkage, {}, "language", Category::Paths}; QString theme; + DarkModeState dark_mode_state; // Shortcut name std::vector shortcuts; @@ -260,3 +262,4 @@ Q_DECLARE_METATYPE(Settings::RendererBackend); Q_DECLARE_METATYPE(Settings::ShaderBackend); Q_DECLARE_METATYPE(Settings::AstcRecompression); Q_DECLARE_METATYPE(Settings::AstcDecodeMode); +Q_DECLARE_METATYPE(Settings::DarkModeState);