From 5792d32d32df19a7b7ac3c774c47efb71c617231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Sat, 28 Mar 2026 00:46:36 +0100 Subject: [PATCH] CmdPal: Make Window Walker Close window command respect "Keep open" option (#45721) ## Summary of the Pull Request This PR fixes the "Close window" command in the Window Walker built-in extension and makes it obey the "Keep open after closing window" option in the extension's settings. It also adds a message that forces Window Walker to refresh its list of windows, so that the closed window disappears from the list (and similarly for end task commands). ## PR Checklist - [x] Closes: #43256 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../Settings.cs | 2 +- .../Commands/CloseWindowCommand.cs | 7 ++- .../Commands/EndTaskCommand.cs | 16 ++++--- .../Helpers/ISettingsInterface.cs | 2 +- .../Helpers/SettingsManager.cs | 4 +- .../Messages/RefreshWindowsMessage.cs | 7 +++ .../Microsoft.CmdPal.Ext.WindowWalker.csproj | 3 ++ .../Pages/WindowWalkerListPage.cs | 46 +++++++++++++++++-- 8 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Messages/RefreshWindowsMessage.cs diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs index cbbe365a1b..9fba1bf1ed 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.WindowWalker.UnitTests/Settings.cs @@ -53,7 +53,7 @@ public class Settings : ISettingsInterface public bool KillProcessTree => killProcessTree; - public bool OpenAfterKillAndClose => openAfterKillAndClose; + public bool KeepOpenAfterKillAndClose => openAfterKillAndClose; public bool HideKillProcessOnElevatedProcesses => hideKillProcessOnElevatedProcesses; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/CloseWindowCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/CloseWindowCommand.cs index 73f1c99811..8a9570f14a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/CloseWindowCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/CloseWindowCommand.cs @@ -2,7 +2,11 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Ext.WindowWalker.Components; +using Microsoft.CmdPal.Ext.WindowWalker.Helpers; +using Microsoft.CmdPal.Ext.WindowWalker.Messages; +using Microsoft.CmdPal.Ext.WindowWalker.Properties; using Microsoft.CommandPalette.Extensions; namespace Microsoft.CmdPal.Ext.WindowWalker.Commands; @@ -26,6 +30,7 @@ internal sealed partial class CloseWindowCommand : InvokableCommand } _window.CloseThisWindow(); - return CommandResult.Dismiss(); + WeakReferenceMessenger.Default.Send(new RefreshWindowsMessage()); + return SettingsManager.Instance.KeepOpenAfterKillAndClose ? CommandResult.KeepOpen() : CommandResult.Dismiss(); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/EndTaskCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/EndTaskCommand.cs index aa8648a459..cd52053d37 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/EndTaskCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Commands/EndTaskCommand.cs @@ -2,7 +2,12 @@ // 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; +using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Ext.WindowWalker.Components; +using Microsoft.CmdPal.Ext.WindowWalker.Helpers; +using Microsoft.CmdPal.Ext.WindowWalker.Messages; +using Microsoft.CmdPal.Ext.WindowWalker.Properties; using Microsoft.CommandPalette.Extensions; namespace Microsoft.CmdPal.Ext.WindowWalker.Commands; @@ -57,16 +62,13 @@ internal sealed partial class EndTaskCommand : InvokableCommand // Kill process window.Process.KillThisProcess(SettingsManager.Instance.KillProcessTree); - return !SettingsManager.Instance.OpenAfterKillAndClose; + return !SettingsManager.Instance.KeepOpenAfterKillAndClose; } public override ICommandResult Invoke() { - if (KillProcess(_window)) - { - return CommandResult.Dismiss(); - } - - return CommandResult.KeepOpen(); + WeakReferenceMessenger.Default.Send(new RefreshWindowsMessage()); + var shouldHide = KillProcess(_window); + return shouldHide ? CommandResult.Dismiss() : CommandResult.KeepOpen(); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs index de3827c76a..1bd2d0b184 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/ISettingsInterface.cs @@ -16,7 +16,7 @@ public interface ISettingsInterface public bool KillProcessTree { get; } - public bool OpenAfterKillAndClose { get; } + public bool KeepOpenAfterKillAndClose { get; } public bool HideKillProcessOnElevatedProcesses { get; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs index 54ebb72437..068219fbcd 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Helpers/SettingsManager.cs @@ -45,7 +45,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface false); private readonly ToggleSetting _openAfterKillAndClose = new( - Namespaced(nameof(OpenAfterKillAndClose)), + Namespaced(nameof(KeepOpenAfterKillAndClose)), Resources.windowwalker_SettingOpenAfterKillAndClose, Resources.windowwalker_SettingOpenAfterKillAndClose_Description, false); @@ -84,7 +84,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface public bool KillProcessTree => _killProcessTree.Value; - public bool OpenAfterKillAndClose => _openAfterKillAndClose.Value; + public bool KeepOpenAfterKillAndClose => _openAfterKillAndClose.Value; public bool HideKillProcessOnElevatedProcesses => _hideKillProcessOnElevatedProcesses.Value; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Messages/RefreshWindowsMessage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Messages/RefreshWindowsMessage.cs new file mode 100644 index 0000000000..6d9916a502 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Messages/RefreshWindowsMessage.cs @@ -0,0 +1,7 @@ +// 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. + +namespace Microsoft.CmdPal.Ext.WindowWalker.Messages; + +public record RefreshWindowsMessage(bool Delay = true); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj index 641a8677ab..2fb1f784a5 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Microsoft.CmdPal.Ext.WindowWalker.csproj @@ -18,6 +18,9 @@ + + + Resources.resx diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs index f6f466ba47..ff46dc987e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowWalker/Pages/WindowWalkerListPage.cs @@ -2,12 +2,18 @@ // 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; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Ext.WindowWalker.Components; +using Microsoft.CmdPal.Ext.WindowWalker.Helpers; +using Microsoft.CmdPal.Ext.WindowWalker.Messages; +using Microsoft.CmdPal.Ext.WindowWalker.Properties; using Microsoft.CommandPalette.Extensions; namespace Microsoft.CmdPal.Ext.WindowWalker.Pages; -internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposable +internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposable, IRecipient { private System.Threading.CancellationTokenSource _cancellationTokenSource = new(); @@ -26,6 +32,8 @@ internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposabl Title = Resources.window_walker_top_level_command_title, Subtitle = Resources.windowwalker_NoResultsMessage, }; + + WeakReferenceMessenger.Default.Register(this); } public override void UpdateSearchText(string oldSearch, string newSearch) @@ -75,19 +83,51 @@ internal sealed partial class WindowWalkerListPage : DynamicListPage, IDisposabl public override IListItem[] GetItems() => Query(SearchText); + public void Receive(RefreshWindowsMessage message) + { + if (_disposed) + { + return; + } + + if (!message.Delay) + { + Refresh(); + } + else + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); + Refresh(); + }); + } + } + + private void Refresh() + { + if (_disposed) + { + return; + } + + RaiseItemsChanged(); + } + public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } - public void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!_disposed) { if (disposing) { - _cancellationTokenSource.Dispose(); + WeakReferenceMessenger.Default.UnregisterAll(this); + _cancellationTokenSource?.Dispose(); _disposed = true; } }