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 5737649c37..9f56295553 100644
Binary files a/src/runner/runner.base.rc and b/src/runner/runner.base.rc differ
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 @@
+
+
+
+
-
+
-
+
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs
index b40a021252..73dab06cb9 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Flyout;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
@@ -165,5 +166,24 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
await Task.Run(ViewModel.ViewDiagnosticData);
}
+
+ private void ExitPTItem_Tapped(object sender, RoutedEventArgs e)
+ {
+ const string ptTrayIconWindowClass = "PToyTrayIconWindow"; // Defined in runner/tray_icon.h
+ const nuint ID_EXIT_MENU_COMMAND = 40001; // Generated resource from runner/runner.base.rc
+
+ // Exit the XAML application
+ Application.Current.Exit();
+
+ // Invoke the exit command from the tray icon
+ IntPtr hWnd = NativeMethods.FindWindow(ptTrayIconWindowClass, ptTrayIconWindowClass);
+ NativeMethods.SendMessage(hWnd, NativeMethods.WM_COMMAND, ID_EXIT_MENU_COMMAND, 0);
+ }
+
+ private void BugReportToolClicked(object sender, RoutedEventArgs e)
+ {
+ var launchPage = new LaunchPage();
+ launchPage.ReportBugBtn_Click(sender, e);
+ }
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
index 20aa9f94dc..8f8ea157ba 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
@@ -39,10 +39,17 @@
Margin="48,0,0,0"
VerticalAlignment="Top"
IsHitTestVisible="True">
+
+
+
+
-
+
+
+
+
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