From fdfed82675f852f108e0224a50d58a8b38d67a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 9 Apr 2025 20:48:10 +0200 Subject: [PATCH] Handle Windows theme changes in central theme listener Instead of having each window listen for theme changes we now have a single theme listener window, similar to the way we handle screen management. This reduces the amount of redundant callbacks to QWSI for theme changes somewhat, but Windows still emits several redundant events even for a single window. We want the theme change event to be synchronous, so there is no obvious way to debounce these events, besides the clearing of the message queue that we already do in this patch. Since we don't know exactly what part of the theme changed we can't bail out of the theme change event to QWSI based on the color scheme being the same, as the accent color or other parts of the theme might have changed. Change-Id: I827fa50effadf8a8e674a03ddc72958c60310f48 Reviewed-by: Oliver Wolff Reviewed-by: Zhao Yuhang <2546789017@qq.com> --- src/corelib/kernel/qcoreapplication_win.cpp | 7 +- .../platforms/windows/qwindowscontext.cpp | 13 +-- .../platforms/windows/qwindowscontext.h | 1 + .../platforms/windows/qwindowstheme.cpp | 89 ++++++++++++++----- src/plugins/platforms/windows/qwindowstheme.h | 8 +- 5 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/corelib/kernel/qcoreapplication_win.cpp b/src/corelib/kernel/qcoreapplication_win.cpp index 2cdf0d57d46..45d6e2e3a2e 100644 --- a/src/corelib/kernel/qcoreapplication_win.cpp +++ b/src/corelib/kernel/qcoreapplication_win.cpp @@ -178,7 +178,7 @@ static const char *findWMstr(uint msg) { 0x0014, "WM_ERASEBKGND" }, { 0x0015, "WM_SYSCOLORCHANGE" }, { 0x0018, "WM_SHOWWINDOW" }, - { 0x001A, "WM_WININICHANGE" }, + { 0x001A, "WM_SETTINGCHANGE" }, { 0x001B, "WM_DEVMODECHANGE" }, { 0x001C, "WM_ACTIVATEAPP" }, { 0x001D, "WM_FONTCHANGE" }, @@ -410,6 +410,7 @@ static const char *findWMstr(uint msg) { 0x0317, "WM_PRINT" }, { 0x0318, "WM_PRINTCLIENT" }, { 0x0319, "WM_APPCOMMAND" }, + { 0x0320, "WM_DWMCOLORIZATIONCOLORCHANGED" }, { 0x031A, "WM_THEMECHANGED" }, { 0x0358, "WM_HANDHELDFIRST" }, { 0x0359, "WM_HANDHELDFIRST + 1" }, @@ -804,6 +805,10 @@ QString decodeMSG(const MSG& msg) if (const char *logoffOption = sessionMgrLogOffOption(uint(wParam))) parameters += QLatin1StringView(logoffOption); break; + case WM_SETTINGCHANGE: + parameters = "wParam"_L1 + wParamS + " lParam("_L1 + + QString::fromWCharArray(reinterpret_cast(lParam)) + u')'; + break; default: parameters = "wParam"_L1 + wParamS + " lParam"_L1 + lParamS; break; diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 4c24b119929..3d4176f9f6a 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -75,6 +75,7 @@ Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility") Q_LOGGING_CATEGORY(lcQpaUiAutomation, "qt.qpa.uiautomation") Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon") Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen") +Q_LOGGING_CATEGORY(lcQpaTheme, "qt.qpa.theme") int QWindowsContext::verbose = 0; @@ -195,6 +196,9 @@ QWindowsContext::~QWindowsContext() if (d->m_powerDummyWindow) DestroyWindow(d->m_powerDummyWindow); + if (QWindowsTheme *theme = QWindowsTheme::instance()) + theme->destroyThemeChangeWindow(); + d->m_screenManager.destroyWindow(); unregisterWindowClasses(); @@ -1062,11 +1066,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, #endif case QtWindows::SettingChangedEvent: { QWindowsWindow::settingsChanged(); - // Only refresh the window theme if the user changes the personalize settings. - if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL. - && (wcscmp(reinterpret_cast(lParam), L"ImmersiveColorSet") == 0)) { - QWindowsTheme::handleSettingsChanged(); - } return d->m_screenManager.handleScreenChanges(); } default: @@ -1231,10 +1230,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, QWindowSystemInterface::handleCloseEvent(platformWindow->window()); return true; case QtWindows::ThemeChanged: { - QWindowsThemeCache::clearThemeCache(platformWindow->handle()); - // Switch from Aero to Classic changes margins. - if (QWindowsTheme *theme = QWindowsTheme::instance()) - theme->windowsThemeChanged(platformWindow->window()); return true; } case QtWindows::CompositionSettingsChanged: diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index 4caf16e2dd1..5cb905f5fb6 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -29,6 +29,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility) Q_DECLARE_LOGGING_CATEGORY(lcQpaUiAutomation) Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon) Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) +Q_DECLARE_LOGGING_CATEGORY(lcQpaTheme) class QWindow; class QPlatformScreen; diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index fd09fc5f00d..f35d9a81025 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -480,6 +481,39 @@ static inline QPalette *menuBarPalette(const QPalette &menuPalette, bool light) const char *QWindowsTheme::name = "windows"; QWindowsTheme *QWindowsTheme::m_instance = nullptr; +extern "C" LRESULT QT_WIN_CALLBACK qThemeChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_SETTINGCHANGE: + // Only refresh the theme if the user changes the personalize settings + if (!((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL. + && (wcscmp(reinterpret_cast(lParam), L"ImmersiveColorSet") == 0))) + break; + Q_FALLTHROUGH(); + case WM_THEMECHANGED: + case WM_SYSCOLORCHANGE: + case WM_DWMCOLORIZATIONCOLORCHANGED: + qCDebug(lcQpaTheme) << "Handling theme change due to" + << qUtf8Printable(decodeMSG(MSG{hwnd, message, wParam, lParam, 0, {0, 0}}).trimmed()); + QWindowsTheme::handleThemeChange(); + + MSG msg; // Clear the message queue, we've already reacted to the change + while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)); + + // FIXME: Despite clearing the message queue above, Windows will send + // us redundant theme change events for our single window. We want the + // theme change delivery to be synchronous, so we can't easily debounce + // them by peeking into the QWSI event queue, but perhaps there are other + // ways. + + break; + default: + break; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + QWindowsTheme::QWindowsTheme() { m_instance = this; @@ -489,6 +523,16 @@ QWindowsTheme::QWindowsTheme() std::fill(m_palettes, m_palettes + NPalettes, nullptr); refresh(); refreshIconPixmapSizes(); + + auto className = QWindowsContext::instance()->registerWindowClass( + QWindowsContext::classNamePrefix() + QLatin1String("ThemeChangeObserverWindow"), + qThemeChangeObserverWndProc); + // HWND_MESSAGE windows do not get the required theme events, + // so we use a real top-level window that we never show. + m_themeChangeObserver = CreateWindowEx(0, reinterpret_cast(className.utf16()), + nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + Q_ASSERT(m_themeChangeObserver); } QWindowsTheme::~QWindowsTheme() @@ -498,6 +542,13 @@ QWindowsTheme::~QWindowsTheme() m_instance = nullptr; } +void QWindowsTheme::destroyThemeChangeWindow() +{ + qCDebug(lcQpaTheme) << "Destroying theme change window"; + DestroyWindow(m_themeChangeObserver); + m_themeChangeObserver = nullptr; +} + static inline QStringList iconThemeSearchPaths() { const QFileInfo appDir(QCoreApplication::applicationDirPath() + "/icons"_L1); @@ -594,7 +645,7 @@ Qt::ColorScheme QWindowsTheme::effectiveColorScheme() void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme) { s_colorSchemeOverride = scheme; - handleSettingsChanged(); + handleThemeChange(); } Qt::ContrastPreference QWindowsTheme::contrastPreference() const @@ -603,23 +654,27 @@ Qt::ContrastPreference QWindowsTheme::contrastPreference() const : Qt::ContrastPreference::NoPreference; } -void QWindowsTheme::handleSettingsChanged() +void QWindowsTheme::handleThemeChange() { + QWindowsThemeCache::clearAllThemeCaches(); + const auto oldColorScheme = s_colorScheme; s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry - const auto newColorScheme = effectiveColorScheme(); - const bool colorSchemeChanged = newColorScheme != oldColorScheme; - s_colorScheme = newColorScheme; - if (!colorSchemeChanged) - return; - auto integration = QWindowsIntegration::instance(); - integration->updateApplicationBadge(); - if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) { - QWindowsTheme::instance()->refresh(); - QWindowSystemInterface::handleThemeChange(); + s_colorScheme = effectiveColorScheme(); + if (s_colorScheme != oldColorScheme) { + // Only propagate color scheme changes if the scheme actually changed + auto integration = QWindowsIntegration::instance(); + integration->updateApplicationBadge(); + + for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows())) + w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark); } - for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows())) - w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark); + + // But always reset palette and fonts, and signal the theme + // change, as other parts of the theme could have changed, + // such as the accent color. + QWindowsTheme::instance()->refresh(); + QWindowSystemInterface::handleThemeChange(); } void QWindowsTheme::clearPalettes() @@ -784,12 +839,6 @@ QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const } #endif -void QWindowsTheme::windowsThemeChanged(QWindow * window) -{ - refresh(); - QWindowSystemInterface::handleThemeChange(window); -} - static int fileIconSizes[FileIconSizeCount]; void QWindowsTheme::refreshIconPixmapSizes() diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index 5901b14dce2..63554d12887 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -35,7 +35,7 @@ public: void requestColorScheme(Qt::ColorScheme scheme) override; Qt::ContrastPreference contrastPreference() const override; - static void handleSettingsChanged(); + static void handleThemeChange(); const QPalette *palette(Palette type = SystemPalette) const override { return m_palettes[type]; } @@ -47,7 +47,6 @@ public: QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = {}) const override; QIconEngine *createIconEngine(const QString &iconName) const override; - void windowsThemeChanged(QWindow *window); void displayChanged() { refreshIconPixmapSizes(); } QList availableFileIconSizes() const { return m_fileIconSizes; } @@ -83,9 +82,14 @@ private: static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown; static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown; + friend class QWindowsContext; + QPalette *m_palettes[NPalettes]; QFont *m_fonts[NFonts]; QList m_fileIconSizes; + + HWND m_themeChangeObserver = nullptr; + void destroyThemeChangeWindow(); }; QT_END_NAMESPACE