Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public partial class SettingsModel : ObservableObject

public bool HighlightSearchOnActivate { get; set; } = true;

public bool ShowSystemTrayIcon { get; set; } = true;

public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];

public Dictionary<string, CommandAlias> Aliases { get; set; } = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ public int MonitorPositionIndex
}
}

public bool ShowSystemTrayIcon
{
get => _settings.ShowSystemTrayIcon;
set
{
_settings.ShowSystemTrayIcon = value;
Save();
}
}

public ObservableCollection<ProviderSettingsViewModel> CommandProviders { get; } = [];

public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler)
Expand Down
70 changes: 37 additions & 33 deletions src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -99,7 +98,6 @@ public MainWindow()
_hotkeyWndProc = HotKeyPrc;
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_hotkeyWndProc);
_originalWndProc = Marshal.GetDelegateForFunctionPointer<WNDPROC>(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
AddNotificationIcon();

// Load our settings, and then also wire up a settings changed handler
HotReloadSettings();
Expand Down Expand Up @@ -149,6 +147,7 @@ private void HotReloadSettings()
var settings = App.Current.Services.GetService<SettingsModel>()!;

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.
Expand Down Expand Up @@ -299,7 +298,7 @@ internal void MainWindow_Closed(object sender, WindowEventArgs args)
var extensionService = serviceProvider.GetService<IExtensionService>()!;
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.
Expand Down Expand Up @@ -491,9 +490,9 @@ private LRESULT HotKeyPrc(
// WM_WINDOWPOSCHANGING which is always received on explorer startup sequence.
case PInvoke.WM_WINDOWPOSCHANGING:
{
if (!_createdIcon)
if (_trayIconData == null)
{
AddNotificationIcon();
SetupTrayIcon();
}
}

Expand All @@ -505,7 +504,7 @@ private LRESULT HotKeyPrc(
{
// Handle the case where explorer.exe restarts.
// Even if we created it before, do it again
AddNotificationIcon();
SetupTrayIcon();
}
else if (uMsg == WM_TRAY_ICON)
{
Expand All @@ -525,55 +524,60 @@ private LRESULT HotKeyPrc(
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<SettingsModel>()!.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;
var d = (NOTIFYICONDATAW)_trayIconData;

// Add the notification icon
if (PInvoke.Shell_NotifyIcon(NOTIFY_ICON_MESSAGE.NIM_ADD, in d))
// Add the notification icon
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
<ToggleSwitch IsOn="{x:Bind viewModel.SingleClickActivates, Mode=TwoWay}" />
</controls:SettingsCard>

<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=&#xE75B;}">
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
</controls:SettingsCard>

<!-- Example 'About' section -->
<TextBlock x:Uid="AboutSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,10 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="BehaviorSettingsHeader.Text" xml:space="preserve">
<value>Behavior</value>
</data>
<data name="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard.Header" xml:space="preserve">
<value>Show system tray icon</value>
</data>
<data name="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard.Description" xml:space="preserve">
<value>Choose if Command Palette is visible in the system tray</value>
Comment on lines +389 to +392
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some how duplicated. Do we really need a description here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that description is redundant. I added it because all options has one.

</data>
</root>
Loading