From 60b841936650f17f5395defb2455c1e36bcacf21 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:47:19 +0800 Subject: [PATCH] Runner TrayIcon: Monochrome icon should adapt to windows theme instead of the app theme (#44931) ## Summary of the Pull Request As title ## PR Checklist - [X] Closes: #44891 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed ============ System light + App Light image ============ System Light + App Dark image ============ System Dark + App Light image ============ System Dark + App Dark image ============ --- .github/actions/spell-check/expect.txt | 5 +- src/common/Themes/theme_listener.cpp | 87 ++++++++++++++++++++++++-- src/common/Themes/theme_listener.h | 10 +++ src/runner/tray_icon.cpp | 9 +-- 4 files changed, 97 insertions(+), 14 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ffb0654c40..79bf8cfcea 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -103,7 +103,6 @@ ASYNCWINDOWPLACEMENT ASYNCWINDOWPOS atl ATRIOX -ATX aumid authenticode AUTOBUDDY @@ -298,7 +297,6 @@ cpcontrols cph cplusplus CPower -cppcoreguidelines cpptools cppvsdbg cppwinrt @@ -327,7 +325,7 @@ CURRENTDIR CURSORINFO cursorpos CURSORSHOWING -CURSORWRAP +cursorwrap customaction CUSTOMACTIONTEST CUSTOMFORMATPLACEHOLDER @@ -1760,7 +1758,6 @@ SUBMODULEUPDATE subresource Superbar sut -swe svchost SVGIn SVGIO diff --git a/src/common/Themes/theme_listener.cpp b/src/common/Themes/theme_listener.cpp index 3a31b0db3c..972953a53c 100644 --- a/src/common/Themes/theme_listener.cpp +++ b/src/common/Themes/theme_listener.cpp @@ -16,13 +16,50 @@ DWORD WINAPI _checkTheme(LPVOID lpParam) void ThemeListener::AddChangedHandler(THEME_HANDLE handle) { + std::lock_guard lock(handlesMutex); handles.push_back(handle); } void ThemeListener::DelChangedHandler(THEME_HANDLE handle) { + std::lock_guard lock(handlesMutex); auto it = std::find(handles.begin(), handles.end(), handle); - handles.erase(it); + if (it != handles.end()) + { + handles.erase(it); + } +} + +void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle) +{ + std::lock_guard lock(handlesMutex); + appThemeHandles.push_back(handle); +} + +void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle) +{ + std::lock_guard lock(handlesMutex); + auto it = std::find(appThemeHandles.begin(), appThemeHandles.end(), handle); + if (it != appThemeHandles.end()) + { + appThemeHandles.erase(it); + } +} + +void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle) +{ + std::lock_guard lock(handlesMutex); + systemThemeHandles.push_back(handle); +} + +void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle) +{ + std::lock_guard lock(handlesMutex); + auto it = std::find(systemThemeHandles.begin(), systemThemeHandles.end(), handle); + if (it != systemThemeHandles.end()) + { + systemThemeHandles.erase(it); + } } void ThemeListener::CheckTheme() @@ -48,13 +85,51 @@ void ThemeListener::CheckTheme() WaitForSingleObject(hEvent, INFINITE); - auto _theme = ThemeHelpers::GetAppTheme(); - if (AppTheme != _theme) + auto _appTheme = ThemeHelpers::GetAppTheme(); + auto _systemTheme = ThemeHelpers::GetSystemTheme(); + + bool appThemeChanged = (AppTheme != _appTheme); + bool systemThemeChanged = (SystemTheme != _systemTheme); + + if (appThemeChanged || systemThemeChanged) { - AppTheme = _theme; - for (int i = 0; i < handles.size(); i++) + AppTheme = _appTheme; + SystemTheme = _systemTheme; + + // Copy handlers under lock, then invoke outside lock to avoid deadlock + std::vector handlesCopy; + std::vector appThemeHandlesCopy; + std::vector systemThemeHandlesCopy; + { - handles[i](); + std::lock_guard lock(handlesMutex); + handlesCopy = handles; + if (appThemeChanged) + { + appThemeHandlesCopy = appThemeHandles; + } + if (systemThemeChanged) + { + systemThemeHandlesCopy = systemThemeHandles; + } + } + + // Call generic handlers (backward compatible) + for (const auto& handler : handlesCopy) + { + handler(); + } + + // Call app theme specific handlers + for (const auto& handler : appThemeHandlesCopy) + { + handler(); + } + + // Call system theme specific handlers + for (const auto& handler : systemThemeHandlesCopy) + { + handler(); } } } diff --git a/src/common/Themes/theme_listener.h b/src/common/Themes/theme_listener.h index a6ab4464fc..6937f60dd2 100644 --- a/src/common/Themes/theme_listener.h +++ b/src/common/Themes/theme_listener.h @@ -3,6 +3,7 @@ #include #include #include +#include typedef void (*THEME_HANDLE)(); DWORD WINAPI _checkTheme(LPVOID lpParam); @@ -14,6 +15,7 @@ public: ThemeListener() { AppTheme = ThemeHelpers::GetAppTheme(); + SystemTheme = ThemeHelpers::GetSystemTheme(); dwThreadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_checkTheme, this, 0, &dwThreadId); } ~ThemeListener() @@ -23,12 +25,20 @@ public: } Theme AppTheme; + Theme SystemTheme; void ThemeListener::AddChangedHandler(THEME_HANDLE handle); void ThemeListener::DelChangedHandler(THEME_HANDLE handle); + void ThemeListener::AddAppThemeChangedHandler(THEME_HANDLE handle); + void ThemeListener::DelAppThemeChangedHandler(THEME_HANDLE handle); + void ThemeListener::AddSystemThemeChangedHandler(THEME_HANDLE handle); + void ThemeListener::DelSystemThemeChangedHandler(THEME_HANDLE handle); void CheckTheme(); private: HANDLE dwThreadHandle; DWORD dwThreadId; std::vector handles; + std::vector appThemeHandles; + std::vector systemThemeHandles; + mutable std::mutex handlesMutex; }; \ No newline at end of file diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp index 633c2c1b42..8fa892e312 100644 --- a/src/runner/tray_icon.cpp +++ b/src/runner/tray_icon.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "bug_report.h" namespace @@ -293,7 +294,7 @@ static void handle_theme_change() { if (theme_adaptive_enabled) { - tray_icon_data.hIcon = get_icon(theme_listener.AppTheme); + tray_icon_data.hIcon = get_icon(ThemeHelpers::GetSystemTheme()); Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data); } } @@ -310,7 +311,7 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive) { theme_adaptive_enabled = theme_adaptive; auto h_instance = reinterpret_cast(&__ImageBase); - HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON)); + HICON const icon = theme_adaptive ? get_icon(ThemeHelpers::GetSystemTheme()) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON)); if (icon) { UINT id_tray_icon = 1; @@ -357,7 +358,7 @@ void start_tray_icon(bool isProcessElevated, bool theme_adaptive) ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr); tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE; - theme_listener.AddChangedHandler(&handle_theme_change); + theme_listener.AddSystemThemeChangedHandler(&handle_theme_change); // Register callback to update bug report menu item status BugReportManager::instance().register_callback([](bool isRunning) { @@ -389,7 +390,7 @@ void set_tray_icon_theme_adaptive(bool theme_adaptive) if (theme_adaptive) { - icon = get_icon(theme_listener.AppTheme); + icon = get_icon(ThemeHelpers::GetSystemTheme()); if (!icon) { Logger::warn(L"set_tray_icon_theme_adaptive: Failed to load theme adaptive icon, falling back to default");