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.Pages;
using Microsoft.CommandPalette.UI.ViewModels.Helpers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI;
@@ -36,15 +34,22 @@ using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.HiDpi;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT;
using WinUIEx;
using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
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 ShellPage shellPage;
private readonly ShellPage _shellPage;
private readonly SettingsModel _settingsModel;
private readonly TrayIconService _trayIconService;
private const int DefaultWidth = 800;
private const int DefaultHeight = 480;
@@ -68,10 +73,16 @@ public sealed partial class MainWindow : WindowEx
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.shellPage = shellPage;
_shellPage = shellPage;
_settingsModel = settingsModel;
_trayIconService = trayIconService;
RootElement.Children.Add(_shellPage);
_autoGoHomeTimer = new DispatcherTimer();
_autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick;
@@ -94,10 +105,11 @@ public sealed partial class MainWindow : WindowEx
SetAcrylic();
// WeakReferenceMessenger.Default.Register<DismissMessage>(this);
// WeakReferenceMessenger.Default.Register<QuitMessage>(this);
// WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
// WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
WeakReferenceMessenger.Default.Register<DismissMessage>(this);
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
WeakReferenceMessenger.Default.Register<HideWindowMessage>(this);
// Hide our titlebar.
// We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed
// 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
HotReloadSettings();
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
_settingsModel.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes
RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
@@ -137,11 +149,39 @@ public sealed partial class MainWindow : WindowEx
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)
{
_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.
WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false));
}
@@ -178,8 +218,7 @@ public sealed partial class MainWindow : WindowEx
private void RestoreWindowPosition()
{
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.LastWindowPosition is not WindowPosition savedPosition)
if (_settingsModel.LastWindowPosition is not WindowPosition savedPosition)
{
PositionCentered();
return;
@@ -226,14 +265,12 @@ public sealed partial class MainWindow : WindowEx
private void HotReloadSettings()
{
var settings = App.Current.Services.GetService<SettingsModel>()!;
SetupHotkey(_settingsModel);
_trayIconService.SetupTrayIcon(_settingsModel.ShowSystemTrayIcon);
SetupHotkey(settings);
App.Current.Services.GetService<TrayIconService>()!.SetupTrayIcon(settings.ShowSystemTrayIcon);
_ignoreHotKeyWhenFullScreen = _settingsModel.IgnoreShortcutWhenFullscreen;
_ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen;
_autoGoHomeInterval = settings.AutoGoHomeInterval;
_autoGoHomeInterval = _settingsModel.AutoGoHomeInterval;
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
}
@@ -497,36 +534,6 @@ public sealed partial class MainWindow : WindowEx
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()
{
// 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));
if (hr.Failed)
{
Logger.LogWarning($"DWM cloaking of the main window failed. HRESULT: {hr.Value}.");
Log_DwmCloakingFailed(hr);
}
wasCloaked = hr.Succeeded;
@@ -607,13 +614,11 @@ public sealed partial class MainWindow : WindowEx
internal void MainWindow_Closed(object sender, WindowEventArgs args)
{
var serviceProvider = App.Current.Services;
UpdateWindowPositionInMemory();
var settings = serviceProvider.GetService<SettingsModel>();
if (settings is not null)
if (_settingsModel is not null)
{
settings.LastWindowPosition = new WindowPosition
_settingsModel.LastWindowPosition = new WindowPosition
{
X = _currentWindowPosition.X,
Y = _currentWindowPosition.Y,
@@ -624,12 +629,12 @@ public sealed partial class MainWindow : WindowEx
ScreenHeight = _currentWindowPosition.ScreenHeight,
};
SettingsModel.SaveSettings(settings, logger);
SettingsModel.SaveSettings(_settingsModel, logger);
}
// var extensionService = serviceProvider.GetService<IExtensionService>()!;
// 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).
// 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))
{
var settings = App.Current.Services.GetService<SettingsModel>();
if (settings?.AllowExternalReload == true)
if (_settingsModel.AllowExternalReload == true)
{
Log_ExternalReloadTriggered();
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
@@ -815,13 +819,13 @@ public sealed partial class MainWindow : WindowEx
private void UnregisterHotkeys()
{
_keyboardListener.ClearHotkeys();
// _keyboardListener.ClearHotkeys();
while (_hotkeys.Count > 0)
{
PInvoke.UnregisterHotKey(_hwnd, _hotkeys.Count - 1);
_hotkeys.RemoveAt(_hotkeys.Count - 1);
}
// while (_hotkeys.Count > 0)
// {
// PInvoke.UnregisterHotKey(_hwnd, _hotkeys.Count - 1);
// _hotkeys.RemoveAt(_hotkeys.Count - 1);
// }
}
private void SetupHotkey(SettingsModel settings)
@@ -833,9 +837,8 @@ public sealed partial class MainWindow : WindowEx
{
if (settings.UseLowLevelGlobalHotkey)
{
_keyboardListener.SetHotkeyAction(globalHotkey.Win, globalHotkey.Ctrl, globalHotkey.Shift, globalHotkey.Alt, (byte)globalHotkey.Code, string.Empty);
_hotkeys.Add(new(globalHotkey, string.Empty));
// _keyboardListener.SetHotkeyAction(globalHotkey.Win, globalHotkey.Ctrl, globalHotkey.Shift, globalHotkey.Alt, (byte)globalHotkey.Code, string.Empty);
// _hotkeys.Add(new(globalHotkey, string.Empty));
}
else
{
@@ -847,44 +850,41 @@ public sealed partial class MainWindow : WindowEx
(globalHotkey.Win ? HOT_KEY_MODIFIERS.MOD_WIN : 0)
;
var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk);
if (success)
{
_hotkeys.Add(new(globalHotkey, string.Empty));
}
// var success = PInvoke.RegisterHotKey(_hwnd, _hotkeys.Count, modifiers, (uint)vk);
// if (success)
// {
// _hotkeys.Add(new(globalHotkey, string.Empty));
// }
}
}
foreach (var commandHotkey in settings.CommandHotkeys)
{
var key = commandHotkey.Hotkey;
if (key is not null)
{
if (settings.UseLowLevelGlobalHotkey)
{
_keyboardListener.SetHotkeyAction(key.Win, key.Ctrl, key.Shift, key.Alt, (byte)key.Code, commandHotkey.CommandId);
_hotkeys.Add(new(globalHotkey, string.Empty));
}
else
{
var vk = key.Code;
var modifiers =
(key.Alt ? HOT_KEY_MODIFIERS.MOD_ALT : 0) |
(key.Ctrl ? HOT_KEY_MODIFIERS.MOD_CONTROL : 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)
{
_hotkeys.Add(commandHotkey);
}
}
}
}
// foreach (var commandHotkey in settings.CommandHotkeys)
// {
// var key = commandHotkey.Hotkey;
// if (key is not null)
// {
// if (settings.UseLowLevelGlobalHotkey)
// {
// _keyboardListener.SetHotkeyAction(key.Win, key.Ctrl, key.Shift, key.Alt, (byte)key.Code, commandHotkey.CommandId);
// _hotkeys.Add(new(globalHotkey, string.Empty));
// }
// else
// {
// var vk = key.Code;
// var modifiers =
// (key.Alt ? HOT_KEY_MODIFIERS.MOD_ALT : 0) |
// (key.Ctrl ? HOT_KEY_MODIFIERS.MOD_CONTROL : 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)
// {
// _hotkeys.Add(commandHotkey);
// }
// }
// }
// }
}
private void HandleSummon(string commandId)
@@ -904,7 +904,7 @@ public sealed partial class MainWindow : WindowEx
private void HandleSummonCore(string commandId)
{
var isRootHotkey = string.IsNullOrEmpty(commandId);
PowerToysTelemetry.Log.WriteEvent(new CmdPalHotkeySummoned(isRootHotkey));
PowerToysTelemetry.Log.WriteEvent(new HotkeySummonedEvent(isRootHotkey));
var isVisible = this.Visible;
@@ -959,12 +959,12 @@ public sealed partial class MainWindow : WindowEx
case PInvoke.WM_HOTKEY:
{
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;
}
@@ -982,7 +982,7 @@ public sealed partial class MainWindow : WindowEx
public void Dispose()
{
_localKeyboardListener.Dispose();
// _localKeyboardListener.Dispose();
DisposeAcrylic();
}

View File

@@ -5,8 +5,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.Models.Messages;
using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.UI.Xaml;
using Windows.Win32;
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", "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 WM_TRAY_ICON = PInvoke.WM_USER + 1;