From 082ba75ec727a14caacd4c421dab6244dfbe19b2 Mon Sep 17 00:00:00 2001 From: HO-COOH Date: Thu, 8 Jan 2026 14:23:59 +0800 Subject: [PATCH] Feat: New tray-icon that adapts to theme change (#33321) ## Summary of the Pull Request This idea comes from @Shomnipotence. It replaces the old tray icon with a new outlined design and adapts to windows' theme changes. ## PR Checklist - [ ] **Closes:** #xxx - [ ] **Communication:** I've discussed this with core contributors already. If 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 This should be obvious enough with the video https://github.com/microsoft/PowerToys/assets/42881734/fb8f44c3-5dc0-452a-98c1-636d20b60635 ## Validation Steps Performed 1. Start powertoys, it loads the tray icon of the current theme. 2. Switch theme in windows settings, it dynamically adapts. --------- Co-authored-by: Niels Laute Co-authored-by: Shawn Yuan (from Dev Box) --- .github/actions/spell-check/expect.txt | 3 ++ src/runner/general_settings.cpp | 16 ++++++- src/runner/general_settings.h | 1 + src/runner/main.cpp | 13 ++++-- src/runner/quick_access_host.cpp | 29 +++++++++++- src/runner/runner.vcxproj | 22 +++++++++ src/runner/runner.vcxproj.filters | 2 + src/runner/svgs/PowerToysDark.ico | Bin 0 -> 37916 bytes src/runner/svgs/PowerToysWhite.ico | Bin 0 -> 37822 bytes src/runner/tray_icon.cpp | 43 +++++++++++++++++- src/runner/tray_icon.h | 4 +- .../Settings.UI.Library/GeneralSettings.cs | 4 ++ .../SettingsXAML/Views/GeneralPage.xaml | 12 ++++- .../Settings.UI/Strings/en-us/Resources.resw | 3 ++ .../ViewModels/GeneralViewModel.cs | 20 ++++++++ 15 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 src/runner/svgs/PowerToysDark.ico create mode 100644 src/runner/svgs/PowerToysWhite.ico diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 6cb41322e4..d9e5f7e254 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -364,6 +364,7 @@ DEFAULTFLAGS DEFAULTICON defaultlib DEFAULTONLY +DEFAULTSIZE DEFAULTTONEAREST Defaulttonearest DEFAULTTONULL @@ -830,9 +831,11 @@ ITHUMBNAIL IUI IUWP IWIC +jeli jfif jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi jjw +JOBOBJECT jobject jpe jpnime diff --git a/src/runner/general_settings.cpp b/src/runner/general_settings.cpp index 2ae775a0fc..5024e39753 100644 --- a/src/runner/general_settings.cpp +++ b/src/runner/general_settings.cpp @@ -67,6 +67,7 @@ namespace // TODO: would be nice to get rid of these globals, since they're basically cached json settings static std::wstring settings_theme = L"system"; static bool show_tray_icon = true; +static bool show_theme_adaptive_tray_icon = false; static bool run_as_elevated = false; static bool show_new_updates_toast_notification = true; static bool download_updates_automatically = true; @@ -99,6 +100,7 @@ json::JsonObject GeneralSettings::to_json() result.SetNamedValue(L"enabled", std::move(enabled)); result.SetNamedValue(L"show_tray_icon", json::value(showSystemTrayIcon)); + result.SetNamedValue(L"show_theme_adaptive_tray_icon", json::value(showThemeAdaptiveTrayIcon)); result.SetNamedValue(L"is_elevated", json::value(isElevated)); result.SetNamedValue(L"run_elevated", json::value(isRunElevated)); result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification)); @@ -126,6 +128,8 @@ json::JsonObject load_general_settings() { settings_theme = L"system"; } + show_tray_icon = loaded.GetNamedBoolean(L"show_tray_icon", true); + show_theme_adaptive_tray_icon = loaded.GetNamedBoolean(L"show_theme_adaptive_tray_icon", false); run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false); show_new_updates_toast_notification = loaded.GetNamedBoolean(L"show_new_updates_toast_notification", true); download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true) && check_user_is_admin(); @@ -159,6 +163,7 @@ GeneralSettings get_general_settings() GeneralSettings settings { .showSystemTrayIcon = show_tray_icon, + .showThemeAdaptiveTrayIcon = show_theme_adaptive_tray_icon, .isElevated = is_process_elevated(), .isRunElevated = run_as_elevated, .isAdmin = is_user_admin, @@ -356,10 +361,19 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save) if (json::has(general_configs, L"show_tray_icon", json::JsonValueType::Boolean)) { show_tray_icon = general_configs.GetNamedBoolean(L"show_tray_icon"); - // Update tray icon visibility when setting is toggled set_tray_icon_visible(show_tray_icon); } + if (json::has(general_configs, L"show_theme_adaptive_tray_icon", json::JsonValueType::Boolean)) + { + bool new_theme_adaptive = general_configs.GetNamedBoolean(L"show_theme_adaptive_tray_icon"); + if (show_theme_adaptive_tray_icon != new_theme_adaptive) + { + show_theme_adaptive_tray_icon = new_theme_adaptive; + set_tray_icon_theme_adaptive(show_theme_adaptive_tray_icon); + } + } + if (json::has(general_configs, L"ignored_conflict_properties", json::JsonValueType::Object)) { ignored_conflict_properties = general_configs.GetNamedObject(L"ignored_conflict_properties"); diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h index 033f75b087..ac93a1fdfd 100644 --- a/src/runner/general_settings.h +++ b/src/runner/general_settings.h @@ -13,6 +13,7 @@ struct GeneralSettings { bool isStartupEnabled; bool showSystemTrayIcon; + bool showThemeAdaptiveTrayIcon; std::wstring startupDisabledReason; std::map isModulesEnabledMap; bool isElevated; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index d9da20d93c..d8fdcbdb04 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -189,13 +189,18 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow //init_global_error_handlers(); #endif Trace::RegisterProvider(); - start_tray_icon(isProcessElevated); - if (get_general_settings().enableQuickAccess) + + // Load settings from file before reading them + load_general_settings(); + auto const settings = get_general_settings(); + start_tray_icon(isProcessElevated, settings.showThemeAdaptiveTrayIcon); + + if (settings.enableQuickAccess) { QuickAccessHost::start(); } - update_quick_access_hotkey(get_general_settings().enableQuickAccess, get_general_settings().quickAccessShortcut); - set_tray_icon_visible(get_general_settings().showSystemTrayIcon); + update_quick_access_hotkey(settings.enableQuickAccess, settings.quickAccessShortcut); + set_tray_icon_visible(settings.showSystemTrayIcon); CentralizedKeyboardHook::Start(); int result = -1; diff --git a/src/runner/quick_access_host.cpp b/src/runner/quick_access_host.cpp index 609b8f2f36..b546ee244e 100644 --- a/src/runner/quick_access_host.cpp +++ b/src/runner/quick_access_host.cpp @@ -18,6 +18,7 @@ extern void receive_json_send_to_main_thread(const std::wstring& msg); namespace { wil::unique_handle quick_access_process; + wil::unique_handle quick_access_job; wil::unique_handle show_event; wil::unique_handle exit_event; std::wstring show_event_name; @@ -53,6 +54,7 @@ namespace } quick_access_process.reset(); + quick_access_job.reset(); show_event.reset(); exit_event.reset(); show_event_name.clear(); @@ -206,7 +208,7 @@ namespace QuickAccessHost startup_info.cb = sizeof(startup_info); PROCESS_INFORMATION process_info{}; - BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup_info, &process_info); + BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &startup_info, &process_info); if (!created) { Logger::error(L"QuickAccessHost: failed to launch Quick Access host. error={}.", GetLastError()); @@ -215,6 +217,31 @@ namespace QuickAccessHost } quick_access_process.reset(process_info.hProcess); + + // Assign to job object to ensure the process is killed if the runner exits unexpectedly (e.g. debugging stop) + quick_access_job.reset(CreateJobObjectW(nullptr, nullptr)); + if (quick_access_job) + { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; + jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (!SetInformationJobObject(quick_access_job.get(), JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) + { + Logger::warn(L"QuickAccessHost: failed to set job object information. error={}", GetLastError()); + } + else + { + if (!AssignProcessToJobObject(quick_access_job.get(), quick_access_process.get())) + { + Logger::warn(L"QuickAccessHost: failed to assign process to job object. error={}", GetLastError()); + } + } + } + else + { + Logger::warn(L"QuickAccessHost: failed to create job object. error={}", GetLastError()); + } + + ResumeThread(process_info.hThread); CloseHandle(process_info.hThread); } diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 1bfd036290..23cc1c9d9f 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -141,6 +141,28 @@ + + + true + true + true + true + $(OutDir)\svgs + $(OutDir)\svgs + $(OutDir)\svgs + $(OutDir)\svgs + + + true + true + true + true + $(OutDir)\svgs + $(OutDir)\svgs + $(OutDir)\svgs + $(OutDir)\svgs + + diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters index ac5fe3ec36..4d1e82ff0d 100644 --- a/src/runner/runner.vcxproj.filters +++ b/src/runner/runner.vcxproj.filters @@ -122,6 +122,8 @@ + + diff --git a/src/runner/svgs/PowerToysDark.ico b/src/runner/svgs/PowerToysDark.ico new file mode 100644 index 0000000000000000000000000000000000000000..313a3d01ec2c67f29a7b952ac0805d36fb1f7ef4 GIT binary patch literal 37916 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 literal 0 HcmV?d00001 diff --git a/src/runner/svgs/PowerToysWhite.ico b/src/runner/svgs/PowerToysWhite.ico new file mode 100644 index 0000000000000000000000000000000000000000..9e55ac8794145e3b5b5872b6ae7f71e4b144c2ba GIT binary patch literal 37822 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 xtMP;F90ia=P$yxrmcmwqE$TxL&RA_ #include #include +#include #include "bug_report.h" namespace @@ -39,6 +40,8 @@ namespace bool double_clicked = false; POINT tray_icon_click_point; + static ThemeListener theme_listener; + static bool theme_adaptive_enabled = false; } // Struct to fill with callback and the data. The window_proc is responsible for cleaning it. @@ -266,6 +269,28 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam return DefWindowProc(window, message, wparam, lparam); } +static HICON get_icon(Theme theme) +{ + std::wstring icon_path = get_module_folderpath(); + icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico"; + return static_cast(LoadImage(NULL, + icon_path.c_str(), + IMAGE_ICON, + 0, + 0, + LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED)); +} + + +static void handle_theme_change() +{ + if (theme_adaptive_enabled) + { + tray_icon_data.hIcon = get_icon(theme_listener.AppTheme); + Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data); + } +} + void update_bug_report_menu_status(bool isRunning) { if (h_sub_menu != nullptr) @@ -274,10 +299,11 @@ void update_bug_report_menu_status(bool isRunning) } } -void start_tray_icon(bool isProcessElevated) +void start_tray_icon(bool isProcessElevated, bool theme_adaptive) { + theme_adaptive_enabled = theme_adaptive; auto h_instance = reinterpret_cast(&__ImageBase); - auto icon = LoadIcon(h_instance, MAKEINTRESOURCE(APPICON)); + HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON)); if (icon) { UINT id_tray_icon = 1; @@ -324,6 +350,7 @@ void start_tray_icon(bool isProcessElevated) ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr); tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE; + theme_listener.AddChangedHandler(&handle_theme_change); // Register callback to update bug report menu item status BugReportManager::instance().register_callback([](bool isRunning) { @@ -345,6 +372,18 @@ void set_tray_icon_visible(bool shouldIconBeVisible) Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data); } +void set_tray_icon_theme_adaptive(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)); + if (icon) + { + tray_icon_data.hIcon = icon; + Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data); + } +} + void stop_tray_icon() { if (tray_icon_created) diff --git a/src/runner/tray_icon.h b/src/runner/tray_icon.h index e94b7630f4..5ef4c3a75b 100644 --- a/src/runner/tray_icon.h +++ b/src/runner/tray_icon.h @@ -4,9 +4,11 @@ #include // Start the Tray Icon -void start_tray_icon(bool isProcessElevated); +void start_tray_icon(bool isProcessElevated, bool theme_adaptive); // Change the Tray Icon visibility void set_tray_icon_visible(bool shouldIconBeVisible); +// Enable or disable theme adaptive tray icon at runtime +void set_tray_icon_theme_adaptive(bool theme_adaptive); // Stop the Tray Icon void stop_tray_icon(); // Open the Settings Window diff --git a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs index b02f28ff13..415eb60040 100644 --- a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs +++ b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs @@ -30,6 +30,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("show_tray_icon")] public bool ShowSysTrayIcon { get; set; } + // Gets or sets a value indicating whether the powertoys system tray icon should show a theme adaptive icon + [JsonPropertyName("show_theme_adaptive_tray_icon")] + public bool ShowThemeAdaptiveTrayIcon { get; set; } + // Gets or sets a value indicating whether the powertoy elevated. [CmdConfigureIgnoreAttribute] [JsonPropertyName("is_elevated")] diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml index fea485e9b4..a0f45625cf 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml @@ -283,13 +283,21 @@ IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" /> - + - + + + + + + A modern UI built with Fluent Design Fluent Design is a product name, do not loc + + Show a monochrome icon that matches the Windows theme + Quick Access flyout diff --git a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs index 321afd3dee..3919eb0a78 100644 --- a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs @@ -164,6 +164,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } _showSysTrayIcon = GeneralSettingsConfig.ShowSysTrayIcon; + _showThemeAdaptiveSysTrayIcon = GeneralSettingsConfig.ShowThemeAdaptiveTrayIcon; _showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification; _autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates; _showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates; @@ -253,6 +254,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private static bool _isDevBuild; private bool _startup; private bool _showSysTrayIcon; + private bool _showThemeAdaptiveSysTrayIcon; private GpoRuleConfigured _runAtStartupGpoRuleConfiguration; private bool _runAtStartupIsGPOConfigured; private bool _isElevated; @@ -406,6 +408,24 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool ShowThemeAdaptiveTrayIcon + { + get + { + return _showThemeAdaptiveSysTrayIcon; + } + + set + { + if (_showThemeAdaptiveSysTrayIcon != value) + { + _showThemeAdaptiveSysTrayIcon = value; + GeneralSettingsConfig.ShowThemeAdaptiveTrayIcon = value; + NotifyPropertyChanged(); + } + } + } + public string RunningAsText { get