diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs index 785c27c3c9..86612e83aa 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs @@ -36,6 +36,8 @@ public partial class SettingsModel : ObservableObject public bool HighlightSearchOnActivate { get; set; } = true; + public bool ShowSystemTrayIcon { get; set; } = true; + public Dictionary ProviderSettings { get; set; } = []; public Dictionary Aliases { get; set; } = []; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs index 190d75b0c3..c66c07c47b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs @@ -87,6 +87,16 @@ public partial class SettingsViewModel : INotifyPropertyChanged } } + public bool ShowSystemTrayIcon + { + get => _settings.ShowSystemTrayIcon; + set + { + _settings.ShowSystemTrayIcon = value; + Save(); + } + } + public ObservableCollection CommandProviders { get; } = []; public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index ac1b0e0860..2ec74b37f2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -54,7 +54,6 @@ public sealed partial class MainWindow : Window, // Notification Area ("Tray") icon data private NOTIFYICONDATAW? _trayIconData; - private bool _createdIcon; private DestroyIconSafeHandle? _largeIcon; private DesktopAcrylicController? _acrylicController; @@ -99,7 +98,6 @@ public sealed partial class MainWindow : Window, _hotkeyWndProc = HotKeyPrc; var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_hotkeyWndProc); _originalWndProc = Marshal.GetDelegateForFunctionPointer(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer)); - AddNotificationIcon(); // Load our settings, and then also wire up a settings changed handler HotReloadSettings(); @@ -149,6 +147,7 @@ public sealed partial class MainWindow : Window, var settings = App.Current.Services.GetService()!; SetupHotkey(settings); + SetupTrayIcon(settings.ShowSystemTrayIcon); // This will prevent our window from appearing in alt+tab or the taskbar. // You'll _need_ to use the hotkey to summon it. @@ -299,7 +298,7 @@ public sealed partial class MainWindow : Window, var extensionService = serviceProvider.GetService()!; extensionService.SignalStopExtensionsAsync(); - RemoveNotificationIcon(); + RemoveTrayIcon(); // WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592). // Workaround by turning it off before shutdown. @@ -491,9 +490,9 @@ public sealed partial class MainWindow : Window, // WM_WINDOWPOSCHANGING which is always received on explorer startup sequence. case PInvoke.WM_WINDOWPOSCHANGING: { - if (!_createdIcon) + if (_trayIconData == null) { - AddNotificationIcon(); + SetupTrayIcon(); } } @@ -505,7 +504,7 @@ public sealed partial class MainWindow : Window, { // Handle the case where explorer.exe restarts. // Even if we created it before, do it again - AddNotificationIcon(); + SetupTrayIcon(); } else if (uMsg == WM_TRAY_ICON) { @@ -525,55 +524,60 @@ public sealed partial class MainWindow : Window, return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam); } - private void AddNotificationIcon() + private void SetupTrayIcon(bool? showSystemTrayIcon = null) { - // We only need to build the tray data once. - if (_trayIconData == null) + if (showSystemTrayIcon ?? App.Current.Services.GetService()!.ShowSystemTrayIcon) { - // We need to stash this handle, so it doesn't clean itself up. If - // explorer restarts, we'll come back through here, and we don't - // really need to re-load the icon in that case. We can just use - // the handle from the first time. - _largeIcon = GetAppIconHandle(); - _trayIconData = new NOTIFYICONDATAW() + // We only need to build the tray data once. + if (_trayIconData == null) { - cbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATAW)), - hWnd = _hwnd, - uID = MY_NOTIFY_ID, - uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP, - uCallbackMessage = WM_TRAY_ICON, - hIcon = (HICON)_largeIcon.DangerousGetHandle(), - szTip = RS_.GetString("AppStoreName"), - }; + // We need to stash this handle, so it doesn't clean itself up. If + // explorer restarts, we'll come back through here, and we don't + // really need to re-load the icon in that case. We can just use + // the handle from the first time. + _largeIcon = GetAppIconHandle(); + _trayIconData = new NOTIFYICONDATAW() + { + cbSize = (uint)Marshal.SizeOf(typeof(NOTIFYICONDATAW)), + hWnd = _hwnd, + uID = MY_NOTIFY_ID, + uFlags = NOTIFY_ICON_DATA_FLAGS.NIF_MESSAGE | NOTIFY_ICON_DATA_FLAGS.NIF_ICON | NOTIFY_ICON_DATA_FLAGS.NIF_TIP, + uCallbackMessage = WM_TRAY_ICON, + hIcon = (HICON)_largeIcon.DangerousGetHandle(), + szTip = RS_.GetString("AppStoreName"), + }; + } + + var d = (NOTIFYICONDATAW)_trayIconData; + + // Add the notification icon + PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d); } - - var d = (NOTIFYICONDATAW)_trayIconData; - - // Add the notification icon - if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d)) + else { - _createdIcon = true; + RemoveTrayIcon(); } } - private void RemoveNotificationIcon() + private void RemoveTrayIcon() { - if (_trayIconData != null && _createdIcon) + if (_trayIconData != null) { var d = (NOTIFYICONDATAW)_trayIconData; if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_DELETE, in d)) { - _createdIcon = false; + _trayIconData = null; } } + + _largeIcon?.Close(); } private DestroyIconSafeHandle GetAppIconHandle() { var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; DestroyIconSafeHandle largeIcon; - DestroyIconSafeHandle smallIcon; - PInvoke.ExtractIconEx(exePath, 0, out largeIcon, out smallIcon, 1); + PInvoke.ExtractIconEx(exePath, 0, out largeIcon, out _, 1); return largeIcon; } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml index e8a36e618a..8494bed1e1 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml @@ -68,6 +68,10 @@ + + + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw index 03e8c40eaa..0a4000997a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw @@ -385,4 +385,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut. Behavior + + Show system tray icon + + + Choose if Command Palette is visible in the system tray + \ No newline at end of file