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

@@ -1,5 +1,64 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
@@ -89,7 +148,7 @@
<value>This setting has been disabled by your administrator.</value> <value>This setting has been disabled by your administrator.</value>
</data> </data>
<data name="STARTUP_DISABLED_BY_USER" xml:space="preserve"> <data name="STARTUP_DISABLED_BY_USER" xml:space="preserve">
<value>This setting has been disabled manually via &lt;a href=&quot;https://ms_settings_startupapps&quot; target=&quot;_blank&quot;&gt;Startup Settings&lt;/a&gt;.</value> <value>This setting has been disabled manually via &lt;a href="https://ms_settings_startupapps" target="_blank"&gt;Startup Settings&lt;/a&gt;.</value>
</data> </data>
<data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve"> <data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve">
<value>An update to PowerToys is available.</value> <value>An update to PowerToys is available.</value>
@@ -121,6 +180,9 @@
<value>Exit</value> <value>Exit</value>
<comment>Exit as a verb, as in Exit the application</comment> <comment>Exit as a verb, as in Exit the application</comment>
</data> </data>
<data name="SHOW_TRAY_ICON_MENU_TEXT" xml:space="preserve">
<value>Show icon</value>
</data>
<data name="SUBMIT_BUG_TEXT" xml:space="preserve"> <data name="SUBMIT_BUG_TEXT" xml:space="preserve">
<value>Report bug</value> <value>Report bug</value>
</data> </data>

View File

