diff --git a/CMakeModules/CopyYuzuQt6Deps.cmake b/CMakeModules/CopyYuzuQt6Deps.cmake index 39f88cbc19..5ea8f74233 100644 --- a/CMakeModules/CopyYuzuQt6Deps.cmake +++ b/CMakeModules/CopyYuzuQt6Deps.cmake @@ -63,6 +63,4 @@ function(copy_yuzu_Qt6_deps target_dir) else() # Update for non-MSVC platforms if needed endif() - # Fixes dark mode being forced automatically even when light theme is set in app settings. - file(WRITE "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/qt.conf" "[Platforms]\nWindowsArguments = darkmode=0") endfunction(copy_yuzu_Qt6_deps) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9cf2f9b76c..38c0e4b3ce 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -96,6 +96,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include #include #include +#include #include #include #include @@ -172,6 +173,91 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/util/clickable_label.h" #include "yuzu/vk_device_info.h" +#ifdef _WIN32 +#include +#include +#include +#pragma comment(lib, "Dwmapi.lib") + +static inline void ApplyWindowsTitleBarDarkMode(HWND hwnd, bool enabled) { + if (!hwnd) + return; + BOOL val = enabled ? TRUE : FALSE; + // 20 = Win11/21H2+ + if (SUCCEEDED(DwmSetWindowAttribute(hwnd, 20, &val, sizeof(val)))) + return; + // 19 = pre-21H2 + DwmSetWindowAttribute(hwnd, 19, &val, sizeof(val)); +} + +static inline void ApplyDarkToTopLevel(QWidget* w, bool on) { + if (!w || !w->isWindow()) + return; + ApplyWindowsTitleBarDarkMode(reinterpret_cast(w->winId()), on); +} + +namespace { +struct TitlebarFilter final : QObject { + bool dark; + explicit TitlebarFilter(bool is_dark) : QObject(qApp), dark(is_dark) {} + + void setDark(bool is_dark) { + dark = is_dark; + } + + void onFocusChanged(QWidget*, QWidget* now) { + if (now) + ApplyDarkToTopLevel(now->window(), dark); + } + + bool eventFilter(QObject* obj, QEvent* ev) override { + if (auto* w = qobject_cast(obj)) { + switch (ev->type()) { + case QEvent::WinIdChange: + case QEvent::Show: + case QEvent::ShowToParent: + case QEvent::Polish: + case QEvent::WindowStateChange: + case QEvent::ZOrderChange: + ApplyDarkToTopLevel(w, dark); + break; + default: + break; + } + } + return QObject::eventFilter(obj, ev); + } +}; + +static TitlebarFilter* g_filter = nullptr; +static QMetaObject::Connection g_focusConn; + +} // namespace + +static void ApplyGlobalDarkTitlebar(bool is_dark) { + if (!g_filter) { + g_filter = new TitlebarFilter(is_dark); + qApp->installEventFilter(g_filter); + g_focusConn = QObject::connect(qApp, &QApplication::focusChanged, g_filter, + &TitlebarFilter::onFocusChanged); + } else { + g_filter->setDark(is_dark); + } + for (QWidget* w : QApplication::topLevelWidgets()) + ApplyDarkToTopLevel(w, is_dark); +} + +static void RemoveTitlebarFilter() { + if (!g_filter) + return; + qApp->removeEventFilter(g_filter); + QObject::disconnect(g_focusConn); + g_filter->deleteLater(); + g_filter = nullptr; +} + +#endif + #ifdef YUZU_CRASH_DUMPS #include "yuzu/breakpad.h" #endif @@ -299,16 +385,16 @@ static void OverrideWindowsFont() { } #endif -bool GMainWindow::CheckDarkMode() { -#ifdef __unix__ - const QPalette test_palette(qApp->palette()); - const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text); - const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); - return (text_color.value() > window_color.value()); +inline static bool isDarkMode() { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + const auto scheme = QGuiApplication::styleHints()->colorScheme(); + return scheme == Qt::ColorScheme::Dark; #else - // TODO: Windows - return false; -#endif // __unix__ + const QPalette defaultPalette; + const auto text = defaultPalette.color(QPalette::WindowText); + const auto window = defaultPalette.color(QPalette::Window); + return text.lightness() > window.lightness(); +#endif // QT_VERSION } GMainWindow::GMainWindow(bool has_broken_vulkan) @@ -358,7 +444,6 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) statusBar()->hide(); // Check dark mode before a theme is loaded - os_dark_mode = CheckDarkMode(); startup_icon_theme = QIcon::themeName(); // fallback can only be set once, colorful theme icons are okay on both light/dark QIcon::setFallbackThemeName(QStringLiteral("colorful")); @@ -5383,15 +5468,11 @@ void GMainWindow::UpdateUITheme() { current_theme = default_theme; } -#ifdef _WIN32 - QIcon::setThemeName(current_theme); - AdjustLinkColor(); -#else if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) { QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme : startup_icon_theme); QIcon::setThemeSearchPaths(QStringList(default_theme_paths)); - if (CheckDarkMode()) { + if (isDarkMode()) { current_theme = QStringLiteral("default_dark"); } } else { @@ -5399,7 +5480,7 @@ void GMainWindow::UpdateUITheme() { QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons"))); AdjustLinkColor(); } -#endif + if (current_theme != default_theme) { QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)}; QFile f(theme_uri); @@ -5422,6 +5503,11 @@ void GMainWindow::UpdateUITheme() { qApp->setStyleSheet({}); setStyleSheet({}); } + +#ifdef _WIN32 + RemoveTitlebarFilter(); + ApplyGlobalDarkTitlebar(UISettings::IsDarkTheme()); +#endif } void GMainWindow::LoadTranslation() { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e82a9ed335..5e0405ee7f 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -466,7 +466,6 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); - bool CheckDarkMode(); bool CheckFirmwarePresence(); void SetFirmwareVersion(); void ConfigureFilesystemProvider(const std::string& filepath); @@ -557,7 +556,6 @@ private: QTimer update_input_timer; QString startup_icon_theme; - bool os_dark_mode = false; // FS std::shared_ptr vfs;