From 09c1575fa049b671e2b990f6015eab62964389fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Wed, 9 Jul 2025 19:35:42 +0200 Subject: [PATCH] Ensure Command Palette main window remains hidden on taskbar Windows Explorer restarts (#40406) ## Summary of the Pull Request Ensures the Command Palette main window stays hidden from the taskbar after Windows Explorer restarts. Also updates how app switcher visibility is managed to prevent unhandled exceptions and avoid startup crashes when Explorer is not running or is not responsive. ## PR Checklist - [x] Closes: #40334 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** no tests - [x] **Localization:** nothing to localize - [x] **Dev docs:** nothing to update - [x] **New binaries:** no new binaries - [x] **Documentation updated:** nothing to update ## Detailed Description of the Pull Request / Additional comments - Settings on the main window are reapplied after the taskbar is re-created (e.g., after Windows Explorer restarts), restoring visibility settings for the app switcher and taskbar button. - Defensive handling of the property AppWindow.IsShownInSwitchers controlling appearance in system representations such as ALT+TAB and the taskbar, to avoid errors when Windows Explorer is not running (see https://github.com/microsoft/microsoft-ui-xaml/issues/8596). - Prevents application startup failures when the Command Palette is launched in environments without Windows Explorer. ## Validation Steps Performed - Verified that the CmdPal main window taskbar button is not visible after Explorer is restarted (without an attached debugger). - Verified that CmdPal can be started when Explorer is not running. - Verified that the CmdPal main window taskbar button is visible after Explorer is restarted (if a debugger is attached to CmdPal). --- .../Helpers/WindowExtensions.cs | 15 +++++++++++++++ .../Microsoft.CmdPal.UI/MainWindow.xaml.cs | 18 +++++++++++++++--- .../Microsoft.CmdPal.UI/ToastWindow.xaml.cs | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs index 406f261e6c..99ff327ea2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/WindowExtensions.cs @@ -17,4 +17,19 @@ public static class WindowExtensions AppWindow appWindow = AppWindow.GetFromWindowId(windowId); appWindow.SetIcon(@"Assets\icon.ico"); } + + public static void SetVisibilityInSwitchers(this Window window, bool showInSwitchers) + { + try + { + // IsShownInSwitchers needs to change the value to apply the effect, but its state might be out-of-sync with + // the actual state of the switchers, so we need to toggle it. + window.AppWindow.IsShownInSwitchers = !showInSwitchers; + window.AppWindow.IsShownInSwitchers = showInSwitchers; + } + catch (NotImplementedException) + { + // SetShownInSwitchers failed. This can happen if the Explorer is not running. + } + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index b935c98af3..2e30412416 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -2,6 +2,7 @@ // 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; @@ -42,6 +43,9 @@ public sealed partial class MainWindow : WindowEx, IRecipient, IRecipient { + [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 WNDPROC? _hotkeyWndProc; private readonly WNDPROC? _originalWndProc; @@ -87,6 +91,8 @@ public sealed partial class MainWindow : WindowEx, SizeChanged += WindowSizeChanged; RootShellPage.Loaded += RootShellPage_Loaded; + 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, @@ -147,9 +153,7 @@ public sealed partial class MainWindow : WindowEx, _ignoreHotKeyWhenFullScreen = settings.IgnoreShortcutWhenFullscreen; - // This will prevent our window from appearing in alt+tab or the taskbar. - // You'll _need_ to use the hotkey to summon it. - AppWindow.IsShownInSwitchers = System.Diagnostics.Debugger.IsAttached; + this.SetVisibilityInSwitchers(Debugger.IsAttached); } // We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material @@ -600,6 +604,14 @@ public sealed partial class MainWindow : WindowEx, return (LRESULT)IntPtr.Zero; } + + default: + if (uMsg == WM_TASKBAR_RESTART) + { + HotReloadSettings(); + } + + break; } return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs index 053bf4a6a0..478f565f35 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ToastWindow.xaml.cs @@ -35,7 +35,7 @@ public sealed partial class ToastWindow : WindowEx, { this.InitializeComponent(); AppWindow.Hide(); - AppWindow.IsShownInSwitchers = false; + this.SetVisibilityInSwitchers(false); ExtendsContentIntoTitleBar = true; AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay); this.SetIcon();