Continuing part deux

This commit is contained in:
Michael Jolley
2025-12-06 10:57:55 -06:00
parent f5c2013247
commit 643bc36400
5 changed files with 167 additions and 113 deletions

View File

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using System.Security;
namespace Microsoft.CommandPalette.UI.Helpers;
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
[DllImport("shell32.dll")]
public static extern int SHQueryUserNotificationState(out UserNotificationState state);
}
internal enum UserNotificationState : int
{
QUNS_NOT_PRESENT = 1,
QUNS_BUSY,
QUNS_RUNNING_D3D_FULL_SCREEN,
QUNS_PRESENTATION_MODE,
QUNS_ACCEPTS_NOTIFICATIONS,
QUNS_QUIET_TIME,
QUNS_APP,
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
namespace Microsoft.CommandPalette.UI.Helpers;
internal sealed partial class WindowHelper
{
public static bool IsWindowFullscreen()
{
UserNotificationState state;
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ne-shellapi-query_user_notification_state
if (Marshal.GetExceptionForHR(NativeMethods.SHQueryUserNotificationState(out state)) is null)
{
if (state == UserNotificationState.QUNS_RUNNING_D3D_FULL_SCREEN ||
state == UserNotificationState.QUNS_BUSY ||
state == UserNotificationState.QUNS_PRESENTATION_MODE)
{
return true;
}
}
return false;
}
}

View File

@@ -13,8 +13,6 @@ using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Models.Messages; using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.CommandPalette.UI.Pages; using Microsoft.CommandPalette.UI.Pages;
using Microsoft.CommandPalette.UI.ViewModels.Helpers; using Microsoft.CommandPalette.UI.ViewModels.Helpers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.UI; using Microsoft.UI;
@@ -36,15 +34,22 @@ using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi; using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging; using Windows.Win32.UI.WindowsAndMessaging;
using WinRT;
using WinUIEx; using WinUIEx;
using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance; using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
namespace Microsoft.CommandPalette.UI; namespace Microsoft.CommandPalette.UI;
public sealed partial class MainWindow : WindowEx public sealed partial class MainWindow : WindowEx,
IRecipient<ShowWindowMessage>,
IRecipient<HideWindowMessage>,
IRecipient<QuitMessage>,
IRecipient<DismissMessage>
{ {
private readonly ILogger logger; private readonly ILogger logger;
private readonly ShellPage shellPage; private readonly ShellPage _shellPage;
private readonly SettingsModel _settingsModel;
private readonly TrayIconService _trayIconService;
private const int DefaultWidth = 800; private const int DefaultWidth = 800;
private const int DefaultHeight = 480; private const int DefaultHeight = 480;
@@ -68,10 +73,16 @@ public sealed partial class MainWindow : WindowEx
private WindowPosition _currentWindowPosition = new(); private WindowPosition _currentWindowPosition = new();
public MainWindow(ShellPage shellPage, ILogger logger) public MainWindow(ShellPage shellPage, SettingsModel settingsModel, TrayIconService trayIconService, ILogger logger)
{ {
InitializeComponent();
this.logger = logger; this.logger = logger;
this.shellPage = shellPage; _shellPage = shellPage;
_settingsModel = settingsModel;
_trayIconService = trayIconService;
RootElement.Children.Add(_shellPage);
_autoGoHomeTimer = new DispatcherTimer(); _autoGoHomeTimer = new DispatcherTimer();
_autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick; _autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick;
@@ -94,10 +105,11 @@ public sealed partial class MainWindow : WindowEx
SetAcrylic(); SetAcrylic();
// WeakReferenceMessenger.Default.Register<DismissMessage>(this); WeakReferenceMessenger.Default.Register<DismissMessage>(this);
// WeakReferenceMessenger.Default.Register<QuitMessage>(this); WeakReferenceMessenger.Default.Register<QuitMessage>(this);
// WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this); WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
// WeakReferenceMessenger.Default.Register<HideWindowMessage>(this); WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
// Hide our titlebar. // Hide our titlebar.
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed // We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
// to hide the old caption buttons. Then, in UpdateRegionsForCustomTitleBar, // to hide the old caption buttons. Then, in UpdateRegionsForCustomTitleBar,
@@ -119,7 +131,7 @@ public sealed partial class MainWindow : WindowEx
// Load our settings, and then also wire up a settings changed handler // Load our settings, and then also wire up a settings changed handler
HotReloadSettings(); HotReloadSettings();
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler; _settingsModel.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes // Make sure that we update the acrylic theme when the OS theme changes
RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic); RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
@@ -137,11 +149,39 @@ public sealed partial class MainWindow : WindowEx
HideWindow(); HideWindow();
} }
public void Receive(ShowWindowMessage message)
{
ShowHwnd(message.Hwnd, _settingsModel.SummonOn);
}
public void Receive(HideWindowMessage message)
{
// This might come in off the UI thread. Make sure to hop back.
DispatcherQueue.TryEnqueue(() =>
{
HideWindow();
});
}
public void Receive(QuitMessage message) =>
// This might come in on a background thread
DispatcherQueue.TryEnqueue(() => Close());
public void Receive(DismissMessage message)
{
// This might come in off the UI thread. Make sure to hop back.
DispatcherQueue.TryEnqueue(() =>
{
HideWindow();
});
}
private void OnAutoGoHomeTimerOnTick(object? s, object e) private void OnAutoGoHomeTimerOnTick(object? s, object e)
{ {
_autoGoHomeTimer.Stop(); _autoGoHomeTimer.Stop();
// BEAR LOADING: Focus Search must be suppressed here; otherwise it may steal focus (for example, from the system tray icon) // LOAD BEARING: Focus Search must be suppressed here; otherwise it may steal focus (for example, from the system tray icon)
// and prevent the user from opening its context menu. // and prevent the user from opening its context menu.
WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false)); WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false));
} }
@@ -178,8 +218,7 @@ public sealed partial class MainWindow : WindowEx
private void RestoreWindowPosition() private void RestoreWindowPosition()
{ {
var settings = App.Current.Services.GetService<SettingsModel>(); if (_settingsModel.LastWindowPosition is not WindowPosition savedPosition)
if (settings?.LastWindowPosition is not WindowPosition savedPosition)
{ {
PositionCentered(); PositionCentered();
return; return;
@@ -226,14 +265,12 @@ public sealed partial class MainWindow : WindowEx
private void HotReloadSettings() private void HotReloadSettings()
{ {
var settings = App.Current.Services.GetService<SettingsModel>()!; SetupHotkey(_settingsModel);
_trayIconService.SetupTrayIcon(_settingsModel.ShowSystemTrayIcon);
SetupHotkey(settings); _ignoreHotKeyWhenFullScreen = _settingsModel.IgnoreShortcutWhenFullscreen;
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen; _autoGoHomeInterval = _settingsModel.AutoGoHomeInterval;
_autoGoHomeInterval = settings.AutoGoHomeInterval;
_autoGoHomeTimer.Interval = _autoGoHomeInterval; _autoGoHomeTimer.Interval = _autoGoHomeInterval;
} }
@@ -497,36 +534,6 @@ public sealed partial class MainWindow : WindowEx
return DisplayArea.Primary; return DisplayArea.Primary;
} }
public void Receive(ShowWindowMessage message)
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
ShowHwnd(message.Hwnd, settings.SummonOn);
}
public void Receive(HideWindowMessage message)
{
// This might come in off the UI thread. Make sure to hop back.
DispatcherQueue.TryEnqueue(() =>
{
HideWindow();
});
}
public void Receive(QuitMessage message) =>
// This might come in on a background thread
DispatcherQueue.TryEnqueue(() => Close());
public void Receive(DismissMessage message)
{
// This might come in off the UI thread. Make sure to hop back.
DispatcherQueue.TryEnqueue(() =>
{
HideWindow();
});
}
private void HideWindow() private void HideWindow()
{ {
// Cloak our HWND to avoid all animations. // Cloak our HWND to avoid all animations.
@@ -580,7 +587,7 @@ public sealed partial class MainWindow : WindowEx
var hr = PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL)); var hr = PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL));
if (hr.Failed) if (hr.Failed)
{ {
Logger.LogWarning($"DWM cloaking of the main window failed. HRESULT: {hr.Value}."); Log_DwmCloakingFailed(hr);
} }
wasCloaked = hr.Succeeded; wasCloaked = hr.Succeeded;
@@ -607,13 +614,11 @@ public sealed partial class MainWindow : WindowEx
internal void MainWindow_Closed(object sender, WindowEventArgs args) internal void MainWindow_Closed(object sender, WindowEventArgs args)
{ {
var serviceProvider = App.Current.Services;
UpdateWindowPositionInMemory(); UpdateWindowPositionInMemory();
var settings = serviceProvider.GetService<SettingsModel>(); if (_settingsModel is not null)
if (settings is not null)
{ {
settings.LastWindowPosition = new WindowPosition _settingsModel.LastWindowPosition = new WindowPosition
{ {
X = _currentWindowPosition.X, X = _currentWindowPosition.X,
Y = _currentWindowPosition.Y, Y = _currentWindowPosition.Y,
@@ -624,12 +629,12 @@ public sealed partial class MainWindow : WindowEx
ScreenHeight = _currentWindowPosition.ScreenHeight, ScreenHeight = _currentWindowPosition.ScreenHeight,
}; };
SettingsModel.SaveSettings(settings, logger); SettingsModel.SaveSettings(_settingsModel, logger);
} }
// var extensionService = serviceProvider.GetService<IExtensionService>()!; // var extensionService = serviceProvider.GetService<IExtensionService>()!;
// extensionService.SignalStopExtensionsAsync(); // extensionService.SignalStopExtensionsAsync();
App.Current.Services.GetService<TrayIconService>()!.Destroy(); _trayIconService.Destroy();
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592). // WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
// Workaround by turning it off before shutdown. // Workaround by turning it off before shutdown.
@@ -767,8 +772,7 @@ public sealed partial class MainWindow : WindowEx
} }
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase)) else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
{ {
var settings = App.Current.Services.GetService<SettingsModel>(); if (_settingsModel.AllowExternalReload == true)
if (settings?.AllowExternalReload == true)
{ {
Log_ExternalReloadTriggered(); Log_ExternalReloadTriggered();
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new()); WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
@@ -815,13 +819,13 @@ public sealed partial class MainWindow : WindowEx
private void UnregisterHotkeys() private void UnregisterHotkeys()
{ {
_keyboardListener.ClearHotkeys(); // _keyboardListener.ClearHotkeys();
while (_hotkeys.Count > 0) // while (_hotkeys.Count > 0)
{ // {
PInvoke.UnregisterHotKey(_hwnd, _hotkeys.Count - 1); // PInvoke.UnregisterHotKey(_hwnd, _hotkeys.Count - 1);
_hotkeys.RemoveAt(_hotkeys.Count - 1); // _hotkeys.RemoveAt(_hotkeys.Count - 1);
} // }
} }
private void SetupHotkey(SettingsModel settings) private void SetupHotkey(SettingsModel settings)
@@ -833,9 +837,8 @@ public sealed partial class MainWindow : WindowEx
{ {
if (settings.UseLowLevelGlobalHotkey) if (settings.UseLowLevelGlobalHotkey)
{ {
_keyboardListener.SetHotkeyAction(globalHotkey.Win, globalHotkey.Ctrl, globalHotkey.Shift, globalHotkey.Alt, (byte)globalHotkey.Code, string.Empty); // _keyboardListener.SetHotkeyAction(globalHotkey.Win, globalHotkey.Ctrl, globalHotkey.Shift, globalHotkey.Alt, (byte)globalHotkey.Code, string.Empty);
// _hotkeys.Add(new(globalHotkey, string.Empty));
_hotkeys.Add(new(globalHotkey, string.Empty));
} }
else else
{ {
@@ -847,44 +850,41 @@ public sealed partial class MainWindow : WindowEx
(globalHotkey.Win ? HOT_KEY_MODIFIERS.MOD_WIN : 0) (globalHotkey.Win ? HOT_KEY_MODIFIERS.MOD_WIN : 0)
; ;
var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk); // var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk);
if (success) // if (success)
{ // {
_hotkeys.Add(new(globalHotkey, string.Empty)); // _hotkeys.Add(new(globalHotkey, string.Empty));
} // }
} }
} }
foreach (var commandHotkey in settings.CommandHotkeys) // foreach (var commandHotkey in settings.CommandHotkeys)
{ // {
var key = commandHotkey.Hotkey; // var key = commandHotkey.Hotkey;
// if (key is not null)
if (key is not null) // {
{ // if (settings.UseLowLevelGlobalHotkey)
if (settings.UseLowLevelGlobalHotkey) // {
{ // _keyboardListener.SetHotkeyAction(key.Win, key.Ctrl, key.Shift, key.Alt, (byte)key.Code, commandHotkey.CommandId);
_keyboardListener.SetHotkeyAction(key.Win, key.Ctrl, key.Shift, key.Alt, (byte)key.Code, commandHotkey.CommandId); // _hotkeys.Add(new(globalHotkey, string.Empty));
// }
_hotkeys.Add(new(globalHotkey, string.Empty)); // else
} // {
else // var vk = key.Code;
{ // var modifiers =
var vk = key.Code; // (key.Alt ? HOT_KEY_MODIFIERS.MOD_ALT : 0) |
var modifiers = // (key.Ctrl ? HOT_KEY_MODIFIERS.MOD_CONTROL : 0) |
(key.Alt ? HOT_KEY_MODIFIERS.MOD_ALT : 0) | // (key.Shift ? HOT_KEY_MODIFIERS.MOD_SHIFT : 0) |
(key.Ctrl ? HOT_KEY_MODIFIERS.MOD_CONTROL : 0) | // (key.Win ? HOT_KEY_MODIFIERS.MOD_WIN : 0)
(key.Shift ? HOT_KEY_MODIFIERS.MOD_SHIFT : 0) | // ;
(key.Win ? HOT_KEY_MODIFIERS.MOD_WIN : 0) // var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk);
; // if (success)
// {
var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk); // _hotkeys.Add(commandHotkey);
if (success) // }
{ // }
_hotkeys.Add(commandHotkey); // }
} // }
}
}
}
} }
private void HandleSummon(string commandId) private void HandleSummon(string commandId)
@@ -904,7 +904,7 @@ public sealed partial class MainWindow : WindowEx
private void HandleSummonCore(string commandId) private void HandleSummonCore(string commandId)
{ {
var isRootHotkey = string.IsNullOrEmpty(commandId); var isRootHotkey = string.IsNullOrEmpty(commandId);
PowerToysTelemetry.Log.WriteEvent(new CmdPalHotkeySummoned(isRootHotkey)); PowerToysTelemetry.Log.WriteEvent(new HotkeySummonedEvent(isRootHotkey));
var isVisible = this.Visible; var isVisible = this.Visible;
@@ -959,12 +959,12 @@ public sealed partial class MainWindow : WindowEx
case PInvoke.WM_HOTKEY: case PInvoke.WM_HOTKEY:
{ {
var hotkeyIndex = (int)wParam.Value; var hotkeyIndex = (int)wParam.Value;
if (hotkeyIndex < _hotkeys.Count)
{
var hotkey = _hotkeys[hotkeyIndex];
HandleSummon(hotkey.CommandId);
}
// if (hotkeyIndex < _hotkeys.Count)
// {
// var hotkey = _hotkeys[hotkeyIndex];
// HandleSummon(hotkey.CommandId);
// }
return (LRESULT)IntPtr.Zero; return (LRESULT)IntPtr.Zero;
} }
@@ -982,7 +982,7 @@ public sealed partial class MainWindow : WindowEx
public void Dispose() public void Dispose()
{ {
_localKeyboardListener.Dispose(); // _localKeyboardListener.Dispose();
DisposeAcrylic(); DisposeAcrylic();
} }

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Page <Page
x:Class="Microsoft.CommandPalette.UI.Pages.ShellPage" x:Class="Microsoft.CommandPalette.UI.Pages.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

View File

@@ -5,8 +5,8 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.Models.Messages;
using Microsoft.CommandPalette.UI.Models; using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Windows.Win32; using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
@@ -19,7 +19,7 @@ namespace Microsoft.CommandPalette.UI.Helpers;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
internal sealed partial class TrayIconService public sealed partial class TrayIconService
{ {
private const uint MY_NOTIFY_ID = 1000; private const uint MY_NOTIFY_ID = 1000;
private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1; private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1;