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 <jaime@janeasystems.com>
Co-authored-by: vanzue <vanzue@outlook.com>
Co-authored-by: Kayla Cinnamon <cinnamon@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
Bradley Myers
2025-05-26 05:03:35 -04:00
committed by GitHub
parent 718e725571
commit 53b989857b
20 changed files with 270 additions and 10 deletions

View File

@@ -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;

View File

@@ -224,6 +224,36 @@ namespace ViewModelTests
viewModel.ThemeIndex = 0;
}
[TestMethod]
public void IsShowSysTrayIconEnabledByDefaultShouldDisableWhenSuccessful()
{
// Arrange
// Assert
Func<string, int> sendMockIPCConfigMSG = msg =>
{
OutGoingGeneralSettings snd = JsonSerializer.Deserialize<OutGoingGeneralSettings>(msg);
Assert.IsFalse(snd.GeneralSettings.ShowSysTrayIcon);
return 0;
};
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
Func<string, int> sendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new(
settingsRepository: SettingsRepository<GeneralSettings>.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()
{

View File

@@ -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);

View File

@@ -39,6 +39,7 @@
<tkconverters:StringFormatConverter x:Key="StringFormatConverter" />
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
<x:Double x:Key="SettingsCardSpacing">2</x:Double>
<!-- Overrides -->
<Thickness x:Key="InfoBarIconMargin">6,16,16,16</Thickness>

View File

@@ -10,7 +10,7 @@
<StackPanel
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
Orientation="Vertical"
Spacing="2" />
Spacing="{StaticResource SettingsCardSpacing}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>

View File

@@ -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();

View File

@@ -266,6 +266,10 @@
<tkcontrols:SettingsCard x:Uid="GeneralPage_RunAtStartUp" IsEnabled="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
<ToggleSwitch x:Uid="ShowSystemTrayIcon_ToggleSwitch" IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<InfoBar
x:Uid="GPO_SettingIsManaged"
BorderThickness="0"
@@ -402,10 +406,11 @@
</InfoBar>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="General_DiagnosticsAndFeedback" Visibility="Visible">
<StackPanel>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<HyperlinkButton
x:Uid="GeneralPage_DiagnosticsAndFeedback_Link"
Margin="-8,0,0,0"
Margin="0,0,0,8"
Padding="0"
NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation" />
<tkcontrols:SettingsCard
x:Uid="GeneralPage_EnableDataDiagnostics"
@@ -449,7 +454,12 @@
<Button x:Uid="GeneralPage_ViewDiagnosticDataViewerInfoButton" Click="Click_ViewDiagnosticDataViewerRestart" />
</InfoBar.ActionButton>
</InfoBar>
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=&#xEBE8;}">
<Button
x:Uid="GeneralPageReportBugPackage"
HorizontalAlignment="Right"
Click="BugReportToolClicked" />
</tkcontrols:SettingsCard>
</StackPanel>
</controls:SettingsGroup>
</StackPanel>

View File

@@ -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);
}
}
}

View File

@@ -39,10 +39,17 @@
Margin="48,0,0,0"
VerticalAlignment="Top"
IsHitTestVisible="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftTitleBarColumn" Width="*" />
<ColumnDefinition x:Name="RightTitleBarColumn" Width="Auto" />
</Grid.ColumnDefinitions>
<animations:Implicit.Animations>
<animations:OffsetAnimation Duration="0:0:0.3" />
</animations:Implicit.Animations>
<StackPanel Orientation="Horizontal">
<StackPanel
Grid.Column="0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Image
Width="16"
Height="16"
@@ -64,6 +71,24 @@
TextWrapping="NoWrap"
Visibility="Collapsed" />
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,0,148,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
x:Name="ShutDownBtn"
Height="48"
Click="ExitPTItem_Tapped"
Style="{StaticResource SubtleButtonStyle}">
<FontIcon FontSize="16" Glyph="&#xE7E8;" />
<ToolTipService.ToolTip>
<ToolTip>
<TextBlock x:Uid="AppTitleBarShutDown_Tooltip" />
</ToolTip>
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
<NavigationView
x:Name="navigationView"

View File

@@ -9,6 +9,7 @@ using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls;
@@ -422,6 +423,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
// A custom title bar is required for full window theme and Mica support.
// https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
u.ExtendsContentIntoTitleBar = true;
u.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(u));
u.SetTitleBar(AppTitleBar);
var loader = ResourceLoaderInstance.ResourceLoader;
@@ -457,5 +459,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{
navigationView.IsPaneOpen = !navigationView.IsPaneOpen;
}
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);
}
}
}

View File

@@ -4299,6 +4299,12 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="GeneralPage_ShowWhatsNewAfterUpdates.Content" xml:space="preserve">
<value>Show the release notes after an update</value>
</data>
<data name="ShowSystemTrayIcon.Description" xml:space="preserve">
<value>This settings page is accessible by running the PowerToys executable again</value>
</data>
<data name="ShowSystemTrayIcon.Header" xml:space="preserve">
<value>Show system tray icon</value>
</data>
<data name="QuickAccent_Prevent_Activation_On_Game_Mode.Content" xml:space="preserve">
<value>Do not activate when Game Mode is on</value>
<comment>"Game mode" is the Windows feature to prevent notification when playing a game.</comment>
@@ -5020,4 +5026,25 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<data name="Help_hueOklch" xml:space="preserve">
<value>hue (Oklch)</value>
</data>
<data name="ExitPT_NavViewItem.Content" xml:space="preserve">
<value>Exit PowerToys</value>
</data>
<data name="General_System.Header" xml:space="preserve">
<value>System</value>
</data>
<data name="GeneralPage_ReportBugPackage.Header" xml:space="preserve">
<value>Generate bug report package</value>
</data>
<data name="GeneralPage_ReportBugPackage.Description" xml:space="preserve">
<value>Create zip folder with logs on the Desktop</value>
</data>
<data name="GeneralPageReportBugPackage.Content" xml:space="preserve">
<value>Generate package</value>
</data>
<data name="AppTitleBarShutDown_Tooltip.Text" xml:space="preserve">
<value>Shut down</value>
</data>
<data name="BugReportUnderConstruction" xml:space="preserve">
<value>Bug report package is being created</value>
</data>
</root>

View File

@@ -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