From 82dc4cdc184b2cea7faec9093e354a0b161dfa1b Mon Sep 17 00:00:00 2001 From: moooyo <42196638+moooyo@users.noreply.github.com> Date: Sat, 25 Oct 2025 03:28:30 +0800 Subject: [PATCH 1/7] [CmdPal] Replace complex cancellation token mechanism with a simple task queue. (#42356) ## Summary of the Pull Request Just consider user are trying to search a long name such as "Visual Studio Code" The old mechanism: User input: V Task: V Then input: i Task cancel for V and start task i etc... The problem is: 1. I don't think we can really cancel the most time-cost part (Find packages from WinGet). 2. User cannot see anything before they really end the input and the last task complete. UX exp is so bad. 3. It's so complex to maintain. Hard to understand for the new contributor. New mechanism: User input: V Task: V Then input: i Prev Task is still running but mark the next task is i Input: s Prev Task is still running but override the next task to s etc... We can get: 1. User can see some results if prev task complete. 2. It's simple to understand 3. The extra time cost I think will not too much. Because we ignored the middle input. Compare: https://github.com/user-attachments/assets/f45f4073-efab-4f43-87f0-f47b727f36dc ## PR Checklist - [ ] Closes: #xxx - [ ] **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 Co-authored-by: Yu Leng --- .../Pages/WinGetExtensionPage.cs | 136 +++++++++--------- 1 file changed, 64 insertions(+), 72 deletions(-) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs index 1d7758769a..e84802b8fa 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/WinGetExtensionPage.cs @@ -28,9 +28,10 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable public bool HasTag => !string.IsNullOrEmpty(_tag); private readonly Lock _resultsLock = new(); + private readonly Lock _taskLock = new(); - private CancellationTokenSource? _cancellationTokenSource; - private Task>? _currentSearchTask; + private string? _nextSearchQuery; + private bool _isTaskRunning; private List? _results; @@ -85,7 +86,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable stopwatch.Stop(); Logger.LogDebug($"Building ListItems took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(GetItems)); - IsLoading = false; return results; } } @@ -98,8 +98,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable Properties.Resources.winget_no_packages_found, }; - IsLoading = false; - return []; } @@ -117,64 +115,70 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable private void DoUpdateSearchText(string newSearch) { - // Cancel any ongoing search - if (_cancellationTokenSource is not null) + lock (_taskLock) { - Logger.LogDebug("Cancelling old search", memberName: nameof(DoUpdateSearchText)); - _cancellationTokenSource.Cancel(); - } - - _cancellationTokenSource = new CancellationTokenSource(); - - var cancellationToken = _cancellationTokenSource.Token; - - IsLoading = true; - - try - { - // Save the latest search task - _currentSearchTask = DoSearchAsync(newSearch, cancellationToken); - } - catch (OperationCanceledException) - { - // DO NOTHING HERE - return; - } - catch (Exception ex) - { - // Handle other exceptions - ExtensionHost.LogMessage($"[WinGet] DoUpdateSearchText throw exception: {ex.Message}"); - return; - } - - // Await the task to ensure only the latest one gets processed - _ = ProcessSearchResultsAsync(_currentSearchTask, newSearch); - } - - private async Task ProcessSearchResultsAsync( - Task> searchTask, - string newSearch) - { - try - { - var results = await searchTask; - - // Ensure this is still the latest task - if (_currentSearchTask == searchTask) + if (_isTaskRunning) { - // Process the results (e.g., update UI) - UpdateWithResults(results, newSearch); + // If a task is running, queue the next search query + // Keep IsLoading = true since we still have work to do + Logger.LogDebug($"Task is running, queueing next search: '{newSearch}'", memberName: nameof(DoUpdateSearchText)); + _nextSearchQuery = newSearch; + } + else + { + // No task is running, start a new search + Logger.LogDebug($"Starting new search: '{newSearch}'", memberName: nameof(DoUpdateSearchText)); + _isTaskRunning = true; + _nextSearchQuery = null; + IsLoading = true; + + _ = ExecuteSearchChainAsync(newSearch); } } - catch (OperationCanceledException) + } + + private async Task ExecuteSearchChainAsync(string query) + { + while (true) { - // Handle cancellation gracefully (e.g., log or ignore) - Logger.LogDebug($" Cancelled search for '{newSearch}'"); - } - catch (Exception ex) - { - // Handle other exceptions - Logger.LogError("Unexpected error while processing results", ex); + try + { + Logger.LogDebug($"Executing search for '{query}'", memberName: nameof(ExecuteSearchChainAsync)); + + var results = await DoSearchAsync(query); + + // Update UI with results + UpdateWithResults(results, query); + } + catch (Exception ex) + { + Logger.LogError($"Unexpected error while searching for '{query}'", ex); + } + + // Check if there's a next query to process + string? nextQuery; + lock (_taskLock) + { + if (_nextSearchQuery is not null) + { + // There's a queued search, execute it + nextQuery = _nextSearchQuery; + _nextSearchQuery = null; + + Logger.LogDebug($"Found queued search, continuing with: '{nextQuery}'", memberName: nameof(ExecuteSearchChainAsync)); + } + else + { + // No more searches queued, mark task as completed + _isTaskRunning = false; + IsLoading = false; + Logger.LogDebug("No more queued searches, task chain completed", memberName: nameof(ExecuteSearchChainAsync)); + break; + } + } + + // Continue with the next query + query = nextQuery; } } @@ -189,11 +193,8 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable RaiseItemsChanged(); } - private async Task> DoSearchAsync(string query, CancellationToken ct) + private async Task> DoSearchAsync(string query) { - // Were we already canceled? - ct.ThrowIfCancellationRequested(); - Stopwatch stopwatch = new(); stopwatch.Start(); @@ -230,9 +231,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable opts.Filters.Add(tagFilter); } - // Clean up here, then... - ct.ThrowIfCancellationRequested(); - var catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog; // Both these catalogs should have been instantiated by the @@ -251,13 +249,11 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable findPackages_stopwatch.Start(); Logger.LogDebug($" Searching {catalog.Info.Name} ({query})", memberName: nameof(DoSearchAsync)); - ct.ThrowIfCancellationRequested(); - Logger.LogDebug($"Preface for \"{searchDebugText}\" took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(DoSearchAsync)); // BODGY, re: microsoft/winget-cli#5151 // FindPackagesAsync isn't actually async. - var internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct); + var internalSearchTask = Task.Run(() => catalog.FindPackages(opts)); var searchResults = await internalSearchTask; findPackages_stopwatch.Stop(); @@ -271,8 +267,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable return []; } - ct.ThrowIfCancellationRequested(); - Logger.LogDebug($" got results for ({query})", memberName: nameof(DoSearchAsync)); // FYI Using .ToArray or any other kind of enumerable loop @@ -282,8 +276,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable { var match = searchResults.Matches[i]; - ct.ThrowIfCancellationRequested(); - var package = match.CatalogPackage; results.Add(package); } From 0e36e7e7a7accc92cfbdaa9828fd7c38fd57d6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Sat, 25 Oct 2025 02:12:59 +0200 Subject: [PATCH 2/7] CmdPal: Add keyboard shortcut (Ctrl+,) to open Settings (#42787) ## Summary of the Pull Request This PR introduces a new keyboard shortcut `Ctrl + ,` that opens the Settings window directly. ## PR Checklist - [x] Closes: #42785 - [ ] **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 --- .../Pages/ShellPage.xaml.cs | 41 +++++++++++-------- .../Strings/en-us/Resources.resw | 3 ++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs index 6ec7f23a59..457b0fddbf 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs @@ -591,24 +591,31 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.RightWindows).HasFlag(CoreVirtualKeyStates.Down); var onlyAlt = altPressed && !ctrlPressed && !shiftPressed && !winPressed; - if (e.Key == VirtualKey.Left && onlyAlt) + var onlyCtrl = !altPressed && ctrlPressed && !shiftPressed && !winPressed; + switch (e.Key) { - WeakReferenceMessenger.Default.Send(new()); - e.Handled = true; - } - else if (e.Key == VirtualKey.Home && onlyAlt) - { - WeakReferenceMessenger.Default.Send(new(WithAnimation: false)); - e.Handled = true; - } - else - { - // The CommandBar is responsible for handling all the item keybindings, - // since the bound context item may need to then show another - // context menu - TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key); - WeakReferenceMessenger.Default.Send(msg); - e.Handled = msg.Handled; + case VirtualKey.Left when onlyAlt: // Alt+Left arrow + WeakReferenceMessenger.Default.Send(new()); + e.Handled = true; + break; + case VirtualKey.Home when onlyAlt: // Alt+Home + WeakReferenceMessenger.Default.Send(new(WithAnimation: false)); + e.Handled = true; + break; + case (VirtualKey)188 when onlyCtrl: // Ctrl+, + WeakReferenceMessenger.Default.Send(new()); + e.Handled = true; + break; + default: + { + // The CommandBar is responsible for handling all the item keybindings, + // since the bound context item may need to then show another + // context menu + TryCommandKeybindingMessage msg = new(ctrlPressed, altPressed, shiftPressed, winPressed, e.Key); + WeakReferenceMessenger.Default.Send(msg); + e.Handled = msg.Handled; + break; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw index ec7ce8b68c..4eb8be9b2b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw @@ -471,4 +471,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut. Navigated to {0} page + + Settings (Ctrl+,) + \ No newline at end of file From 6e5ad11bc320f65392765a5b782e86e4337b23af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Sat, 25 Oct 2025 02:15:34 +0200 Subject: [PATCH 3/7] CmdpPal: SearchBox visibility and async loading race (#42783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request This PR introduces two related fixes to improve the stability and reliability of navigation and search UI behavior in the shell: - **Ensure search box visibility is correctly updated** - `ShellViewModel` previously set `IsSearchBoxVisible` after navigation to the page, but didn’t update it when the value changed. While the value isn’t expected to change dynamically, the property initialization is asynchronous, which could cause a race condition. - As a defensive measure, this also changes the default value of uninitialized property to make it visible by default. - **Cancel asynchronous focus placement if navigation changes** - Ensures that any pending asynchronous focus operation is cancelled when another navigation occurs before it completes. ## PR Checklist - [x] Closes: #42782 - [ ] **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 --- .../PageViewModel.cs | 2 +- .../ShellViewModel.cs | 12 +++ .../Pages/ShellPage.xaml.cs | 97 ++++++++++++++----- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs index 82b4b7d59e..2d750c7df3 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/PageViewModel.cs @@ -68,7 +68,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext // `IsLoading` property as a combo of this value and `IsInitialized` public bool ModelIsLoading { get; protected set; } = true; - public bool HasSearchBox { get; protected set; } + public bool HasSearchBox { get; protected set; } = true; public IconInfoViewModel Icon { get; protected set; } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs index 41db974f5b..046d7ea336 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ShellViewModel.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.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; @@ -48,6 +49,9 @@ public partial class ShellViewModel : ObservableObject, var oldValue = _currentPage; if (SetProperty(ref _currentPage, value)) { + oldValue.PropertyChanged -= CurrentPage_PropertyChanged; + value.PropertyChanged += CurrentPage_PropertyChanged; + if (oldValue is IDisposable disposable) { try @@ -63,6 +67,14 @@ public partial class ShellViewModel : ObservableObject, } } + private void CurrentPage_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(PageViewModel.HasSearchBox)) + { + IsSearchBoxVisible = CurrentPage.HasSearchBox; + } + } + private IPage? _rootPage; private bool _isNested; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs index 457b0fddbf..3509db9f6d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs @@ -48,7 +48,8 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, IRecipient, IRecipient, IRecipient, - INotifyPropertyChanged + INotifyPropertyChanged, + IDisposable { private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread(); @@ -65,6 +66,9 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, private SettingsWindow? _settingsWindow; + private CancellationTokenSource? _focusAfterLoadedCts; + private WeakReference? _lastNavigatedPageRef; + public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService()!; public event PropertyChangedEventHandler? PropertyChanged; @@ -488,6 +492,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, if (e.Content is Page element) { + _lastNavigatedPageRef = new WeakReference(element); element.Loaded += FocusAfterLoaded; } } @@ -497,6 +502,18 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, var page = (Page)sender; page.Loaded -= FocusAfterLoaded; + // Only handle focus for the latest navigated page + if (_lastNavigatedPageRef is null || !_lastNavigatedPageRef.TryGetTarget(out var last) || !ReferenceEquals(page, last)) + { + return; + } + + // Cancel any previous pending focus work + _focusAfterLoadedCts?.Cancel(); + _focusAfterLoadedCts?.Dispose(); + _focusAfterLoadedCts = new CancellationTokenSource(); + var token = _focusAfterLoadedCts.Token; + AnnounceNavigationToPage(page); var shouldSearchBoxBeVisible = ViewModel.CurrentPage?.HasSearchBox ?? false; @@ -509,34 +526,57 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, } else { - _ = Task.Run(async () => - { - await page.DispatcherQueue.EnqueueAsync(async () => + _ = Task.Run( + async () => { - // I hate this so much, but it can take a while for the page to be ready to accept focus; - // focusing page with MarkdownTextBlock takes up to 5 attempts (* 100ms delay between attempts) - for (var i = 0; i < 10; i++) + if (token.IsCancellationRequested) { - if (FocusManager.FindFirstFocusableElement(page) is FrameworkElement frameworkElement) - { - var set = frameworkElement.Focus(FocusState.Programmatic); - if (set) - { - break; - } - } - - await Task.Delay(100); + return; } - // Update the search box visibility based on the current page: - // - We do this here after navigation so the focus is not jumping around too much, - // it messes with screen readers if we do it too early - // - Since this should hide the search box on content pages, it's not a problem if we - // wait for the code above to finish trying to focus the content - ViewModel.IsSearchBoxVisible = ViewModel.CurrentPage?.HasSearchBox ?? false; - }); - }); + try + { + await page.DispatcherQueue.EnqueueAsync( + async () => + { + // I hate this so much, but it can take a while for the page to be ready to accept focus; + // focusing page with MarkdownTextBlock takes up to 5 attempts (* 100ms delay between attempts) + for (var i = 0; i < 10; i++) + { + token.ThrowIfCancellationRequested(); + + if (FocusManager.FindFirstFocusableElement(page) is FrameworkElement frameworkElement) + { + var set = frameworkElement.Focus(FocusState.Programmatic); + if (set) + { + break; + } + } + + await Task.Delay(100, token); + } + + token.ThrowIfCancellationRequested(); + + // Update the search box visibility based on the current page: + // - We do this here after navigation so the focus is not jumping around too much, + // it messes with screen readers if we do it too early + // - Since this should hide the search box on content pages, it's not a problem if we + // wait for the code above to finish trying to focus the content + ViewModel.IsSearchBoxVisible = ViewModel.CurrentPage?.HasSearchBox ?? false; + }); + } + catch (OperationCanceledException) + { + // Swallow cancellation - another FocusAfterLoaded invocation superseded this one + } + catch (Exception ex) + { + Logger.LogError("Error during FocusAfterLoaded async focus work", ex); + } + }, + token); } } @@ -665,4 +705,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, Logger.LogError("Error handling mouse button press event", ex); } } + + public void Dispose() + { + _focusAfterLoadedCts?.Cancel(); + _focusAfterLoadedCts?.Dispose(); + _focusAfterLoadedCts = null; + } } From 20188bda9b4e3e5b2ebfdb90f3b6d2462f61771f Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Fri, 24 Oct 2025 19:16:21 -0500 Subject: [PATCH 4/7] File search now has filters (#42141) Closes #39260 Search for all files & folders, folders only, or files only. Enjoy. https://github.com/user-attachments/assets/43ba93f5-dfc5-4e73-8414-547cf99dcfcf --- .../ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs | 6 +- .../Indexer/SearchFilters.cs | 27 +++++++++ .../Pages/IndexerPage.cs | 55 ++++++++++++++++--- .../Properties/Resources.Designer.cs | 27 +++++++++ .../Properties/Resources.resx | 9 +++ 5 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchFilters.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs index f57b8a2d07..bd9759514c 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs @@ -6,7 +6,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CmdPal.Ext.Indexer; -internal sealed class Icons +internal static class Icons { internal static IconInfo FileExplorerSegoeIcon { get; } = new("\uEC50"); @@ -19,4 +19,8 @@ internal sealed class Icons internal static IconInfo DocumentIcon { get; } = new("\uE8A5"); // Document internal static IconInfo FolderOpenIcon { get; } = new("\uE838"); // FolderOpen + + internal static IconInfo FilesIcon { get; } = new("\uF571"); // PrintAllPages + + internal static IconInfo FilterIcon { get; } = new("\uE71C"); // Filter } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchFilters.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchFilters.cs new file mode 100644 index 0000000000..bf2e5dc451 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Indexer/SearchFilters.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.CmdPal.Ext.Indexer.Properties; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.Indexer.Indexer; + +internal sealed partial class SearchFilters : Filters +{ + public SearchFilters() + { + CurrentFilterId = "all"; + } + + public override IFilterItem[] GetFilters() + { + return [ + new Filter() { Id = "all", Name = Resources.Indexer_Filter_All, Icon = Icons.FilterIcon }, + new Separator(), + new Filter() { Id = "folders", Name = Resources.Indexer_Filter_Folders_Only, Icon = Icons.FolderOpenIcon }, + new Filter() { Id = "files", Name = Resources.Indexer_Filter_Files_Only, Icon = Icons.FilesIcon }, + ]; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs index 3b09fcf149..0ed9458398 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/IndexerPage.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Microsoft.CmdPal.Ext.Indexer.Indexer; using Microsoft.CmdPal.Ext.Indexer.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -36,6 +37,11 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable PlaceholderText = Resources.Indexer_PlaceholderText; _searchEngine = new(); _queryCookie = 10; + + var filters = new SearchFilters(); + filters.PropChanged += Filters_PropChanged; + Filters = filters; + CreateEmptyContent(); } @@ -49,6 +55,11 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable initialQuery = query; SearchText = query; disposeSearchEngine = false; + + var filters = new SearchFilters(); + filters.PropChanged += Filters_PropChanged; + Filters = filters; + CreateEmptyContent(); } @@ -79,30 +90,56 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable { // {20D04FE0-3AEA-1069-A2D8-08002B30309D} is CLSID for "This PC" const string template = "search-ms:query={0}&crumb=location:::{{20D04FE0-3AEA-1069-A2D8-08002B30309D}}"; - var encodedSearchText = UrlEncoder.Default.Encode(SearchText); + var fullSearchText = FullSearchString(SearchText); + var encodedSearchText = UrlEncoder.Default.Encode(fullSearchText); var command = string.Format(CultureInfo.CurrentCulture, template, encodedSearchText); ShellHelpers.OpenInShell(command); } public override ICommandItem EmptyContent => _isEmptyQuery ? _noSearchEmptyContent : _nothingFoundEmptyContent; + private void Filters_PropChanged(object sender, IPropChangedEventArgs args) + { + PerformSearch(SearchText); + } + public override void UpdateSearchText(string oldSearch, string newSearch) { if (oldSearch != newSearch && newSearch != initialQuery) { - _ = Task.Run(() => - { - _isEmptyQuery = string.IsNullOrWhiteSpace(newSearch); - Query(newSearch); - LoadMore(); - OnPropertyChanged(nameof(EmptyContent)); - initialQuery = null; - }); + PerformSearch(newSearch); } } + private void PerformSearch(string newSearch) + { + var actualSearch = FullSearchString(newSearch); + _ = Task.Run(() => + { + _isEmptyQuery = string.IsNullOrWhiteSpace(actualSearch); + Query(actualSearch); + LoadMore(); + OnPropertyChanged(nameof(EmptyContent)); + initialQuery = null; + }); + } + public override IListItem[] GetItems() => [.. _indexerListItems]; + private string FullSearchString(string query) + { + switch (Filters.CurrentFilterId) + { + case "folders": + return $"{query} kind:folders"; + case "files": + return $"{query} kind:NOT folders"; + case "all": + default: + return query; + } + } + public override void LoadMore() { IsLoading = true; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs index 44b87b05e2..d4d8f9cd0f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs @@ -177,6 +177,33 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties { } } + /// + /// Looks up a localized string similar to Files and folders. + /// + internal static string Indexer_Filter_All { + get { + return ResourceManager.GetString("Indexer_Filter_All", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Files. + /// + internal static string Indexer_Filter_Files_Only { + get { + return ResourceManager.GetString("Indexer_Filter_Files_Only", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Folders. + /// + internal static string Indexer_Filter_Folders_Only { + get { + return ResourceManager.GetString("Indexer_Filter_Folders_Only", resourceCulture); + } + } + /// /// Looks up a localized string similar to Find file from path. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx index 66504abed1..8e67eae4e9 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx @@ -196,4 +196,13 @@ You can try searching all files on this PC or adjust your indexing settings. Search all files + + Files and folders + + + Folders + + + Files + \ No newline at end of file From 623c804093922c5ff34432096a567aa6045e4e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Sun, 26 Oct 2025 02:50:07 +0200 Subject: [PATCH 5/7] ManagedCommon: Log correct HRESULT for the inner exception (#42178) ## Summary of the Pull Request This PR fixes incorrect HRESULT for inner exception when an error is logged. ## PR Checklist - [ ] Closes: #xxx - [ ] **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 --- src/common/ManagedCommon/Logger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/ManagedCommon/Logger.cs b/src/common/ManagedCommon/Logger.cs index 11115b1846..0db1614671 100644 --- a/src/common/ManagedCommon/Logger.cs +++ b/src/common/ManagedCommon/Logger.cs @@ -130,7 +130,7 @@ namespace ManagedCommon { exMessage += "Inner exception: " + Environment.NewLine + - ex.InnerException.GetType() + " (" + ex.HResult + "): " + ex.InnerException.Message + Environment.NewLine; + ex.InnerException.GetType() + " (" + ex.InnerException.HResult + "): " + ex.InnerException.Message + Environment.NewLine; } exMessage += From e256e7968575ffde90c7c7a27d9bb8cdb063b442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Sun, 26 Oct 2025 02:51:53 +0200 Subject: [PATCH 6/7] CmdPal: Fix search box text selection in ShellPage.GoHome (#42937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request This PR fixes an issue where `ShellPage.GoHome` wouldn’t select the search box text when the current page was already the home page. In that case, the navigation stack was empty, and no code was executed because focusing the text had been delegated to the `GoBack` operation. ## Change log one-liner Ensured search text is selected when Go home when activated and Highlight search on activate are both enabled. ## PR Checklist - [x] Closes: #42443 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] **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 --- .../cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs index 3509db9f6d..d009c626e0 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Pages/ShellPage.xaml.cs @@ -451,7 +451,15 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page, { while (RootFrame.CanGoBack) { - GoBack(withAnimation, focusSearch); + // don't focus on each step, just at the end + GoBack(withAnimation, focusSearch: false); + } + + // focus search box, even if we were already home + if (focusSearch) + { + SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic); + SearchBox.SelectSearch(); } } From b774e13176fdec6c72f15994714f77908c1c191f Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Mon, 27 Oct 2025 09:33:26 +0800 Subject: [PATCH 7/7] Fix the foreground style for find my mouse (#42865) ## Summary of the Pull Request Find my mouse should use full transparent window ## PR Checklist - [X] Closes: #42758 - [ ] **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 https://github.com/user-attachments/assets/75c73eb3-04bb-438c-8823-3c9f18923cc6 --- src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index c94c79e178..f953af0fdd 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -189,7 +189,7 @@ bool SuperSonar::Initialize(HINSTANCE hinst) return false; } - DWORD exStyle = WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle(); + DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle(); HWND created = CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this); if (!created) { @@ -269,10 +269,6 @@ LRESULT SuperSonar::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n case WM_NCHITTEST: return HTTRANSPARENT; - - case WM_SETCURSOR: - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - return TRUE; } if (message == WM_PRIV_SHORTCUT) @@ -539,7 +535,7 @@ void SuperSonar::StartSonar() Trace::MousePointerFocused(); // Cover the entire virtual screen. // HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen. - SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, SWP_NOACTIVATE); + SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0); m_sonarPos = ptNowhere; OnMouseTimer(); UpdateMouseSnooping();