// 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.Diagnostics; using System.Runtime.InteropServices; using CmdPalKeyboardService; using CommunityToolkit.Mvvm.Messaging; using ManagedCommon; using Microsoft.CmdPal.Core.Common.Helpers; using Microsoft.CmdPal.Core.Common.Services; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Ext.ClipboardHistory.Messages; using Microsoft.CmdPal.UI.Controls; using Microsoft.CmdPal.UI.Events; using Microsoft.CmdPal.UI.Helpers; using Microsoft.CmdPal.UI.Messages; using Microsoft.CmdPal.UI.Services; using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.PowerToys.Telemetry; using Microsoft.UI; using Microsoft.UI.Composition; using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.Windows.AppLifecycle; using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Graphics; using Windows.System; using Windows.UI; using Windows.UI.WindowManagement; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; 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.CmdPal.UI.Helpers.ResourceLoaderInstance; namespace Microsoft.CmdPal.UI; public sealed partial class MainWindow : WindowEx, IRecipient, IRecipient, IRecipient, IRecipient, IRecipient, IRecipient, IDisposable { private const int DefaultWidth = 800; private const int DefaultHeight = 480; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_")] private readonly uint WM_TASKBAR_RESTART; private readonly HWND _hwnd; private readonly DispatcherTimer _autoGoHomeTimer; private readonly WNDPROC? _hotkeyWndProc; private readonly WNDPROC? _originalWndProc; private readonly List _hotkeys = []; private readonly KeyboardListener _keyboardListener; private readonly LocalKeyboardListener _localKeyboardListener; private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new(); private readonly IThemeService _themeService; private readonly WindowThemeSynchronizer _windowThemeSynchronizer; private bool _ignoreHotKeyWhenFullScreen = true; private bool _themeServiceInitialized; private DesktopAcrylicController? _acrylicController; private SystemBackdropConfiguration? _configurationSource; private TimeSpan _autoGoHomeInterval = Timeout.InfiniteTimeSpan; private WindowPosition _currentWindowPosition = new(); private bool _preventHideWhenDeactivated; private MainWindowViewModel ViewModel { get; } public MainWindow() { InitializeComponent(); ViewModel = App.Current.Services.GetService()!; _autoGoHomeTimer = new DispatcherTimer(); _autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick; _themeService = App.Current.Services.GetRequiredService(); _themeService.ThemeChanged += ThemeServiceOnThemeChanged; _windowThemeSynchronizer = new WindowThemeSynchronizer(_themeService, this); _hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32()); unsafe { CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value); } SetAcrylic(); _hiddenOwnerBehavior.ShowInTaskbar(this, Debugger.IsAttached); _keyboardListener = new KeyboardListener(); _keyboardListener.Start(); _keyboardListener.SetProcessCommand(new CmdPalKeyboardService.ProcessCommand(HandleSummon)); this.SetIcon(); AppWindow.Title = RS_.GetString("AppName"); RestoreWindowPosition(); UpdateWindowPositionInMemory(); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); // Hide our titlebar. // We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed // to hide the old caption buttons. Then, in UpdateRegionsForCustomTitleBar, // we'll make the top drag-able again. (after our content loads) ExtendsContentIntoTitleBar = true; AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed; SizeChanged += WindowSizeChanged; RootElement.Loaded += RootElementLoaded; WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated"); // LOAD BEARING: If you don't stick the pointer to HotKeyPrc into a // member (and instead like, use a local), then the pointer we marshal // into the WindowLongPtr will be useless after we leave this function, // and our **WindProc will explode**. _hotkeyWndProc = HotKeyPrc; var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_hotkeyWndProc); _originalWndProc = Marshal.GetDelegateForFunctionPointer(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer)); // Load our settings, and then also wire up a settings changed handler HotReloadSettings(); App.Current.Services.GetService()!.SettingsChanged += SettingsChangedHandler; // Make sure that we update the acrylic theme when the OS theme changes RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic); // Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () => { Summon(string.Empty); }); _localKeyboardListener = new LocalKeyboardListener(); _localKeyboardListener.KeyPressed += LocalKeyboardListener_OnKeyPressed; _localKeyboardListener.Start(); // Force window to be created, and then cloaked. This will offset initial animation when the window is shown. 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) // and prevent the user from opening its context menu. WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false)); } private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e) { UpdateAcrylic(); } private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e) { if (e.Key == VirtualKey.GoBack) { WeakReferenceMessenger.Default.Send(new GoBackMessage()); } } private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings(); private void RootElementLoaded(object sender, RoutedEventArgs e) { // Now that our content has loaded, we can update our draggable regions UpdateRegionsForCustomTitleBar(); // Add dev ribbon if enabled if (!BuildInfo.IsCiBuild) { RootElement.Children.Add(new DevRibbon { Margin = new Thickness(-1, -1, 120, -1) }); } } private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs args) => UpdateRegionsForCustomTitleBar(); private void PositionCentered() { var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest); PositionCentered(displayArea); } private void RestoreWindowPosition() { var settings = App.Current.Services.GetService(); if (settings?.LastWindowPosition is not WindowPosition savedPosition) { PositionCentered(); return; } if (savedPosition.Width <= 0 || savedPosition.Height <= 0) { PositionCentered(); return; } var newRect = EnsureWindowIsVisible(savedPosition.ToPhysicalWindowRectangle(), new SizeInt32(savedPosition.ScreenWidth, savedPosition.ScreenHeight), savedPosition.Dpi); AppWindow.MoveAndResize(newRect); } private void PositionCentered(DisplayArea displayArea) { if (displayArea is not null) { var centeredPosition = AppWindow.Position; centeredPosition.X = (displayArea.WorkArea.Width - AppWindow.Size.Width) / 2; centeredPosition.Y = (displayArea.WorkArea.Height - AppWindow.Size.Height) / 2; centeredPosition.X += displayArea.WorkArea.X; centeredPosition.Y += displayArea.WorkArea.Y; AppWindow.Move(centeredPosition); } } private void UpdateWindowPositionInMemory() { var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest) ?? DisplayArea.Primary; _currentWindowPosition = new WindowPosition { X = AppWindow.Position.X, Y = AppWindow.Position.Y, Width = AppWindow.Size.Width, Height = AppWindow.Size.Height, Dpi = (int)this.GetDpiForWindow(), ScreenWidth = displayArea.WorkArea.Width, ScreenHeight = displayArea.WorkArea.Height, }; } private void HotReloadSettings() { var settings = App.Current.Services.GetService()!; SetupHotkey(settings); App.Current.Services.GetService()!.SetupTrayIcon(settings.ShowSystemTrayIcon); _ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen; _autoGoHomeInterval = settings.AutoGoHomeInterval; _autoGoHomeTimer.Interval = _autoGoHomeInterval; } private void SetAcrylic() { if (DesktopAcrylicController.IsSupported()) { // Hooking up the policy object. _configurationSource = new SystemBackdropConfiguration { // Initial configuration state. IsInputActive = true, }; UpdateAcrylic(); } } private void UpdateAcrylic() { try { if (_acrylicController != null) { _acrylicController.RemoveAllSystemBackdropTargets(); _acrylicController.Dispose(); } var backdrop = _themeService.Current.BackdropParameters; _acrylicController = new DesktopAcrylicController { TintColor = backdrop.TintColor, TintOpacity = backdrop.TintOpacity, FallbackColor = backdrop.FallbackColor, LuminosityOpacity = backdrop.LuminosityOpacity, }; // Enable the system backdrop. // Note: Be sure to have "using WinRT;" to support the Window.As<...>() call. _acrylicController.AddSystemBackdropTarget(this.As()); _acrylicController.SetSystemBackdropConfiguration(_configurationSource); } catch (Exception ex) { Logger.LogError("Failed to update backdrop", ex); } } private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target) { StopAutoGoHome(); var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd); // Remember, IsIconic == "minimized", which is entirely different state // from "show/hide" // If we're currently minimized, restore us first, before we reveal // our window. Otherwise, we'd just be showing a minimized window - // which would remain not visible to the user. if (PInvoke.IsIconic(hwnd)) { // Make sure our HWND is cloaked before any possible window manipulations Cloak(); PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_RESTORE); } if (target == MonitorBehavior.ToLast) { var newRect = EnsureWindowIsVisible(_currentWindowPosition.ToPhysicalWindowRectangle(), new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight), _currentWindowPosition.Dpi); AppWindow.MoveAndResize(newRect); } else { var display = GetScreen(hwnd, target); PositionCentered(display); } // Check if the debugger is attached. If it is, we don't want to apply the tool window style, // because that would make it hard to debug the app if (Debugger.IsAttached) { _hiddenOwnerBehavior.ShowInTaskbar(this, true); } // Just to be sure, SHOW our hwnd. PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOW); // Once we're done, uncloak to avoid all animations Uncloak(); PInvoke.SetForegroundWindow(hwnd); PInvoke.SetActiveWindow(hwnd); // Push our window to the top of the Z-order and make it the topmost, so that it appears above all other windows. // We want to remove the topmost status when we hide the window (because we cloak it instead of hiding it). PInvoke.SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE); } /// /// Ensures that the window rectangle is visible on-screen. /// /// The window rectangle in physical pixels. /// The desktop area the window was positioned on. /// The window's original DPI. /// /// A window rectangle in physical pixels, moved to the nearest display and resized /// if the DPI has changed. /// private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi) { var displayArea = DisplayArea.GetFromRect(windowRect, DisplayAreaFallback.Nearest); if (displayArea is null) { return windowRect; } var workArea = displayArea.WorkArea; if (workArea.Width <= 0 || workArea.Height <= 0) { // Fallback, nothing reasonable to do return windowRect; } var effectiveDpi = GetEffectiveDpiFromDisplayId(displayArea); if (originalDpi <= 0) { originalDpi = effectiveDpi; // use current DPI as baseline (no scaling adjustment needed) } var hasInvalidSize = windowRect.Width <= 0 || windowRect.Height <= 0; if (hasInvalidSize) { windowRect = new RectInt32(windowRect.X, windowRect.Y, DefaultWidth, DefaultHeight); } // If we have a DPI change, scale the window rectangle accordingly if (effectiveDpi != originalDpi) { var scalingFactor = effectiveDpi / (double)originalDpi; windowRect = new RectInt32( (int)Math.Round(windowRect.X * scalingFactor), (int)Math.Round(windowRect.Y * scalingFactor), (int)Math.Round(windowRect.Width * scalingFactor), (int)Math.Round(windowRect.Height * scalingFactor)); } var targetWidth = Math.Min(windowRect.Width, workArea.Width); var targetHeight = Math.Min(windowRect.Height, workArea.Height); // Ensure at least some minimum visible area (e.g., 100 pixels) // This helps prevent the window from being entirely offscreen, regardless of display scaling. const int minimumVisibleSize = 100; var isOffscreen = windowRect.X + minimumVisibleSize > workArea.X + workArea.Width || windowRect.X + windowRect.Width - minimumVisibleSize < workArea.X || windowRect.Y + minimumVisibleSize > workArea.Y + workArea.Height || windowRect.Y + windowRect.Height - minimumVisibleSize < workArea.Y; // if the work area size has changed, re-center the window var workAreaSizeChanged = originalScreen.Width != workArea.Width || originalScreen.Height != workArea.Height; int targetX; int targetY; var recenter = isOffscreen || workAreaSizeChanged || hasInvalidSize; if (recenter) { targetX = workArea.X + ((workArea.Width - targetWidth) / 2); targetY = workArea.Y + ((workArea.Height - targetHeight) / 2); } else { targetX = windowRect.X; targetY = windowRect.Y; } return new RectInt32(targetX, targetY, targetWidth, targetHeight); } private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea) { var effectiveDpi = 96; var hMonitor = (HMONITOR)Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId); if (!hMonitor.IsNull) { var hr = PInvoke.GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _); if (hr == 0) { effectiveDpi = (int)dpiX; } else { Logger.LogWarning($"GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {displayArea.DisplayId}"); } } if (effectiveDpi <= 0) { effectiveDpi = 96; } return effectiveDpi; } private DisplayArea GetScreen(HWND currentHwnd, MonitorBehavior target) { // Leaving a note here, in case we ever need it: // https://github.com/microsoft/microsoft-ui-xaml/issues/6454 // If we need to ever FindAll, we'll need to iterate manually var displayAreas = Microsoft.UI.Windowing.DisplayArea.FindAll(); switch (target) { case MonitorBehavior.InPlace: if (PInvoke.GetWindowRect(currentHwnd, out var bounds)) { RectInt32 converted = new(bounds.X, bounds.Y, bounds.Width, bounds.Height); return DisplayArea.GetFromRect(converted, DisplayAreaFallback.Nearest); } break; case MonitorBehavior.ToFocusedWindow: var foregroundWindowHandle = PInvoke.GetForegroundWindow(); if (foregroundWindowHandle != IntPtr.Zero) { if (PInvoke.GetWindowRect(foregroundWindowHandle, out var fgBounds)) { RectInt32 converted = new(fgBounds.X, fgBounds.Y, fgBounds.Width, fgBounds.Height); return DisplayArea.GetFromRect(converted, DisplayAreaFallback.Nearest); } } break; case MonitorBehavior.ToPrimary: return DisplayArea.Primary; case MonitorBehavior.ToMouse: default: if (PInvoke.GetCursorPos(out var cursorPos)) { return DisplayArea.GetFromPoint(new PointInt32(cursorPos.X, cursorPos.Y), DisplayAreaFallback.Nearest); } break; } return DisplayArea.Primary; } public void Receive(ShowWindowMessage message) { var settings = App.Current.Services.GetService()!; 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) { if (message.ForceGoHome) { WeakReferenceMessenger.Default.Send(new GoHomeMessage(false, false)); } // 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. var cloaked = Cloak(); // Then hide our HWND, to make sure that the OS gives the FG / focus back to another app // (there's no way for us to guess what the right hwnd might be, only the OS can do it right) PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_HIDE); if (cloaked) { // TRICKY: show our HWND again. This will trick XAML into painting our // HWND again, so that we avoid the "flicker" caused by a WinUI3 app // window being first shown // SW_SHOWNA will prevent us for trying to fight the focus back PInvoke.ShowWindow(_hwnd, SHOW_WINDOW_CMD.SW_SHOWNA); // Intentionally leave the window cloaked. So our window is "visible", // but also cloaked, so you can't see it. // If the window was not cloaked, then leave it hidden. // Sure, it's not ideal, but at least it's not visible. } // Start auto-go-home timer RestartAutoGoHome(); } private void StopAutoGoHome() { _autoGoHomeTimer.Stop(); } private void RestartAutoGoHome() { if (_autoGoHomeInterval == Timeout.InfiniteTimeSpan) { return; } _autoGoHomeTimer.Stop(); _autoGoHomeTimer.Start(); } private bool Cloak() { bool wasCloaked; unsafe { BOOL value = true; 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}."); } wasCloaked = hr.Succeeded; } if (wasCloaked) { // Because we're only cloaking the window, bury it at the bottom in case something can // see it - e.g. some accessibility helper (note: this also removes the top-most status). PInvoke.SetWindowPos(_hwnd, HWND.HWND_BOTTOM, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE); } return wasCloaked; } private void Uncloak() { unsafe { BOOL value = false; PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAK, &value, (uint)sizeof(BOOL)); } } internal void MainWindow_Closed(object sender, WindowEventArgs args) { var serviceProvider = App.Current.Services; UpdateWindowPositionInMemory(); var settings = serviceProvider.GetService(); if (settings is not null) { settings.LastWindowPosition = new WindowPosition { X = _currentWindowPosition.X, Y = _currentWindowPosition.Y, Width = _currentWindowPosition.Width, Height = _currentWindowPosition.Height, Dpi = _currentWindowPosition.Dpi, ScreenWidth = _currentWindowPosition.ScreenWidth, ScreenHeight = _currentWindowPosition.ScreenHeight, }; SettingsModel.SaveSettings(settings); } var extensionService = serviceProvider.GetService()!; extensionService.SignalStopExtensionsAsync(); App.Current.Services.GetService()!.Destroy(); // WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592). // Workaround by turning it off before shutdown. App.Current.DebugSettings.FailFastOnErrors = false; _localKeyboardListener.Dispose(); DisposeAcrylic(); _keyboardListener.Stop(); Environment.Exit(0); } private void DisposeAcrylic() { if (_acrylicController is not null) { _acrylicController.Dispose(); _acrylicController = null!; _configurationSource = null!; } } // Updates our window s.t. the top of the window is draggable. private void UpdateRegionsForCustomTitleBar() { // Specify the interactive regions of the title bar. var scaleAdjustment = RootElement.XamlRoot.RasterizationScale; // Get the rectangle around our XAML content. We're going to mark this // rectangle as "Passthrough", so that the normal window operations // (resizing, dragging) don't apply in this space. var transform = RootElement.TransformToVisual(null); // Reserve 16px of space at the top for dragging. var topHeight = 16; var bounds = transform.TransformBounds(new Rect( 0, topHeight, RootElement.ActualWidth, RootElement.ActualHeight)); var contentRect = GetRect(bounds, scaleAdjustment); var rectArray = new RectInt32[] { contentRect }; var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id); nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray); // Add a drag-able region on top var w = RootElement.ActualWidth; _ = RootElement.ActualHeight; var dragSides = new RectInt32[] { GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall }; nonClientInputSrc.SetRegionRects(NonClientRegionKind.Caption, dragSides); } private static RectInt32 GetRect(Rect bounds, double scale) { return new RectInt32( _X: (int)Math.Round(bounds.X * scale), _Y: (int)Math.Round(bounds.Y * scale), _Width: (int)Math.Round(bounds.Width * scale), _Height: (int)Math.Round(bounds.Height * scale)); } internal void MainWindow_Activated(object sender, WindowActivatedEventArgs args) { if (!_themeServiceInitialized && args.WindowActivationState != WindowActivationState.Deactivated) { try { _themeService.Initialize(); _themeServiceInitialized = true; } catch (Exception ex) { Logger.LogError("Failed to initialize ThemeService", ex); } } if (args.WindowActivationState == WindowActivationState.Deactivated) { // Save the current window position before hiding the window UpdateWindowPositionInMemory(); // If there's a debugger attached... if (System.Diagnostics.Debugger.IsAttached) { // ... then don't hide the window when it loses focus. return; } // Are we disabled? If we are, then we don't want to dismiss on focus lost. // This can happen if an extension wanted to show a modal dialog on top of our // window i.e. in the case of an MSAL auth window. if (PInvoke.IsWindowEnabled(_hwnd) == 0) { return; } // We're doing something that requires us to lose focus, but we don't want to hide the window if (_preventHideWhenDeactivated) { return; } // This will DWM cloak our window: HideWindow(); PowerToysTelemetry.Log.WriteEvent(new CmdPalDismissedOnLostFocus()); } if (_configurationSource is not null) { _configurationSource.IsInputActive = args.WindowActivationState != WindowActivationState.Deactivated; } } public void HandleLaunchNonUI(AppActivationArguments? activatedEventArgs) { // LOAD BEARING // Any reading and processing of the activation arguments must be done // synchronously in this method, before it returns. The sending instance // remains blocked until this returns; afterward it may quit, causing // the activation arguments to be lost. if (activatedEventArgs is null) { Summon(string.Empty); return; } try { if (activatedEventArgs.Kind == ExtendedActivationKind.StartupTask) { return; } if (activatedEventArgs.Kind == ExtendedActivationKind.Protocol) { if (activatedEventArgs.Data is IProtocolActivatedEventArgs protocolArgs) { if (protocolArgs.Uri.ToString() is string uri) { // was the URI "x-cmdpal://background" ? if (uri.StartsWith("x-cmdpal://background", StringComparison.OrdinalIgnoreCase)) { // we're running, we don't want to activate our window. bail return; } else if (uri.StartsWith("x-cmdpal://settings", StringComparison.OrdinalIgnoreCase)) { WeakReferenceMessenger.Default.Send(new()); return; } else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase)) { var settings = App.Current.Services.GetService(); if (settings?.AllowExternalReload == true) { Logger.LogInfo("External Reload triggered"); WeakReferenceMessenger.Default.Send(new()); } else { Logger.LogInfo("External Reload is disabled"); } return; } } } } } catch (COMException ex) { // https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-return-values const int RPC_S_SERVER_UNAVAILABLE = -2147023174; const int RPC_S_CALL_FAILED = 2147023170; // Accessing properties activatedEventArgs.Kind and activatedEventArgs.Data might cause COMException // if the args are not valid or not passed correctly. if (ex.HResult is RPC_S_SERVER_UNAVAILABLE or RPC_S_CALL_FAILED) { Logger.LogWarning( $"COM exception (HRESULT {ex.HResult}) when accessing activation arguments. " + $"This might be due to the calling application not passing them correctly or exiting before we could read them. " + $"The application will continue running and fall back to showing the Command Palette window."); } else { Logger.LogError( $"COM exception (HRESULT {ex.HResult}) when activating the application. " + $"The application will continue running and fall back to showing the Command Palette window.", ex); } } Summon(string.Empty); } public void Summon(string commandId) => // The actual showing and hiding of the window will be done by the // ShellPage. This is because we don't want to show the window if the // user bound a hotkey to just an invokable command, which we can't // know till the message is being handled. WeakReferenceMessenger.Default.Send(new(commandId, _hwnd)); private void UnregisterHotkeys() { _keyboardListener.ClearHotkeys(); while (_hotkeys.Count > 0) { PInvoke.UnregisterHotKey(_hwnd, _hotkeys.Count - 1); _hotkeys.RemoveAt(_hotkeys.Count - 1); } } private void SetupHotkey(SettingsModel settings) { UnregisterHotkeys(); var globalHotkey = settings.Hotkey; if (globalHotkey is not null) { if (settings.UseLowLevelGlobalHotkey) { _keyboardListener.SetHotkeyAction(globalHotkey.Win, globalHotkey.Ctrl, globalHotkey.Shift, globalHotkey.Alt, (byte)globalHotkey.Code, string.Empty); _hotkeys.Add(new(globalHotkey, string.Empty)); } else { var vk = globalHotkey.Code; var modifiers = (globalHotkey.Alt ? HOT_KEY_MODIFIERS.MOD_ALT : 0) | (globalHotkey.Ctrl ? HOT_KEY_MODIFIERS.MOD_CONTROL : 0) | (globalHotkey.Shift ? HOT_KEY_MODIFIERS.MOD_SHIFT : 0) | (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)); } } } 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) { if (_ignoreHotKeyWhenFullScreen) { // If we're in full screen mode, ignore the hotkey if (WindowHelper.IsWindowFullscreen()) { return; } } HandleSummonCore(commandId); } private void HandleSummonCore(string commandId) { var isRootHotkey = string.IsNullOrEmpty(commandId); PowerToysTelemetry.Log.WriteEvent(new CmdPalHotkeySummoned(isRootHotkey)); var isVisible = this.Visible; unsafe { // We need to check if our window is cloaked or not. A cloaked window is still // technically visible, because SHOW/HIDE != iconic (minimized) != cloaked // (these are all separate states) long attr = 0; PInvoke.DwmGetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_CLOAKED, &attr, sizeof(long)); if (attr == 1 /* DWM_CLOAKED_APP */) { isVisible = false; } } // Note to future us: the wParam will have the index of the hotkey we registered. // We can use that in the future to differentiate the hotkeys we've pressed // so that we can bind hotkeys to individual commands if (!isVisible || !isRootHotkey) { Summon(commandId); } else if (isRootHotkey) { // If there's a debugger attached... if (System.Diagnostics.Debugger.IsAttached) { // ... then manually hide our window. When debugged, we won't get the cool cloaking, // but that's the price to pay for having the HWND not light-dismiss while we're debugging. Cloak(); this.Hide(); return; } HideWindow(); } } private LRESULT HotKeyPrc( HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { // Prevent the window from maximizing when double-clicking the title bar area case PInvoke.WM_NCLBUTTONDBLCLK: return (LRESULT)IntPtr.Zero; case PInvoke.WM_HOTKEY: { var hotkeyIndex = (int)wParam.Value; if (hotkeyIndex < _hotkeys.Count) { var hotkey = _hotkeys[hotkeyIndex]; HandleSummon(hotkey.CommandId); } return (LRESULT)IntPtr.Zero; } default: if (uMsg == WM_TASKBAR_RESTART) { HotReloadSettings(); } break; } return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam); } public void Dispose() { _localKeyboardListener.Dispose(); _windowThemeSynchronizer.Dispose(); DisposeAcrylic(); } public void Receive(DragStartedMessage message) { _preventHideWhenDeactivated = true; } public void Receive(DragCompletedMessage message) { _preventHideWhenDeactivated = false; Task.Delay(200).ContinueWith(_ => { DispatcherQueue.TryEnqueue(StealForeground); }); } private unsafe void StealForeground() { var foregroundWindow = PInvoke.GetForegroundWindow(); if (foregroundWindow == _hwnd) { return; } // This is bad, evil, and I'll have to forgo today's dinner dessert to punish myself // for writing this. But there's no way to make this work without it. // If the window is not reactivated, the UX breaks down: a deactivated window has to // be activated and then deactivated again to hide. var currentThreadId = PInvoke.GetCurrentThreadId(); var foregroundThreadId = PInvoke.GetWindowThreadProcessId(foregroundWindow, null); if (foregroundThreadId != currentThreadId) { PInvoke.AttachThreadInput(currentThreadId, foregroundThreadId, true); PInvoke.SetForegroundWindow(_hwnd); PInvoke.AttachThreadInput(currentThreadId, foregroundThreadId, false); } else { PInvoke.SetForegroundWindow(_hwnd); } } }