@@ -1,6 +1,7 @@
#include "pch.h" #include "pch.h"
#include "general_settings.h" #include "general_settings.h"
#include "auto_start_helper.h" #include "auto_start_helper.h"
#include "tray_icon.h"
#include "Generated files/resource.h" #include "Generated files/resource.h"
#include <common/SettingsAPI/settings_helpers.h> #include <common/SettingsAPI/settings_helpers.h>
@@ -14,6 +15,7 @@
// TODO: would be nice to get rid of these globals, since they're basically cached json settings // 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 std::wstring settings_theme = L"system";
static bool show_tray_icon = true;
static bool run_as_elevated = false; static bool run_as_elevated = false;
static bool show_new_updates_toast_notification = true; static bool show_new_updates_toast_notification = true;
static bool download_updates_automatically = 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"enabled", std::move(enabled));
result.SetNamedValue(L"show_tray_icon", json::value(showSystemTrayIcon));
result.SetNamedValue(L"is_elevated", json::value(isElevated)); result.SetNamedValue(L"is_elevated", json::value(isElevated));
result.SetNamedValue(L"run_elevated", json::value(isRunElevated)); result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification)); result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification));
@@ -74,7 +77,9 @@ json::JsonObject load_general_settings()
GeneralSettings get_general_settings() GeneralSettings get_general_settings()
{ {
const bool is_user_admin = check_user_is_admin(); const bool is_user_admin = check_user_is_admin();
GeneralSettings settings{ GeneralSettings settings
{
.showSystemTrayIcon = show_tray_icon,
.isElevated = is_process_elevated(), .isElevated = is_process_elevated(),
.isRunElevated = run_as_elevated, .isRunElevated = run_as_elevated,
.isAdmin = is_user_admin, .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"); 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) if (save)
{ {
GeneralSettings save_settings = get_general_settings(); GeneralSettings save_settings = get_general_settings();

View File

@@ -5,6 +5,7 @@
struct GeneralSettings struct GeneralSettings
{ {
bool isStartupEnabled; bool isStartupEnabled;
bool showSystemTrayIcon;
std::wstring startupDisabledReason; std::wstring startupDisabledReason;
std::map<std::wstring, bool> isModulesEnabledMap; std::map<std::wstring, bool> isModulesEnabledMap;
bool isElevated; bool isElevated;

View File

@@ -90,6 +90,7 @@ void open_menu_from_another_instance(std::optional<std::string> settings_window)
msg = static_cast<LPARAM>(ESettingsWindowNames_from_string(settings_window.value())); msg = static_cast<LPARAM>(ESettingsWindowNames_from_string(settings_window.value()));
} }
PostMessageW(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, msg); 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) 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 #endif
Trace::RegisterProvider(); Trace::RegisterProvider();
start_tray_icon(isProcessElevated); start_tray_icon(isProcessElevated);
set_tray_icon_visible(get_general_settings().showSystemTrayIcon);
CentralizedKeyboardHook::Start(); CentralizedKeyboardHook::Start();
int result = -1; int result = -1;

View File

@@ -21,3 +21,4 @@
#define ID_REPORT_BUG_COMMAND 40004 #define ID_REPORT_BUG_COMMAND 40004
#define ID_DOCUMENTATION_MENU_COMMAND 40005 #define ID_DOCUMENTATION_MENU_COMMAND 40005
#define ID_QUICK_ACCESS_MENU_COMMAND 40006 #define ID_QUICK_ACCESS_MENU_COMMAND 40006
#define ID_SHOW_TRAY_ICON_MENU_COMMAND 40007

Binary file not shown.

View File

@@ -2,6 +2,7 @@
#include "Generated files/resource.h" #include "Generated files/resource.h"
#include "settings_window.h" #include "settings_window.h"
#include "tray_icon.h" #include "tray_icon.h"
#include "general_settings.h"
#include "centralized_hotkeys.h" #include "centralized_hotkeys.h"
#include "centralized_kb_hook.h" #include "centralized_kb_hook.h"
#include <Windows.h> #include <Windows.h>
@@ -90,6 +91,13 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
} }
DestroyWindow(window); DestroyWindow(window);
break; 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: case ID_ABOUT_MENU_COMMAND:
if (!about_box_shown) 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 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 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 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 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); 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_SETTINGS_MENU_COMMAND, settings_menuitem_label.data());
change_menu_item_text(ID_EXIT_MENU_COMMAND, exit_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_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_DOCUMENTATION_MENU_COMMAND, documentation_menuitem_label.data());
change_menu_item_text(ID_QUICK_ACCESS_MENU_COMMAND, quick_access_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() void stop_tray_icon()
{ {
if (tray_icon_created) if (tray_icon_created)

View File

@@ -4,6 +4,8 @@
// Start the Tray Icon // Start the Tray Icon
void start_tray_icon(bool isProcessElevated); void start_tray_icon(bool isProcessElevated);
// Change the Tray Icon visibility
void set_tray_icon_visible(bool shouldIconBeVisible);
// Stop the Tray Icon // Stop the Tray Icon
void stop_tray_icon(); void stop_tray_icon();
// Open the Settings Window // Open the Settings Window
@@ -13,4 +15,5 @@ typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback // Calls a callback in _callback
bool dispatch_run_on_main_ui_thread(main_loop_callback_function _callback, PVOID data); 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"; const inline wchar_t* pt_tray_icon_window_class = L"PToyTrayIconWindow";

View File

@@ -19,6 +19,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("startup")] [JsonPropertyName("startup")]
public bool Startup { get; set; } 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. // Gets or sets a value indicating whether the powertoy elevated.
[CmdConfigureIgnoreAttribute] [CmdConfigureIgnoreAttribute]
[JsonPropertyName("is_elevated")] [JsonPropertyName("is_elevated")]
@@ -75,6 +79,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public GeneralSettings() public GeneralSettings()
{ {
Startup = false; Startup = false;
ShowSysTrayIcon = true;
IsAdmin = false; IsAdmin = false;
EnableWarningsElevatedApps = true; EnableWarningsElevatedApps = true;
IsElevated = false; IsElevated = false;

View File

@@ -224,6 +224,36 @@ namespace ViewModelTests
viewModel.ThemeIndex = 0; 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] [TestMethod]
public void AllModulesAreEnabledByDefault() 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_SHOWNORMAL = 1;
internal const int SW_SHOWMAXIMIZED = 3; internal const int SW_SHOWMAXIMIZED = 3;
internal const int SW_HIDE = 0; 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")] [DllImport("user32.dll")]
internal static extern IntPtr GetActiveWindow(); internal static extern IntPtr GetActiveWindow();
@@ -48,6 +49,12 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
[DllImport("Comdlg32.dll", CharSet = CharSet.Unicode)] [DllImport("Comdlg32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetOpenFileName([In, Out] OpenFileName openFileName); 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)] [DllImport("comdlg32.dll", CharSet = CharSet.Auto, EntryPoint = "ChooseFont", SetLastError = true)]
internal static extern bool ChooseFont(IntPtr lpChooseFont); internal static extern bool ChooseFont(IntPtr lpChooseFont);

View File

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

View File

@@ -10,7 +10,7 @@
<StackPanel <StackPanel
ChildrenTransitions="{StaticResource SettingsCardsAnimations}" ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
Orientation="Vertical" Orientation="Vertical"
Spacing="2" /> Spacing="{StaticResource SettingsCardSpacing}" />
</ItemsPanelTemplate> </ItemsPanelTemplate>
</Setter.Value> </Setter.Value>
</Setter> </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(); ViewModel.StartBugReport();

View File

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

View File

@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Flyout;
using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels; using Microsoft.PowerToys.Settings.UI.ViewModels;
@@ -165,5 +166,24 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{ {
await Task.Run(ViewModel.ViewDiagnosticData); 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" Margin="48,0,0,0"
VerticalAlignment="Top" VerticalAlignment="Top"
IsHitTestVisible="True"> IsHitTestVisible="True">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftTitleBarColumn" Width="*" />
<ColumnDefinition x:Name="RightTitleBarColumn" Width="Auto" />
</Grid.ColumnDefinitions>
<animations:Implicit.Animations> <animations:Implicit.Animations>
<animations:OffsetAnimation Duration="0:0:0.3" /> <animations:OffsetAnimation Duration="0:0:0.3" />
</animations:Implicit.Animations> </animations:Implicit.Animations>
<StackPanel Orientation="Horizontal"> <StackPanel
Grid.Column="0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<Image <Image
Width="16" Width="16"
Height="16" Height="16"
@@ -64,6 +71,24 @@
TextWrapping="NoWrap" TextWrapping="NoWrap"
Visibility="Collapsed" /> Visibility="Collapsed" />
</StackPanel> </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> </Grid>
<NavigationView <NavigationView
x:Name="navigationView" x:Name="navigationView"

View File

@@ -9,6 +9,7 @@ using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Services; using Microsoft.PowerToys.Settings.UI.Services;
using Microsoft.PowerToys.Settings.UI.ViewModels; using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Controls; 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. // 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 // https://docs.microsoft.com/windows/apps/develop/title-bar?tabs=winui3#full-customization
u.ExtendsContentIntoTitleBar = true; u.ExtendsContentIntoTitleBar = true;
u.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(u)); WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(u));
u.SetTitleBar(AppTitleBar); u.SetTitleBar(AppTitleBar);
var loader = ResourceLoaderInstance.ResourceLoader; var loader = ResourceLoaderInstance.ResourceLoader;
@@ -457,5 +459,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
{ {
navigationView.IsPaneOpen = !navigationView.IsPaneOpen; 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"> <data name="GeneralPage_ShowWhatsNewAfterUpdates.Content" xml:space="preserve">
<value>Show the release notes after an update</value> <value>Show the release notes after an update</value>
</data> </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"> <data name="QuickAccent_Prevent_Activation_On_Game_Mode.Content" xml:space="preserve">
<value>Do not activate when Game Mode is on</value> <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> <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"> <data name="Help_hueOklch" xml:space="preserve">
<value>hue (Oklch)</value> <value>hue (Oklch)</value>
</data> </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> </root>

View File

@@ -146,6 +146,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_startup = GeneralSettingsConfig.Startup; _startup = GeneralSettingsConfig.Startup;
} }
_showSysTrayIcon = GeneralSettingsConfig.ShowSysTrayIcon;
_showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification; _showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification;
_autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates; _autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates;
_showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates; _showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates;
@@ -228,6 +229,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private static bool _isDevBuild; private static bool _isDevBuild;
private bool _startup; private bool _startup;
private bool _showSysTrayIcon;
private GpoRuleConfigured _runAtStartupGpoRuleConfiguration; private GpoRuleConfigured _runAtStartupGpoRuleConfiguration;
private bool _runAtStartupIsGPOConfigured; private bool _runAtStartupIsGPOConfigured;
private bool _isElevated; 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 public string RunningAsText
{ {
get get