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
============ System Light + App Dark
============ System Dark + App Light
============ System Dark + App Dark
============
---
.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");