From 53b989857be05c829543a0f7ffe76017223960d2 Mon Sep 17 00:00:00 2001 From: Bradley Myers Date: Mon, 26 May 2025 05:03:35 -0400 Subject: [PATCH] Option to toggle the system tray icon (#23220) * Added option to toggle the system tray icon At the moment, this hides the icon making the settings window inaccessible without first modifying the general `settings.json` file. * Use IPC messages to manage the tray icon settings * Fix launching second window binds to active settings process * Added context menu option to hide tray icon * Added Exit PT button to settings ui NavigationView.PaneFooter * Moved DllImports to NativeMethods.cs * Sentence case titles * Fix whitespace * Re-add exit icon to NavView * Re-added toggle switch to new UI * Fix build * Fix build after merge main * Fix the string to display * add shut down buttons * finish polish * fix string * Styling tweaks to titlebar and settingscards * fix comment * fix unit test * fix ut --------- Co-authored-by: Jaime Bernardo Co-authored-by: vanzue Co-authored-by: Kayla Cinnamon Co-authored-by: Niels Laute --- src/runner/Resources.resx | 66 +++++++++++++++++- src/runner/general_settings.cpp | 14 +++- src/runner/general_settings.h | 1 + src/runner/main.cpp | 2 + src/runner/resource.base.h | 3 +- src/runner/runner.base.rc | Bin 3146 -> 3092 bytes src/runner/tray_icon.cpp | 18 +++++ src/runner/tray_icon.h | 3 + .../Settings.UI.Library/GeneralSettings.cs | 5 ++ .../ViewModelTests/General.cs | 30 ++++++++ .../Settings.UI/Helpers/NativeMethods.cs | 7 ++ .../Settings.UI/SettingsXAML/App.xaml | 1 + .../Controls/SettingsGroup/SettingsGroup.xaml | 2 +- .../SettingsXAML/Flyout/LaunchPage.xaml.cs | 2 +- .../SettingsXAML/Views/GeneralPage.xaml | 16 ++++- .../SettingsXAML/Views/GeneralPage.xaml.cs | 20 ++++++ .../SettingsXAML/Views/ShellPage.xaml | 27 ++++++- .../SettingsXAML/Views/ShellPage.xaml.cs | 15 ++++ .../Settings.UI/Strings/en-us/Resources.resw | 27 +++++++ .../ViewModels/GeneralViewModel.cs | 21 ++++++ 20 files changed, 270 insertions(+), 10 deletions(-) diff --git a/src/runner/Resources.resx b/src/runner/Resources.resx index 902e3a0874..2986ec6cef 100644 --- a/src/runner/Resources.resx +++ b/src/runner/Resources.resx @@ -1,5 +1,64 @@  + @@ -89,7 +148,7 @@ This setting has been disabled by your administrator. - This setting has been disabled manually via <a href="https://ms_settings_startupapps" target="_blank">Startup Settings</a>. + This setting has been disabled manually via <a href="https://ms_settings_startupapps" target="_blank">Startup Settings</a>. An update to PowerToys is available. @@ -121,6 +180,9 @@ Exit Exit as a verb, as in Exit the application + + Show icon + Report bug @@ -134,4 +196,4 @@ Administrator - + \ No newline at end of file diff --git a/src/runner/general_settings.cpp b/src/runner/general_settings.cpp index 83128b05e9..bf80fb2df8 100644 --- a/src/runner/general_settings.cpp +++ b/src/runner/general_settings.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "general_settings.h" #include "auto_start_helper.h" +#include "tray_icon.h" #include "Generated files/resource.h" #include @@ -14,6 +15,7 @@ // 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 run_as_elevated = false; static bool show_new_updates_toast_notification = true; static bool download_updates_automatically = true; @@ -38,6 +40,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"is_elevated", json::value(isElevated)); result.SetNamedValue(L"run_elevated", json::value(isRunElevated)); result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification)); @@ -74,7 +77,9 @@ json::JsonObject load_general_settings() GeneralSettings get_general_settings() { const bool is_user_admin = check_user_is_admin(); - GeneralSettings settings{ + GeneralSettings settings + { + .showSystemTrayIcon = show_tray_icon, .isElevated = is_process_elevated(), .isRunElevated = run_as_elevated, .isAdmin = is_user_admin, @@ -214,6 +219,13 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save) settings_theme = general_configs.GetNamedString(L"theme"); } + 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 (save) { GeneralSettings save_settings = get_general_settings(); diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h index f56271cf07..ef2224b132 100644 --- a/src/runner/general_settings.h +++ b/src/runner/general_settings.h @@ -5,6 +5,7 @@ struct GeneralSettings { bool isStartupEnabled; + bool showSystemTrayIcon; std::wstring startupDisabledReason; std::map isModulesEnabledMap; bool isElevated; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index e9ea4e59d9..91f9639c05 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -90,6 +90,7 @@ void open_menu_from_another_instance(std::optional settings_window) msg = static_cast(ESettingsWindowNames_from_string(settings_window.value())); } PostMessageW(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, msg); + SetForegroundWindow(hwnd_main); // Bring the settings window to the front } int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow, bool openOobe, bool openScoobe, bool showRestartNotificationAfterUpdate) @@ -104,6 +105,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow #endif Trace::RegisterProvider(); start_tray_icon(isProcessElevated); + set_tray_icon_visible(get_general_settings().showSystemTrayIcon); CentralizedKeyboardHook::Start(); int result = -1; diff --git a/src/runner/resource.base.h b/src/runner/resource.base.h index 22d040cc82..0ee4cbb2e5 100644 --- a/src/runner/resource.base.h +++ b/src/runner/resource.base.h @@ -20,4 +20,5 @@ #define ID_ABOUT_MENU_COMMAND 40003 #define ID_REPORT_BUG_COMMAND 40004 #define ID_DOCUMENTATION_MENU_COMMAND 40005 -#define ID_QUICK_ACCESS_MENU_COMMAND 40006 \ No newline at end of file +#define ID_QUICK_ACCESS_MENU_COMMAND 40006 +#define ID_SHOW_TRAY_ICON_MENU_COMMAND 40007 diff --git a/src/runner/runner.base.rc b/src/runner/runner.base.rc index 5737649c37fdeb87cd10216387ba8ae325ad55ac..9f56295553d8fd29c64c47c2b508fcf01b29a887 100644 GIT binary patch delta 83 zcmX>lF-2lS3*%%dCV|avOma+?!3-G;`3&U@3JjhM$v{>fgA#)dnCHR}&k)Su!Qjsj f4rGNe1Ti==L;_i!49-9?zsZSA`kMn-ez5}p;tUZG delta 135 zcmbOtaY|xC3u9m)Ln%WhLo!1)g91Y$kWOYuWhe&17={uCA0RsoNb3ScbD*k}7<7Ot zJsDgW;u!)NLV+x225%tS5lA~TxH1G!Udw1Wxqwkc8f1bCLq5>_B%tn8WK%XzW|U{z JtjF?<9RO^D925Wm diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp index 015fb158b8..30cca89951 100644 --- a/src/runner/tray_icon.cpp +++ b/src/runner/tray_icon.cpp @@ -2,6 +2,7 @@ #include "Generated files/resource.h" #include "settings_window.h" #include "tray_icon.h" +#include "general_settings.h" #include "centralized_hotkeys.h" #include "centralized_kb_hook.h" #include @@ -90,6 +91,13 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam) } DestroyWindow(window); break; + case ID_SHOW_TRAY_ICON_MENU_COMMAND: + { + GeneralSettings settings = get_general_settings(); + settings.showSystemTrayIcon = true; + apply_general_settings(settings.to_json(), true); + break; + } case ID_ABOUT_MENU_COMMAND: if (!about_box_shown) { @@ -191,11 +199,13 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam { static std::wstring settings_menuitem_label = GET_RESOURCE_STRING(IDS_SETTINGS_MENU_TEXT); static std::wstring exit_menuitem_label = GET_RESOURCE_STRING(IDS_EXIT_MENU_TEXT); + static std::wstring show_tray_icon_menuitem_label = GET_RESOURCE_STRING(IDS_SHOW_TRAY_ICON_MENU_TEXT); static std::wstring submit_bug_menuitem_label = GET_RESOURCE_STRING(IDS_SUBMIT_BUG_TEXT); static std::wstring documentation_menuitem_label = GET_RESOURCE_STRING(IDS_DOCUMENTATION_MENU_TEXT); static std::wstring quick_access_menuitem_label = GET_RESOURCE_STRING(IDS_QUICK_ACCESS_MENU_TEXT); change_menu_item_text(ID_SETTINGS_MENU_COMMAND, settings_menuitem_label.data()); change_menu_item_text(ID_EXIT_MENU_COMMAND, exit_menuitem_label.data()); + change_menu_item_text(ID_SHOW_TRAY_ICON_MENU_COMMAND, show_tray_icon_menuitem_label.data()); change_menu_item_text(ID_REPORT_BUG_COMMAND, submit_bug_menuitem_label.data()); change_menu_item_text(ID_DOCUMENTATION_MENU_COMMAND, documentation_menuitem_label.data()); change_menu_item_text(ID_QUICK_ACCESS_MENU_COMMAND, quick_access_menuitem_label.data()); @@ -312,6 +322,14 @@ void start_tray_icon(bool isProcessElevated) } } +void set_tray_icon_visible(bool shouldIconBeVisible) +{ + tray_icon_data.uFlags |= NIF_STATE; + tray_icon_data.dwStateMask = NIS_HIDDEN; + tray_icon_data.dwState = shouldIconBeVisible ? 0 : NIS_HIDDEN; + 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 3c323053ca..4fa7ebfe5a 100644 --- a/src/runner/tray_icon.h +++ b/src/runner/tray_icon.h @@ -4,6 +4,8 @@ // Start the Tray Icon void start_tray_icon(bool isProcessElevated); +// Change the Tray Icon visibility +void set_tray_icon_visible(bool shouldIconBeVisible); // Stop the Tray Icon void stop_tray_icon(); // Open the Settings Window @@ -13,4 +15,5 @@ typedef void (*main_loop_callback_function)(PVOID); // Calls a callback in _callback bool dispatch_run_on_main_ui_thread(main_loop_callback_function _callback, PVOID data); +// Must be the same as: settings-ui/Settings.UI/Views/ShellPage.xaml.cs -> ExitPTItem_Tapped() -> const string ptTrayIconWindowClass const inline wchar_t* pt_tray_icon_window_class = L"PToyTrayIconWindow"; \ No newline at end of file diff --git a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs index ed7b503150..3d295284e3 100644 --- a/src/settings-ui/Settings.UI.Library/GeneralSettings.cs +++ b/src/settings-ui/Settings.UI.Library/GeneralSettings.cs @@ -19,6 +19,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("startup")] public bool Startup { get; set; } + // Gets or sets a value indicating whether the powertoys system tray icon should be hidden. + [JsonPropertyName("show_tray_icon")] + public bool ShowSysTrayIcon { get; set; } + // Gets or sets a value indicating whether the powertoy elevated. [CmdConfigureIgnoreAttribute] [JsonPropertyName("is_elevated")] @@ -75,6 +79,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library public GeneralSettings() { Startup = false; + ShowSysTrayIcon = true; IsAdmin = false; EnableWarningsElevatedApps = true; IsElevated = false; diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs index 70bfbe4fba..8b47e8147a 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs @@ -224,6 +224,36 @@ namespace ViewModelTests viewModel.ThemeIndex = 0; } + [TestMethod] + public void IsShowSysTrayIconEnabledByDefaultShouldDisableWhenSuccessful() + { + // Arrange + // Assert + Func sendMockIPCConfigMSG = msg => + { + OutGoingGeneralSettings snd = JsonSerializer.Deserialize(msg); + Assert.IsFalse(snd.GeneralSettings.ShowSysTrayIcon); + return 0; + }; + + Func sendRestartAdminIPCMessage = msg => { return 0; }; + Func sendCheckForUpdatesIPCMessage = msg => { return 0; }; + GeneralViewModel viewModel = new( + settingsRepository: SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), + "GeneralSettings_RunningAsAdminText", + "GeneralSettings_RunningAsUserText", + false, + false, + sendMockIPCConfigMSG, + sendRestartAdminIPCMessage, + sendCheckForUpdatesIPCMessage, + GeneralSettingsFileName); + Assert.IsTrue(viewModel.ShowSysTrayIcon); + + // Act + viewModel.ShowSysTrayIcon = false; + } + [TestMethod] public void AllModulesAreEnabledByDefault() { diff --git a/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs b/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs index 072f12b00c..35132b2049 100644 --- a/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs +++ b/src/settings-ui/Settings.UI/Helpers/NativeMethods.cs @@ -17,6 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers internal const int SW_SHOWNORMAL = 1; internal const int SW_SHOWMAXIMIZED = 3; internal const int SW_HIDE = 0; + internal const int WM_COMMAND = 0x0111; // https://learn.microsoft.com/en-us/windows/win32/menurc/wm-command [DllImport("user32.dll")] internal static extern IntPtr GetActiveWindow(); @@ -48,6 +49,12 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers [DllImport("Comdlg32.dll", CharSet = CharSet.Unicode)] internal static extern bool GetOpenFileName([In, Out] OpenFileName openFileName); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll")] + internal static extern IntPtr SendMessage(IntPtr hWnd, IntPtr msg, UIntPtr wParam, UIntPtr lParam); + [DllImport("comdlg32.dll", CharSet = CharSet.Auto, EntryPoint = "ChooseFont", SetLastError = true)] internal static extern bool ChooseFont(IntPtr lpChooseFont); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml index 365c592d27..c1a5392bdc 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml @@ -39,6 +39,7 @@ + 2 6,16,16,16 diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml index d3462c6655..912211d9c4 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml @@ -10,7 +10,7 @@ + Spacing="{StaticResource SettingsCardSpacing}" /> diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs index dadcd78c68..aad7dcf215 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs @@ -176,7 +176,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout }); } - private void ReportBugBtn_Click(object sender, RoutedEventArgs e) + internal void ReportBugBtn_Click(object sender, RoutedEventArgs e) { ViewModel.StartBugReport(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml index 079a634e97..13d6ee5c8f 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml @@ -266,6 +266,10 @@ + + + + - + - + + + Show the release notes after an update + + This settings page is accessible by running the PowerToys executable again + + + Show system tray icon + Do not activate when Game Mode is on "Game mode" is the Windows feature to prevent notification when playing a game. @@ -5020,4 +5026,25 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m hue (Oklch) + + Exit PowerToys + + + System + + + Generate bug report package + + + Create zip folder with logs on the Desktop + + + Generate package + + + Shut down + + + Bug report package is being created + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs index c535ffa579..6b3bb8243a 100644 --- a/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/GeneralViewModel.cs @@ -146,6 +146,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _startup = GeneralSettingsConfig.Startup; } + _showSysTrayIcon = GeneralSettingsConfig.ShowSysTrayIcon; _showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification; _autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates; _showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates; @@ -228,6 +229,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private static bool _isDevBuild; private bool _startup; + private bool _showSysTrayIcon; private GpoRuleConfigured _runAtStartupGpoRuleConfiguration; private bool _runAtStartupIsGPOConfigured; private bool _isElevated; @@ -359,6 +361,25 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + // Gets or sets a value indicating whether the PowerToys icon should be shown in the system tray. + public bool ShowSysTrayIcon + { + get + { + return _showSysTrayIcon; + } + + set + { + if (_showSysTrayIcon != value) + { + _showSysTrayIcon = value; + GeneralSettingsConfig.ShowSysTrayIcon = value; + NotifyPropertyChanged(); + } + } + } + public string RunningAsText { get