Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
a769fdffb5 Implement search box re-focus fix to restore cached results
Co-authored-by: yeelam-gordon <73506701+yeelam-gordon@users.noreply.github.com>
2025-08-26 02:51:00 +00:00
copilot-swe-agent[bot]
3c42e262bf Initial plan 2025-08-26 02:41:05 +00:00
161 changed files with 1056 additions and 3582 deletions

View File

@@ -49,7 +49,6 @@ ALPHATYPE
AModifier
amr
ANDSCANS
animatedvisuals
Animnate
ANull
AOC
@@ -70,7 +69,6 @@ APPMODEL
APPNAME
appref
appsettings
appsfeatures
appwindow
appwiz
appxpackage
@@ -330,7 +328,6 @@ Deact
debugbreak
decryptor
Dedup
Deduplicator
Deeplink
DEFAULTBOOTSTRAPPERINSTALLFOLDER
DEFAULTCOLOR
@@ -1027,6 +1024,8 @@ MYICON
NAMECHANGE
namespaceanddescendants
nao
Navigatable
NavigatablePage
NCACTIVATE
ncc
NCCALCSIZE
@@ -1317,7 +1316,6 @@ PRODUCTVERSION
Progman
programdata
projectname
projitems
PROPERTYKEY
Propset
PROPVARIANT
@@ -1397,7 +1395,6 @@ regkey
regroot
regsvr
REINSTALLMODE
releaseblog
reloadable
Relogger
remappings

View File

@@ -260,7 +260,3 @@ Process Process
# ZoomIt menu items with accelerator keys
E&xit
St&yle
# This matches a relative clause where the relative pronoun "that" is omitted.
# Example: "Gets or sets the window the TitleBar should configure."
\bthe\s+\w+\s+the\b

View File

@@ -1,38 +0,0 @@
name: Manual Batch Issue Deduplication
on:
workflow_dispatch:
inputs:
issue_numbers:
description: "JSON array of issue numbers to deduplicate (e.g. [101,102,103])"
required: true
since:
description: "Only compare against issues created after this date (ISO 8601, e.g. 2019-05-05T00:00:00Z)"
required: false
default: "2019-05-05T00:00:00Z"
label_as_duplicate:
description: "Apply duplicate label if duplicates are found (true/false)"
required: false
default: "true"
permissions:
models: read
issues: write
jobs:
deduplicate:
runs-on: ubuntu-latest
strategy:
matrix:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_issue: ${{ matrix.issue }}
label_as_duplicate: ${{ github.event.inputs.label_as_duplicate }}

View File

@@ -23,6 +23,7 @@
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.WinUI.UI.Controls.Markdown" Version="7.1.2" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" Version="0.1.250703-build.2173" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.TitleBar" Version="0.0.1-build.2206" />
<PackageVersion Include="ControlzEx" Version="6.0.0" />
<PackageVersion Include="HelixToolkit" Version="2.24.0" />
<PackageVersion Include="HelixToolkit.Core.Wpf" Version="2.24.0" />

View File

@@ -1499,6 +1499,7 @@ SOFTWARE.
- CoenM.ImageSharp.ImageHash
- CommunityToolkit.Common
- CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock
- CommunityToolkit.Labs.WinUI.TitleBar
- CommunityToolkit.Mvvm
- CommunityToolkit.WinUI.Animations
- CommunityToolkit.WinUI.Collections

199
README.md
View File

@@ -35,19 +35,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline
Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user.
<!-- items that need to be updated release to release -->
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.94%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysUserSetup-0.94.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysUserSetup-0.94.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysSetup-0.94.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.94.0/PowerToysSetup-0.94.0-arm64.exe
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.94%22
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.93%22
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-x64.exe
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysUserSetup-0.93.0-arm64.exe
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-x64.exe
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.93.0/PowerToysSetup-0.93.0-arm64.exe
| Description | Filename |
|----------------|----------|
| Per user - x64 | [PowerToysUserSetup-0.94.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.94.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.94.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.94.0-arm64.exe][ptMachineArm64] |
| Per user - x64 | [PowerToysUserSetup-0.93.0-x64.exe][ptUserX64] |
| Per user - ARM64 | [PowerToysUserSetup-0.93.0-arm64.exe][ptUserArm64] |
| Machine wide - x64 | [PowerToysSetup-0.93.0-x64.exe][ptMachineX64] |
| Machine wide - ARM64 | [PowerToysSetup-0.93.0-arm64.exe][ptMachineArm64] |
This is our preferred method.
@@ -93,145 +93,118 @@ For guidance on developing for PowerToys, please read the [developer docs](./doc
Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on.
### 0.94 - Sep 2025 Update
### 0.93 - Aug 2025 Update
In this release, we focused on new features, stability, optimization improvements, and automation.
For an in-depth look at the latest changes, visit the [release blog](https://aka.ms/powertoys-releaseblog).
**✨Highlights**
- PowerToys Settings added a Settings search with fuzzy matching, suggestions, a results page, and UX polish to make finding options faster.
- A comprehensive hotkey conflict detection system was introduced in Settings to surface and help resolve conflicting shortcuts. Note that the default hotkey settings (Win+Ctrl+Shift+T, Win+Ctrl+V, Win+Ctrl+T, Win+Shift+T) may overlap with existing Windows system shortcuts. This is expected. You can resolve the conflict by assigning different hotkeys.
- Mouse Utilities added a “Gliding cursor” accessibility feature to Mouse Pointer Crosshairs for singlebutton cursor movement and clicking. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
- The installer was upgraded to WiX 5 after WiX 3 reached end-of-life; this move improved installer security, reliability, and community support.
- Tons of bug fixes and improvements for Command Palette, including visual updates and new support for filters on ListPages (handy for extension developers).
- Hosts Editor now has a “No leading spaces” option so active host entries can start at column 0 even if others are disabled. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)!
- Context menu registration was moved from the installer to runtime to avoid loading disabled modules (runtime registrations).
- Quick Accent now supports Maltese, and frequently used accents appear first (and are remembered across sessions). Thanks [@rovercoder](https://github.com/rovercoder)! [@davidegiacometti](https://github.com/davidegiacometti)!
### Always On Top
- Fixed the border hover cursor so it shows the arrow instead of the wait cursor. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- PowerToys settings debuts a modern, card-based dashboard with clearer descriptions and faster navigation for a streamlined user experience.
- Command Palette had over 99 issues resolved, including bringing back Clipboard History, adding context menu shortcuts, pinning favorite apps, and supporting history in Run.
- Command Palette reduced its startup memory usage by ~15%, load time by ~40%, built-in extensions loading time by ~70%, and installation size by ~55%—all due to using the full Ahead-of-Time (AOT) compilation mode in Windows App SDK.
- Peek now supports instant previews and embedded thumbnails for Binary G-code (.bgcode) 3D printing files, making it easy to inspect models at a glance. Thanks [@pedrolamas](https://github.com/pedrolamas)!
- Mouse Utilities introduces a new spotlight highlighting mode that dims the screen and draws attention to your cursor, perfect for presentations.
- Test coverage improvements for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename — ensuring better reliability and quality, with over 600 new unit tests (mostly for Command Palette) and doubled UI automation coverage.
### Command Palette
- Applied single-click activation only to pointer input; keyboard always activates immediately. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Let context menus open at the cursor by removing window-bound constraints. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made error messages clearer with timestamps, HRESULTs, and full details for easier diagnosis. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Prevented crashes and improved robustness when updating providers without commands. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Ensured the Settings window reliably comes to the front when opened. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Replaced the Clipboard History icon with a colorful Fluent icon. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Hardened ContentIcon to avoid duplicate parenting and improve diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Standardized null checks using C# pattern matching for safer behavior.
- Improved accessibility by focusing the activation shortcut dialog and making text reachable. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- Moved the extension SDK to a stable Windows SDK and cleaned up message namespaces.
- Added path shortcuts: ~ to home, and / or \\ to system root, plus UNC support. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Fixed a race in cancellation handling to avoid InvalidOperationException. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Aligned separator styling with WinUI 3 for consistent visuals. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added ARM64 PDBs to the Extensions SDK NuGet for better debugging.
- Added single-select filters to DynamicListPage and updated Windows Services sample.
- Updated main page placeholder text to better describe what can be searched. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Removed explicit WinAppSDK/WebView2 dependencies from toolkit and API. Thanks [@rluengen](https://github.com/rluengen)!
- Added a local keyboard hook to handle the GoBack key reliably. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Propagated alias changes safely and resolved conflicts across view models.
- Allowed providers to override Dispose with a virtual method.
- Fixed memory leaks by cleaning up removed or cancelled list items.
- Sorted DateTime extension results by relevance for better usability.
- Reduced search text “jiggling” by avoiding redundant change notifications.
- Centralized automation notifications in a UIHelper for better accessibility. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- Preserved Adaptive Card action types during trimming via DynamicDependency.
- Added an acrylic backdrop and refined styling to the context menu. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Prevented disposed pages and Settings windows from handling stale messages. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made the extension API easier to evolve without breaking clients.
- Added “evil” sample pages to help reproduce tricky bugs.
- Fixed WinGet trim-safety issues by replacing LINQ with manual iteration.
- Cancelled stale list fetches to avoid older results overwriting newer ones in CmdPal.
- Ensured screen readers are notified when the selected item in the list changes for better accessibility.
- Fixed command title changes not being properly notified to screen readers. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Made icon controls excluded from keyboard navigation by default for better accessibility. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved UI design with better text sizing and alignment.
- Fixed keyboard shortcuts to work better in text boxes and context menus.
- Added right-click context menus with critical command styling and separators.
- Improved various context menu issues, improving item selection, handling of long titles, search bar text scaling, initial item behavior, and primary button functionality.
- Fixed context menu crashes with better type handling.
- Fixed "Reload" command to work with both uppercase and lowercase letters.
- Added mouse back button support for easier navigation. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Alt+Left Arrow navigation not working when search box contains text. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Updated back button tooltip to show keyboard shortcut information. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window not appearing properly when activated. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed Command Palette window staying hidden from taskbar after File Explorer restarts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window focus not returning to previous app properly.
- Fixed Command Palette window to always appear on top when shown and move to bottom when hidden. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed window hiding to properly work on UI thread. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Fixed crashes and improved stability with better synchronization of Command list updates. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved extension disposal with better error handling to prevent crashes. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Improved stability by fixing a UI threading issue when loading more results, preventing possible crashes and ensuring the loading state resets if loading fails. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Enhanced icon loading stability with better exception handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added thread safety to recent commands to prevent crashes. Thanks [@MaoShengelia](https://github.com/MaoShengelia)!
- Fixed acrylic (frosted glass) system backdrop display issues by ensuring proper UI thread handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
### Command Palette extensions
- Improved empty states and ranking logic for multiple extensions. Thanks [@htcfreek](https://github.com/htcfreek)!
- Added app icons to the All Apps "Run" context command when available.
- Restored missing builtin icons by standardizing extension dependencies.
- Unblocked local deployment by adding WinAppSDK to two sample extensions.
### Hosts File Editor
- Added a "No leading spaces" option so active hosts entries can start at column 0 even when others are disabled. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)!
### Image Resizer
- Fixed Image Resizer localization by installing satellite resources under the WinUI 3 apps culture path.
- Added settings to each provider to control which fallback commands are enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)! for fixing a regression in this feature.
- Added sample code showing how Command Palette extensions can track when their pages are loaded or unloaded. [Check it out here](./src/modules/cmdpal/ext/SamplePagesExtension/OnLoadPage.cs).
- Fixed *Calculator* to accept regular spaces in numbers that use space separators. Thanks [@PesBandi](https://github.com/PesBandi)!
- Added a new setting to *Calculator* to make "Copy" the primary button (replacing “Save”) and enable "Close on Enter", streamlining the workflow. Thanks [@PesBandi](https://github.com/PesBandi)!
- Improved *Apps* indexing error handling and removed obsolete code. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Prevented apps from showing in search when the *Apps* extension is disabled. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added ability to pin/unpin *Apps* using Ctrl+P shortcut.
- Added keyboard shortcuts to the *Apps* context menu items for faster access.
- Added all file context menu options to the *Apps* items context menu, making all file actions available there for better functionality.
- Streamlined All *Apps* extension settings by removing redundant descriptions, making the UI clearer.
- Added command history to the *Run* page for easier access to previous commands.
- Fixed directory path handling in *Run* fallback for better file navigation.
- Fixed URL fallback item hiding properly in *Web Search* extension when search query becomes invalid. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added proper empty state message for *Web Search* extension when no results found. Thanks [@jiripolasek](https://github.com/jiripolasek)!
- Added fallback command to *Windows Settings* extension for better search results.
- Re-enabled *Clipboard History* feature with proper window handling.
- Improved *Add Bookmark* extension to automatically detect file, folder, or URL types without manual input.
- Updated terminology from "Kill process" to "End task" in *Window Walker* for consistency with Windows.
- Fixed minor grammar error in SamplePagesExtension code comments. Thanks [@purofle](https://github.com/purofle)!
### Mouse Utilities
- Introduced "Gliding cursor" to control the pointer and click with a single hotkey for better accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
### Mouse Without Borders
- Blocked Easy Mouse from switching machines during fullscreen apps, with an allow-list for exceptions. Thanks [@dot-tb](https://github.com/dot-tb)!
- Added a new spotlight highlighting mode that creates a large transparent circle around your cursor with a backdrop effect, providing an alternative to the traditional circle highlight. Perfect for presentations where you want to focus attention on a specific area while dimming the rest of the screen.
### Peek
- Added Visual Studio shared project file types to XML preview and fixed bgcode handler registration. Thanks [@rezanid](https://github.com/rezanid)!
- Fixes bgcode preview handler registration and events for reliable previews. Thanks [@pedrolamas](https://github.com/pedrolamas)!
### PowerRename
- Changed the Explorer accelerator key to PowErRename to avoid clashing with the New menu. Thanks [@aaron-ni](https://github.com/aaron-ni)!
- Added preview and thumbnail support for Binary G-code (.bgcode) files used in 3D printing. You can now see embedded thumbnails and preview these compressed 3D printing files directly in Peek and File Explorer. Thanks [@pedrolamas](https://github.com/pedrolamas)!
### Quick Accent
- Remembered character usage across sessions so frequently used accents appear first. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Added Maltese language support with specific characters and the Euro symbol. Thanks [@rovercoder](https://github.com/rovercoder)!
- Reduced GPU usage issues by making the window Topmost only when the picker is visible. Thanks [@daverayment](https://github.com/daverayment)!
- Added Vietnamese language support to Quick Accent, mappings for Vietnamese vowels (a, e, i, o, u, y) and the letter d. Thanks [@octastylos-pseudodipteros](https://github.com/octastylos-pseudodipteros)!
### Settings
- Added telemetry to track usage of the new shortcut conflict detection workflow.
- Moved the shutdown action from the title bar to a footer menu item with confirmation. Thanks [@davidegiacometti](https://github.com/davidegiacometti)!
- Implemented comprehensive hotkey conflict detection with a dedicated resolution dialog.
- Added branded visuals for Office and Copilot keys in the KeyVisual control.
- Introduced Settings search with fuzzy matching and navigation to specific controls.
- Corrected Spanish localization so product names like Awake remain in English across Settings and OOBE.
- Simplified the Advanced Paste description in Settings for quicker reading and consistent capitalization. Thanks [@OldUser101](https://github.com/OldUser101)!
- Localized conflict messages in the conflict window and dialog.
### Installer
- Upgraded the installer to WiX 5 with silent "Files in Use" handling for smoother winget installs.
- Switched Win10 context menu modules to runtime registration and added cleanup on uninstall to avoid stale entries.
- Completely redesigned the Settings dashboard with a modern card-based layout featuring organized sections for quick actions and shortcuts overview, replacing the old module list.
- Rewrote setting descriptions to be more concise and follow Windows writing style guidelines, making them easier to understand.
- Improved formatting and readability of release notes in the "What's New" section with better typography and spacing.
- Added missing deep link support for various settings pages (Peek, Quick Accent, PowerToys Run, etc.) so you can jump directly to specific settings.
- Resolved an issue where the settings page header would drift away from its position when resizing the settings window.
- Resolved a settings crash related to incompatible property names in ZoomIt configuration.
### Documentation
- Adds docs for building the installer locally and testing winget installs.
- Fixed a broken style guide link in developer documentation. Thanks [@denizmaral](https://github.com/denizmaral)!
- Added detailed step-by-step instructions for first-time developers building the Command Palette module, including prerequisites and Visual Studio setup guidance. Thanks [@chatasweetie](https://github.com/chatasweetie)!
- **Fixed Broken SDK Link**: Corrected a broken markdown link in the Command Palette SDK README that was pointing to an incorrect directory path. Thanks [@ChrisGuzak](https://github.com/ChrisGuzak)!
- Added documentation for the "Open With Cursor" plugin that enables opening Visual Studio and VS Code recent files using Cursor AI. Thanks [@VictorNoxx](https://github.com/VictorNoxx)!
- Added documentation for two new community plugins - Hotkeys plugin for creating custom keyboard shortcuts, and RandomGen plugin for generating random data like passwords, colors, and placeholder text. Thanks [@ruslanlap](https://github.com/ruslanlap)!
### Development
- Excluded test and coverage DLLs from BinSkim scans to cut false positives and speed up security analysis.
- Simplified NOTICE maintenance by removing version numbers and filtering out Microsoft/System packages.
- Improved NuGet dependency validation to prevent package downgrades and catch issues during restore.
- Updated UTF.Unknown to a modern version to improve compatibility without breaking changes. Thanks [@304NotModified](https://github.com/304NotModified)!
- Refreshed package catalog in CI before installing dependencies to prevent Linux workflow failures.
- Refactored CmdPal tests with dependency injection and added coverage for queries and settings.
- Added unit tests to verify Close on Enter swaps Copy/Save as expected. Thanks [@mohammed-saalim](https://github.com/mohammed-saalim)!
- Added accessibility IDs to CmdPal UI for stable UI tests.
- Rewrote system command tests with a new test base and cleaner patterns.
- Added unit tests for WebSearch and Shell extensions with mockable settings.
- Added unit tests and abstractions for Apps and Bookmarks extensions.
- Cleans up AIgenerated tests; adds meaningful query tests across extensions.
- Removed the obsolete debug dialog from Settings for a smoother developer loop.
- Updated .NET libraries to 9.0.8 for performance and security. Thanks [@snickler](https://github.com/snickler)!
- Updated the spell check system to version 0.0.25 with better GitHub integration and SARIF reporting, plus fixed numerous spelling errors throughout the codebase including property names and documentation. Thanks [@jsoref](https://github.com/jsoref)!
- Cleaned up spelling check configuration to eliminate false positives and excessive noise that was appearing in every pull request, making the development process smoother.
- Replaced NuGet feed with Azure Artifacts for better package management.
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours.
- Replaced brittle pixel-by-pixel image comparison with perceptual hash (pHash) technology that's more robust to minor rendering differences - no more test failures due to anti-aliasing or compression artifacts.
- Reduced CI/fuzzing/UI test timeouts from 4 hours to 90 minutes, dramatically improving developer feedback loops and preventing long waits when builds get stuck.
- Standardized test project naming across the entire codebase and improved pipeline result identification by adding platform/install mode context to test run titles. Thanks [@khmyznikov](https://github.com/khmyznikov)!
- Added comprehensive UI test suites for multiple PowerToys modules including Command Palette, Advanced Paste, Peek, Text Extractor, and PowerRename - ensuring better reliability and quality.
- Enhanced UI test automation with command-line argument support, better session management, and improved element location methods using pattern matching to avoid failures from minor differences in exact matches.
### What is being planned over the next few releases
For [v0.95][github-next-release-work], we'll work on the items below:
For [v0.94][github-next-release-work], we'll work on the items below:
- Continued Command Palette polish
- Working on Shortcut Guide v2 (Thanks [@noraa-junker](https://github.com/noraa-junker)!)
- Working on upgrading the installer to WiX 5
- Working on shortcut conflict detection
- Working on setting search
- Upgrading Keyboard Manager's editor UI
- UI tweaking utility with day/night theme switcher
- DSC v3 support for top utilities
- New UI automation tests
- Stability, bug fixes

View File

@@ -76,7 +76,6 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
1. Windows 10 April 2018 Update (version 1803) or newer
1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer
1. A local clone of the PowerToys repository
1. Enable long paths in Windows (see [Enable Long Paths](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enabling-long-paths-in-windows-10-version-1607-and-later) for details)
### Install Visual Studio dependencies

View File

@@ -32,8 +32,17 @@ namespace ManagedCommon
/// <param name="isLocalLow">If the process using Logger is a low-privilege process.</param>
public static void InitializeLogger(string applicationLogPath, bool isLocalLow = false)
{
string versionedPath = LogDirectoryPath(applicationLogPath, isLocalLow);
string basePath = Path.GetDirectoryName(versionedPath);
string basePath;
if (isLocalLow)
{
basePath = Environment.GetEnvironmentVariable("userprofile") + "\\appdata\\LocalLow\\Microsoft\\PowerToys" + applicationLogPath;
}
else
{
basePath = Constants.AppDataPath() + applicationLogPath;
}
string versionedPath = Path.Combine(basePath, Version);
if (!Directory.Exists(versionedPath))
{
@@ -50,22 +59,6 @@ namespace ManagedCommon
Task.Run(() => DeleteOldVersionLogFolders(basePath, versionedPath));
}
public static string LogDirectoryPath(string applicationLogPath, bool isLocalLow = false)
{
string basePath;
if (isLocalLow)
{
basePath = Environment.GetEnvironmentVariable("userprofile") + "\\appdata\\LocalLow\\Microsoft\\PowerToys" + applicationLogPath;
}
else
{
basePath = Constants.AppDataPath() + applicationLogPath;
}
string versionedPath = Path.Combine(basePath, Version);
return versionedPath;
}
/// <summary>
/// Deletes old version log folders, keeping only the current version's folder.
/// </summary>
@@ -122,13 +115,13 @@ namespace ManagedCommon
{
var exMessage =
message + Environment.NewLine +
ex.GetType() + " (" + ex.HResult + "): " + ex.Message + Environment.NewLine;
ex.GetType() + ": " + ex.Message + Environment.NewLine;
if (ex.InnerException != null)
{
exMessage +=
"Inner exception: " + Environment.NewLine +
ex.InnerException.GetType() + " (" + ex.HResult + "): " + ex.InnerException.Message + Environment.NewLine;
ex.InnerException.GetType() + ": " + ex.InnerException.Message + Environment.NewLine;
}
exMessage +=

View File

@@ -20,14 +20,27 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="titleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/EnvironmentVariables/EnvironmentVariables.ico" />
</TitleBar.LeftHeader>
</TitleBar>
<Grid
x:Name="titleBar"
Height="32"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="../Assets/EnvironmentVariables/EnvironmentVariables.ico" />
<TextBlock
x:Name="AppTitleTextBlock"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
</Grid>
</winuiex:WindowEx>

View File

@@ -4,19 +4,22 @@
using System;
using System.Runtime.InteropServices;
using EnvironmentVariables.Win32;
using EnvironmentVariablesUILib;
using EnvironmentVariablesUILib.Helpers;
using EnvironmentVariablesUILib.ViewModels;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinUIEx;
namespace EnvironmentVariables
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx
{
private EnvironmentVariablesMainPage MainPage { get; }
@@ -31,9 +34,8 @@ namespace EnvironmentVariables
AppWindow.SetIcon("Assets/EnvironmentVariables/EnvironmentVariables.ico");
var loader = ResourceLoaderInstance.ResourceLoader;
var title = App.GetService<IElevationHelper>().IsElevated ? loader.GetString("WindowAdminTitle") : loader.GetString("WindowTitle");
Title = title;
titleBar.Title = title;
AppTitleTextBlock.Text = title;
var handle = this.GetWindowHandle();
RegisterWindow(handle);

View File

@@ -19,26 +19,6 @@
class FileLocksmithModule : public PowertoyModuleIface
{
private:
// Update registration based on enabled state
void UpdateRegistration(bool enabled)
{
if (enabled)
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
Logger::info(L"File Locksmith context menu registered");
#endif
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered");
#endif
}
}
public:
FileLocksmithModule()
{
@@ -108,16 +88,21 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
#endif
m_enabled = true;
UpdateRegistration(m_enabled);
}
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered (Win10)");
#endif
m_enabled = false;
UpdateRegistration(m_enabled);
}
virtual bool is_enabled() override
@@ -150,7 +135,6 @@ private:
{
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
m_extended_only = FileLocksmithSettingsInstance().GetShowInExtendedContextMenu();
UpdateRegistration(m_enabled);
Trace::EnableFileLocksmith(m_enabled);
}

View File

@@ -20,15 +20,30 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="titleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/FileLocksmith/Icon.ico" />
</TitleBar.LeftHeader>
</TitleBar>
<Grid
x:Name="AppTitleBar"
Height="32"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightDragColumn" Width="*" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="../Assets/FileLocksmith/Icon.ico" />
<TextBlock
x:Name="AppTitleTextBlock"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
<views:MainPage x:Name="mainPage" Grid.Row="1" />
</Grid>
</winuiex:WindowEx>
</winuiex:WindowEx>

View File

@@ -18,16 +18,30 @@ namespace FileLocksmithUI
{
InitializeComponent();
mainPage.ViewModel.IsElevated = isElevated;
SetTitleBar(titleBar);
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
SetTitleBar(AppTitleBar);
Activated += MainWindow_Activated;
AppWindow.SetIcon("Assets/FileLocksmith/Icon.ico");
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(this.GetWindowHandle());
var loader = ResourceLoaderInstance.ResourceLoader;
var title = isElevated ? loader.GetString("AppAdminTitle") : loader.GetString("AppTitle");
Title = title;
titleBar.Title = title;
AppTitleTextBlock.Text = title;
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
AppTitleTextBlock.Foreground =
(SolidColorBrush)App.Current.Resources["WindowCaptionForegroundDisabled"];
}
else
{
AppTitleTextBlock.Foreground =
(SolidColorBrush)App.Current.Resources["WindowCaptionForeground"];
}
}
public void Dispose()

View File

@@ -190,7 +190,7 @@
TextWrapping="Wrap" />
</ContentDialog>
<ContentDialog x:Name="ProcessFilesListDialog" x:Uid="ProcessFilesListDialog">
<ScrollViewer Padding="16" HorizontalScrollBarVisibility="Auto">
<ScrollViewer Padding="15" HorizontalScrollBarVisibility="Auto">
<TextBlock
x:Name="ProcessFilesListDialogTextBlock"
x:Uid="ProcessFilesListDialogTextBlock"

View File

@@ -20,14 +20,27 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="titleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/Hosts/Hosts.ico" />
</TitleBar.LeftHeader>
</TitleBar>
<Grid
x:Name="titleBar"
Height="32"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="../Assets/Hosts/Hosts.ico" />
<TextBlock
x:Name="AppTitleTextBlock"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
</Grid>
</winuiex:WindowEx>

View File

@@ -9,15 +9,19 @@ using HostsUILib.Helpers;
using HostsUILib.Views;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.Windows.ApplicationModel.Resources;
using WinUIEx;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Hosts
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx
{
private HostsMainPage MainPage { get; }
@@ -34,18 +38,31 @@ namespace Hosts
var title = Host.GetService<IElevationHelper>().IsElevated ? loader.GetString("WindowAdminTitle") : loader.GetString("WindowTitle");
Title = title;
titleBar.Title = title;
AppTitleTextBlock.Text = title;
var handle = this.GetWindowHandle();
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(handle);
WindowHelpers.BringToForeground(handle);
Activated += MainWindow_Activated;
MainPage = Host.GetService<HostsMainPage>();
PowerToysTelemetry.Log.WriteEvent(new HostEditorStartFinishEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
AppTitleTextBlock.Foreground = (SolidColorBrush)App.Current.Resources["WindowCaptionForegroundDisabled"];
}
else
{
AppTitleTextBlock.Foreground = (SolidColorBrush)App.Current.Resources["WindowCaptionForeground"];
}
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
MainGrid.Children.Add(MainPage);

View File

@@ -31,11 +31,7 @@ struct CommonState
Measurement::Unit units = Measurement::Unit::Pixel;
#pragma warning(push)
#pragma warning(disable : 4324)
alignas(8) POINT cursorPosSystemSpace = {}; // updated atomically
#pragma warning(pop)
POINT cursorPosSystemSpace = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
float GetPhysicalPx2MmRatio(HWND window) const

View File

@@ -21,26 +21,6 @@
// Note: Settings are managed via Settings and UI Settings
class NewModule : public PowertoyModuleIface
{
private:
// Update registration based on enabled state
void UpdateRegistration(bool enabled)
{
if (enabled)
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
Logger::info(L"New+ context menu registered");
#endif
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::Unregister();
Logger::info(L"New+ context menu unregistered");
#endif
}
}
public:
NewModule()
{
@@ -118,9 +98,14 @@ public:
{
newplus::utilities::register_msix_package();
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::EnsureRegisteredWin10();
#endif
}
powertoy_new_enabled = true;
UpdateRegistration(powertoy_new_enabled);
}
virtual void disable() override
@@ -165,14 +150,19 @@ private:
{
Trace::EventToggleOnOff(false);
}
if (!package::IsWin11OrGreater())
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
NewPlusRuntimeRegistration::Unregister();
Logger::info(L"New+ context menu unregistered (Win10)");
#endif
}
powertoy_new_enabled = false;
UpdateRegistration(powertoy_new_enabled);
}
void init_settings()
{
powertoy_new_enabled = NewSettingsInstance().GetEnabled();
UpdateRegistration(powertoy_new_enabled);
}
};

View File

@@ -49,9 +49,7 @@ namespace Awake.Core
private static DateTimeOffset ExpireAt { get; set; }
private static readonly CompositeFormat AwakeMinute = CompositeFormat.Parse(Resources.AWAKE_MINUTE);
private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES);
private static readonly CompositeFormat AwakeHour = CompositeFormat.Parse(Resources.AWAKE_HOUR);
private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS);
private static readonly BlockingCollection<ExecutionState> _stateQueue;
private static CancellationTokenSource _tokenSource;
@@ -453,7 +451,7 @@ namespace Awake.Core
Dictionary<string, uint> optionsList = new()
{
{ string.Format(CultureInfo.InvariantCulture, AwakeMinutes, 30), 1800 },
{ string.Format(CultureInfo.InvariantCulture, AwakeHour, 1), 3600 },
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 1), 3600 },
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 2), 7200 },
};
return optionsList;

View File

@@ -159,15 +159,6 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to {0} hour.
/// </summary>
internal static string AWAKE_HOUR {
get {
return ResourceManager.GetString("AWAKE_HOUR", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} hours.
/// </summary>
@@ -249,15 +240,6 @@ namespace Awake.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to {0} minute.
/// </summary>
internal static string AWAKE_MINUTE {
get {
return ResourceManager.GetString("AWAKE_MINUTE", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} minutes.
/// </summary>

View File

@@ -123,10 +123,6 @@
<data name="AWAKE_EXIT" xml:space="preserve">
<value>Exit</value>
</data>
<data name="AWAKE_HOUR" xml:space="preserve">
<value>{0} hour</value>
<comment>{0} shouldn't be removed. It will be replaced by the number 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
</data>
<data name="AWAKE_HOURS" xml:space="preserve">
<value>{0} hours</value>
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
@@ -146,10 +142,6 @@
<value>Keep awake until expiration date and time</value>
<comment>Keep the system awake until expiration date and time</comment>
</data>
<data name="AWAKE_MINUTE" xml:space="preserve">
<value>{0} minute</value>
<comment>{0} shouldn't be removed. It will be replaced by the number 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
</data>
<data name="AWAKE_MINUTES" xml:space="preserve">
<value>{0} minutes</value>
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>

View File

@@ -1,51 +0,0 @@
// 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.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Well-known key chords used in the Command Palette and extensions.
/// </summary>
/// <remarks>
/// Assigned key chords should not conflict with system or application shortcuts.
/// However, the key chords in this class are not guaranteed to be unique and may conflict
/// with each other, especially when commands appear together in the same menu.
/// </remarks>
public static class WellKnownKeyChords
{
/// <summary>
/// Gets the well-known key chord for opening the file location. Shortcut: Ctrl+Shift+E.
/// </summary>
public static KeyChord OpenFileLocation { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.E);
/// <summary>
/// Gets the well-known key chord for copying the file path. Shortcut: Ctrl+Shift+C.
/// </summary>
public static KeyChord CopyFilePath { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.C);
/// <summary>
/// Gets the well-known key chord for opening the current location in a console. Shortcut: Ctrl+Shift+R.
/// </summary>
public static KeyChord OpenInConsole { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.R);
/// <summary>
/// Gets the well-known key chord for running the selected item as administrator. Shortcut: Ctrl+Shift+Enter.
/// </summary>
public static KeyChord RunAsAdministrator { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.Enter);
/// <summary>
/// Gets the well-known key chord for running the selected item as a different user. Shortcut: Ctrl+Shift+U.
/// </summary>
public static KeyChord RunAsDifferentUser { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: (int)VirtualKey.U);
/// <summary>
/// Gets the well-known key chord for toggling the pin state. Shortcut: Ctrl+P.
/// </summary>
public static KeyChord TogglePin { get; } = KeyChordHelpers.FromModifiers(ctrl: true, vkey: (int)VirtualKey.P);
}

View File

@@ -160,7 +160,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
Initialized |= InitializedState.Initialized;
}
public virtual void SlowInitializeProperties()
public void SlowInitializeProperties()
{
if (IsSelectedInitialized)
{

View File

@@ -47,21 +47,9 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
UpdateTags(li.Tags);
TextToSuggest = li.TextToSuggest;
Section = li.Section ?? string.Empty;
UpdateProperty(nameof(Section));
}
public override void SlowInitializeProperties()
{
base.SlowInitializeProperties();
var model = Model.Unsafe;
if (model is null)
{
return;
}
var extensionDetails = model.Details;
var extensionDetails = li.Details;
if (extensionDetails is not null)
{
Details = new(extensionDetails, PageContext);
@@ -70,8 +58,8 @@ public partial class ListItemViewModel(IListItem model, WeakReference<IPageConte
UpdateProperty(nameof(HasDetails));
}
TextToSuggest = model.TextToSuggest;
UpdateProperty(nameof(TextToSuggest));
UpdateProperty(nameof(Section));
}
protected override void FetchProperty(string propertyName)

View File

@@ -3,12 +3,10 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
@@ -41,7 +39,7 @@ public partial class AppStateModel : ObservableObject
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(LoadState)}");
throw new InvalidOperationException($"You must set a valid {nameof(SettingsModel.FilePath)} before calling {nameof(LoadState)}");
}
if (!File.Exists(FilePath))
@@ -79,84 +77,43 @@ public partial class AppStateModel : ObservableObject
try
{
// Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel!);
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
// validate JSON
if (JsonNode.Parse(settingsJson) is not JsonObject newSettings)
// Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
{
Logger.LogError("Failed to parse app state as a JsonObject.");
return;
}
// Now, read the existing content from the file
var oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
// read previous settings
if (!TryReadSavedState(out var savedSettings))
{
savedSettings = new JsonObject();
}
// Is it valid JSON?
if (JsonNode.Parse(oldContent) is JsonObject savedSettings)
{
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
// merge new settings into old ones
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
File.WriteAllText(FilePath, serialized);
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel!.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
// have a file change watcher on the settings file, and
// reload the settings then
model.StateChanged?.Invoke(model, null);
}
catch (Exception ex)
{
Logger.LogError($"Failed to save application state to {FilePath}:", ex);
}
}
private static bool TryReadSavedState([NotNullWhen(true)] out JsonObject? savedSettings)
{
savedSettings = null;
// read existing content from the file
string oldContent;
try
{
if (File.Exists(FilePath))
{
oldContent = File.ReadAllText(FilePath);
// TODO: Instead of just raising the event here, we should
// have a file change watcher on the settings file, and
// reload the settings then
model.StateChanged?.Invoke(model, null);
}
else
{
Debug.WriteLine("Failed to parse settings file as JsonObject.");
}
}
else
{
// file doesn't exist (might not have been created yet), so consider this a success
// and return empty settings
savedSettings = new JsonObject();
return true;
Debug.WriteLine("Failed to parse settings file as JsonObject.");
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to read app state file {FilePath}:\n{ex}");
return false;
}
// detect empty file, just for sake of logging
if (string.IsNullOrWhiteSpace(oldContent))
{
Logger.LogInfo($"App state file is empty: {FilePath}");
return false;
}
// is it valid JSON?
try
{
savedSettings = JsonNode.Parse(oldContent) as JsonObject;
return savedSettings != null;
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to parse app state from {FilePath}:\n{ex}");
return false;
Debug.WriteLine(ex.ToString());
}
}

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Commands;
using Microsoft.CmdPal.UI.ViewModels.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -20,10 +19,6 @@ internal sealed partial class FallbackLogItem : FallbackCommandItem
Title = string.Empty;
_logMessagesPage.Name = string.Empty;
Subtitle = Properties.Resources.builtin_log_subtitle;
var logPath = Logger.LogDirectoryPath("\\CmdPal\\Logs\\");
var openLogCommand = new OpenFileCommand(logPath) { Name = Resources.builtin_log_folder_command_name };
MoreCommands = [new CommandContextItem(openLogCommand)];
}
public override void UpdateQuery(string query)

View File

@@ -27,9 +27,7 @@ public partial class MainListPage : DynamicListPage,
private readonly IServiceProvider _serviceProvider;
private readonly TopLevelCommandManager _tlcManager;
private IEnumerable<Scored<IListItem>>? _filteredItems;
private IEnumerable<Scored<IListItem>>? _filteredApps;
private IEnumerable<IListItem>? _allApps;
private IEnumerable<IListItem>? _filteredItems;
private bool _includeApps;
private bool _filteredItemsIncludesApps;
@@ -85,7 +83,7 @@ public partial class MainListPage : DynamicListPage,
}
else
{
RaiseItemsChanged();
RaiseItemsChanged(_tlcManager.TopLevelCommands.Count);
}
}
@@ -150,13 +148,7 @@ public partial class MainListPage : DynamicListPage,
{
lock (_tlcManager.TopLevelCommands)
{
var items = Enumerable.Empty<Scored<IListItem>>()
.Concat(_filteredItems is not null ? _filteredItems : [])
.Concat(_filteredApps is not null ? _filteredApps : [])
.OrderByDescending(o => o.Score)
.Select(s => s.Item)
.ToArray();
return items;
return _filteredItems?.ToArray() ?? [];
}
}
}
@@ -175,8 +167,6 @@ public partial class MainListPage : DynamicListPage,
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
}
@@ -194,8 +184,6 @@ public partial class MainListPage : DynamicListPage,
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
_filteredApps = null;
_allApps = null;
RaiseItemsChanged(commands.Count);
return;
}
@@ -205,49 +193,35 @@ public partial class MainListPage : DynamicListPage,
if (!newSearch.StartsWith(oldSearch, StringComparison.CurrentCultureIgnoreCase))
{
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
// If the internal state has changed, reset _filteredItems to reset the list.
if (_filteredItemsIncludesApps != _includeApps)
{
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
var newFilteredItems = _filteredItems?.Select(s => s.Item);
// If we don't have any previous filter results to work with, start
// with a list of all our commands & apps.
if (newFilteredItems is null && _filteredApps is null)
if (_filteredItems is null)
{
newFilteredItems = commands;
_filteredItems = commands;
_filteredItemsIncludesApps = _includeApps;
if (_includeApps)
{
_allApps = AllAppsCommandProvider.Page.GetItems();
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
var appIds = apps.Select(app => app.Command.Id).ToArray();
// Remove any top level pinned apps and use the apps from AllAppsCommandProvider.Page.GetItems()
// since they contain details.
_filteredItems = _filteredItems.Where(item => item.Command is not AppCommand);
_filteredItems = _filteredItems.Concat(apps);
}
}
// Produce a list of everything that matches the current filter.
_filteredItems = ListHelpers.FilterListWithScores<IListItem>(newFilteredItems ?? [], SearchText, ScoreTopLevelItem);
// Produce a list of filtered apps with the appropriate limit
if (_allApps is not null)
{
_filteredApps = ListHelpers.FilterListWithScores<IListItem>(_allApps, SearchText, ScoreTopLevelItem);
var appResultLimit = AllAppsCommandProvider.TopLevelResultLimit;
if (appResultLimit >= 0)
{
_filteredApps = _filteredApps.Take(appResultLimit);
}
}
RaiseItemsChanged();
_filteredItems = ListHelpers.FilterList<IListItem>(_filteredItems, SearchText, ScoreTopLevelItem);
RaiseItemsChanged(_filteredItems.Count());
}
}

View File

@@ -126,10 +126,6 @@ public class ExtensionWrapper : IExtensionWrapper
// We'll just return out nothing.
return;
}
else if (hr.Value != 0)
{
Logger.LogError($"Failed to find {ExtensionDisplayName}: {hr.Value}");
}
// Marshal.ThrowExceptionForHR(hr);
_extensionObject = MarshalInterface<IExtension>.FromAbi((nint)extensionPtr);

View File

@@ -285,15 +285,6 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to View log folder.
/// </summary>
public static string builtin_log_folder_command_name {
get {
return ResourceManager.GetString("builtin_log_folder_command_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to View log.
/// </summary>

View File

@@ -135,9 +135,6 @@
<data name="builtin_log_title" xml:space="preserve">
<value>View log</value>
</data>
<data name="builtin_log_folder_command_name" xml:space="preserve">
<value>View log folder</value>
</data>
<data name="builtin_reload_subtitle" xml:space="preserve">
<value>Reload Command Palette extensions</value>
</data>

View File

@@ -32,7 +32,6 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
private string _generatedId = string.Empty;
private HotkeySettings? _hotkey;
private IIconInfo? _initialIcon;
private CommandAlias? Alias { get; set; }
@@ -58,8 +57,6 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
public IIconInfo Icon => _commandItemViewModel.Icon;
public IIconInfo InitialIcon => _initialIcon ?? _commandItemViewModel.Icon;
ICommand? ICommandItem.Command => _commandItemViewModel.Command.Model.Unsafe;
IContextItem?[] ICommandItem.MoreCommands => _commandItemViewModel.MoreCommands
@@ -208,8 +205,6 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
DisplayTitle = fallback.DisplayTitle;
}
UpdateInitialIcon(false);
}
}
@@ -226,31 +221,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
FetchAliasFromAliasManager();
UpdateHotkey();
UpdateTags();
UpdateInitialIcon();
}
else if (e.PropertyName == nameof(CommandItem.Icon))
{
UpdateInitialIcon();
}
}
}
private void UpdateInitialIcon(bool raiseNotification = true)
{
if (_initialIcon != null || !_commandItemViewModel.Icon.IsSet)
{
return;
}
_initialIcon = _commandItemViewModel.Icon;
if (raiseNotification)
{
DoOnUiThread(
() =>
{
PropChanged?.Invoke(this, new PropChangedEventArgs(nameof(InitialIcon)));
});
}
}

View File

@@ -85,7 +85,7 @@ public partial class App : Application
AppWindow = new MainWindow();
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
((MainWindow)AppWindow).HandleLaunch(activatedEventArgs);
}
/// <summary>

View File

@@ -134,15 +134,6 @@ public sealed partial class CommandBar : UserControl,
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
}
/// <summary>
/// Sets focus to the "More" button after closing the context menu,
/// keeping keyboard navigation intuitive.
/// </summary>
public void FocusMoreCommandsButton()
{
MoreCommandsButton?.Focus(FocusState.Programmatic);
}
private void ContextMenuFlyout_Opened(object sender, object e)
{
// We need to wait until our flyout is opened to try and toss focus

View File

@@ -15,7 +15,6 @@
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
Background="Transparent"
PreviewKeyDown="UserControl_PreviewKeyDown"
mc:Ignorable="d">
<UserControl.Resources>

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.Messages;
@@ -116,24 +115,6 @@ public sealed partial class ContextMenu : UserControl,
}
}
/// <summary>
/// Handles Escape to close the context menu and return focus to the "More" button.
/// </summary>
private void UserControl_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Escape)
{
// Close the context menu (if not already handled)
WeakReferenceMessenger.Default.Send(new CloseContextMenuMessage());
// Find the parent CommandBar and set focus to MoreCommandsButton
var parent = this.FindParent<CommandBar>();
parent?.FocusMoreCommandsButton();
e.Handled = true;
}
}
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var prop = e.PropertyName;

View File

@@ -123,9 +123,6 @@ public sealed partial class MainWindow : WindowEx,
_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 static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
@@ -236,6 +233,9 @@ public sealed partial class MainWindow : WindowEx,
{
var hwnd = new HWND(hwndValue != 0 ? hwndValue : _hwnd);
// Make sure our HWND is cloaked before any possible window manipulations
Cloak();
// Remember, IsIconic == "minimized", which is entirely different state
// from "show/hide"
// If we're currently minimized, restore us first, before we reveal
@@ -243,9 +243,6 @@ public sealed partial class MainWindow : WindowEx,
// 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);
}
@@ -484,13 +481,8 @@ public sealed partial class MainWindow : WindowEx,
}
}
public void HandleLaunchNonUI(AppActivationArguments? activatedEventArgs)
public void HandleLaunch(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);
@@ -527,26 +519,9 @@ public sealed partial class MainWindow : WindowEx,
}
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);
}
Logger.LogError("COM exception when activating the application", ex);
}
Summon(string.Empty);
@@ -635,20 +610,6 @@ public sealed partial class MainWindow : WindowEx,
}
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));
@@ -673,6 +634,8 @@ public sealed partial class MainWindow : WindowEx,
// so that we can bind hotkeys to individual commands
if (!isVisible || !isRootHotkey)
{
Activate();
Summon(commandId);
}
else if (isRootHotkey)
@@ -708,6 +671,15 @@ public sealed partial class MainWindow : WindowEx,
var hotkeyIndex = (int)wParam.Value;
if (hotkeyIndex < _hotkeys.Count)
{
if (_ignoreHotKeyWhenFullScreen)
{
// If we're in full screen mode, ignore the hotkey
if (WindowHelper.IsWindowFullscreen())
{
return (LRESULT)IntPtr.Zero;
}
}
var hotkey = _hotkeys[hotkeyIndex];
HandleSummon(hotkey.CommandId);
}

View File

@@ -107,33 +107,12 @@ internal sealed class Program
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
using var redirectSemaphore = new Semaphore(0, 1);
var redirectTimeout = TimeSpan.FromSeconds(32);
_ = Task.Run(() =>
var redirectSemaphore = new Semaphore(0, 1);
Task.Run(() =>
{
using var cts = new CancellationTokenSource(redirectTimeout);
try
{
keyInstance.RedirectActivationToAsync(args)
.AsTask(cts.Token)
.GetAwaiter()
.GetResult();
}
catch (OperationCanceledException)
{
Logger.LogError($"Failed to activate existing instance; timed out after {redirectTimeout}.");
}
catch (Exception ex)
{
Logger.LogError("Failed to activate existing instance", ex);
}
finally
{
redirectSemaphore.Release();
}
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
redirectSemaphore.Release();
});
_ = PInvoke.CoWaitForMultipleObjects(
(uint)CWMO_FLAGS.CWMO_DEFAULT,
PInvoke.INFINITE,
@@ -145,14 +124,13 @@ internal sealed class Program
{
// If we already have a form, display the message now.
// Otherwise, add it to the collection for displaying later.
if (App.Current?.AppWindow is MainWindow mainWindow)
if (App.Current is App thisApp)
{
// LOAD BEARING
// This must be synchronous to ensure the method does not return
// before the activation is fully handled and the parameters are processed.
// The sending instance remains blocked until this returns; afterward it may quit,
// causing the activation arguments to be lost.
mainWindow.HandleLaunchNonUI(args);
if (thisApp.AppWindow is not null and
MainWindow mainWindow)
{
uiContext?.Post(_ => mainWindow.HandleLaunch(args), null);
}
}
}
}

View File

@@ -125,7 +125,7 @@
Width="20"
Height="20"
AutomationProperties.AccessibilityView="Raw"
SourceKey="{x:Bind InitialIcon, Mode=OneWay}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind helpers:IconCacheProvider.SourceRequested}" />
</cpcontrols:ContentIcon.Content>
</cpcontrols:ContentIcon>

View File

@@ -24,15 +24,23 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="TitleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="ms-appx:///Assets/icon.svg" />
</TitleBar.LeftHeader>
</TitleBar>
<!-- TO DO: Replace this with WinUI TitleBar once that ships. -->
<StackPanel
x:Name="AppTitleBar"
Grid.Row="0"
Height="48"
Margin="16,0,0,0"
Orientation="Horizontal">
<Image
Width="16"
Height="16"
Source="ms-appx:///Assets/icon.svg" />
<TextBlock
x:Uid="CmdPalSettingsHeader"
Margin="12,0,0,0"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
<NavigationView
x:Name="NavView"
Grid.Row="1"
@@ -69,6 +77,7 @@
x:Name="NavigationBreadcrumbBar"
Grid.Row="0"
MaxWidth="1000"
Margin="16,0,0,0"
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
<BreadcrumbBar.ItemTemplate>

View File

@@ -31,10 +31,8 @@ public sealed partial class SettingsWindow : WindowEx,
this.InitializeComponent();
this.ExtendsContentIntoTitleBar = true;
this.SetIcon();
var title = RS_.GetString("SettingsWindowTitle");
this.AppWindow.Title = title;
this.AppWindow.Title = RS_.GetString("SettingsWindowTitle");
this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
this.TitleBar.Title = title;
PositionCentered();
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);

View File

@@ -438,7 +438,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="NavigationPaneClosed" xml:space="preserve">
<value>Navigation pane closed</value>
</data>
<data name="NavigationPaneOpened" xml:space="preserve">
<data name="NavigationPageOpened" xml:space="preserve">
<value>Navigation page opened</value>
</data>
</root>

View File

@@ -15,13 +15,13 @@ public class MockSettingsInterface : ISettingsInterface
public bool GlobalIfURI { get; set; }
public uint HistoryItemCount { get; set; }
public string ShowHistory { get; set; }
public MockSettingsInterface(uint historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)
public MockSettingsInterface(string showHistory = "none", bool globalIfUri = true, List<HistoryItem> mockHistory = null)
{
_historyItems = mockHistory ?? new List<HistoryItem>();
GlobalIfURI = globalIfUri;
HistoryItemCount = historyItemCount;
ShowHistory = showHistory;
}
public List<ListItem> LoadHistory()
@@ -50,9 +50,9 @@ public class MockSettingsInterface : ISettingsInterface
_historyItems.Add(historyItem);
// Simulate the same logic as SettingsManager
if (HistoryItemCount > 0)
if (int.TryParse(ShowHistory, out var maxHistoryItems) && maxHistoryItems > 0)
{
while (_historyItems.Count > HistoryItemCount)
while (_historyItems.Count > maxHistoryItems)
{
_historyItems.RemoveAt(0); // Remove the oldest item
}

View File

@@ -54,7 +54,7 @@ public class QueryTests : CommandPaletteUnitTestBase
new HistoryItem("another search", DateTime.Parse("2024-01-02 13:00:00", CultureInfo.CurrentCulture)),
};
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 5);
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "5");
var page = new WebSearchListPage(settings);
@@ -89,7 +89,7 @@ public class QueryTests : CommandPaletteUnitTestBase
new HistoryItem("another search4", DateTime.Parse("2024-01-05 13:00:00", CultureInfo.CurrentCulture)),
};
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 5);
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "5");
var page = new WebSearchListPage(settings);
@@ -122,7 +122,7 @@ public class QueryTests : CommandPaletteUnitTestBase
new HistoryItem("another search5", DateTime.Parse("2024-01-06 13:00:00", CultureInfo.CurrentCulture)),
};
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, historyItemCount: 0);
var settings = new MockSettingsInterface(mockHistory: mockHistoryItems, showHistory: "None");
var page = new WebSearchListPage(settings);

View File

@@ -5,7 +5,7 @@
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
<VersionMajor>0</VersionMajor>
<VersionMinor>5</VersionMinor>
<VersionMinor>4</VersionMinor>
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
</PropertyGroup>
</Project>

View File

@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions;
@@ -43,28 +45,6 @@ public partial class AllAppsCommandProvider : CommandProvider
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
}
public static int TopLevelResultLimit
{
get
{
var limitSetting = AllAppsSettings.Instance.SearchResultLimit;
if (limitSetting is null)
{
return -1;
}
var quantity = -1;
if (int.TryParse(limitSetting, out var result))
{
quantity = result;
}
return quantity;
}
}
public override ICommandItem[] TopLevelCommands() => [_listItem, .._page.GetPinnedApps()];
public ICommandItem? LookupApp(string displayName)

View File

@@ -20,16 +20,6 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
private static string Experimental(string propertyName) => $"{_namespace}.experimental.{propertyName}";
private static readonly List<ChoiceSetSetting.Choice> _searchResultLimitChoices =
[
new ChoiceSetSetting.Choice(Resources.limit_none, "-1"),
new ChoiceSetSetting.Choice(Resources.limit_0, "0"),
new ChoiceSetSetting.Choice(Resources.limit_1, "1"),
new ChoiceSetSetting.Choice(Resources.limit_5, "5"),
new ChoiceSetSetting.Choice(Resources.limit_10, "10"),
new ChoiceSetSetting.Choice(Resources.limit_20, "20"),
];
#pragma warning disable SA1401 // Fields should be private
internal static AllAppsSettings Instance = new();
#pragma warning restore SA1401 // Fields should be private
@@ -52,14 +42,6 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
public bool EnablePathEnvironmentVariableSource => _enablePathEnvironmentVariableSource.Value;
private readonly ChoiceSetSetting _searchResultLimitSource = new(
Namespaced(nameof(SearchResultLimit)),
Resources.limit_fallback_results_source,
Resources.limit_fallback_results_source_description,
_searchResultLimitChoices);
public string SearchResultLimit => _searchResultLimitSource.Value ?? string.Empty;
private readonly ToggleSetting _enableStartMenuSource = new(
Namespaced(nameof(EnableStartMenuSource)),
Resources.enable_start_menu_source,
@@ -105,7 +87,6 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
Settings.Add(_enableDesktopSource);
Settings.Add(_enableRegistrySource);
Settings.Add(_enablePathEnvironmentVariableSource);
Settings.Add(_searchResultLimitSource);
// Load settings from file upon initialization
LoadSettings();

View File

@@ -133,13 +133,17 @@ internal sealed partial class AppListItem : ListItem
newCommands.Add(new Separator());
// 0x50 = P
// Full key chord would be Ctrl+P
var pinKeyChord = KeyChordHelpers.FromModifiers(true, false, false, false, 0x50, 0);
if (isPinned)
{
newCommands.Add(
new CommandContextItem(
new UnpinAppCommand(this.AppIdentifier))
{
RequestedShortcut = KeyChords.TogglePin,
RequestedShortcut = pinKeyChord,
});
}
else
@@ -148,7 +152,7 @@ internal sealed partial class AppListItem : ListItem
new CommandContextItem(
new PinAppCommand(this.AppIdentifier))
{
RequestedShortcut = KeyChords.TogglePin,
RequestedShortcut = pinKeyChord,
});
}

View File

@@ -1,130 +0,0 @@
// 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;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Management.Deployment;
namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class UninstallApplicationCommand : InvokableCommand
{
// This is a ms-settings URI that opens the Apps & Features page in Windows Settings.
// It's correct and follows the Microsoft documentation:
// https://learn.microsoft.com/en-us/windows/apps/develop/launch/launch-settings-app#apps
private const string AppsFeaturesUri = "ms-settings:appsfeatures";
private readonly UWPApplication? _uwpTarget;
private readonly Win32Program? _win32Target;
public UninstallApplicationCommand(UWPApplication target)
{
Name = Resources.uninstall_application;
Icon = Icons.UninstallApplicationIcon;
_uwpTarget = target ?? throw new ArgumentNullException(nameof(target));
}
public UninstallApplicationCommand(Win32Program target)
{
Name = Resources.uninstall_application;
Icon = Icons.UninstallApplicationIcon;
_win32Target = target ?? throw new ArgumentNullException(nameof(target));
}
private async Task<CommandResult> UninstallUwpAppAsync(UWPApplication app)
{
if (string.IsNullOrWhiteSpace(app.Package.FullName))
{
Logger.LogError($"Critical error while uninstalling: packageFullName cannot be null or empty.");
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_failed), app.DisplayName),
Result = CommandResult.KeepOpen(),
});
}
try
{
// Which timeout to use for the uninstallation operation?
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)))
{
var packageManager = new PackageManager();
var result = await packageManager.RemovePackageAsync(app.Package.FullName).AsTask(cts.Token);
if (result.ErrorText is not null && result.ErrorText.Length > 0)
{
Logger.LogError($"Failed to uninstall {app.Package.FullName}: {result.ErrorText}");
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_failed), app.DisplayName),
Result = CommandResult.KeepOpen(),
});
}
}
// TODO: Update the Search results after uninstalling the app - unsure how to do this yet.
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_successful), app.DisplayName),
Result = CommandResult.GoHome(),
});
}
catch (OperationCanceledException)
{
Logger.LogError($"Timeout exceeded while uninstalling {app.Package.FullName}");
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_failed), app.DisplayName),
Result = CommandResult.KeepOpen(),
});
}
catch (UnauthorizedAccessException ex)
{
Logger.LogError($"Permission denied to uninstall {app.Package.FullName}. Elevated privileges may be required. Error: {ex.Message}");
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_failed), app.DisplayName),
Result = CommandResult.KeepOpen(),
});
}
catch (Exception ex)
{
Logger.LogError($"An unexpected error occurred during uninstallation of {app.Package.FullName}: {ex.Message}");
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_failed), app.DisplayName),
Result = CommandResult.KeepOpen(),
});
}
}
public override CommandResult Invoke()
{
if (_uwpTarget is not null)
{
return UninstallUwpAppAsync(_uwpTarget).ConfigureAwait(false).GetAwaiter().GetResult();
}
if (_win32Target is not null)
{
Process.Start(new ProcessStartInfo
{
FileName = AppsFeaturesUri,
UseShellExecute = true,
});
return CommandResult.Dismiss();
}
Logger.LogError("UninstallApplicationCommand invoked with no target.");
return CommandResult.Dismiss();
}
}

View File

@@ -1,66 +0,0 @@
// 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;
using System.Globalization;
using System.Text;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.Apps.Commands;
internal sealed partial class UninstallApplicationConfirmation : InvokableCommand
{
private readonly UWPApplication? _uwpTarget;
private readonly Win32Program? _win32Target;
public UninstallApplicationConfirmation(UWPApplication target)
{
Name = Resources.uninstall_application;
Icon = Icons.UninstallApplicationIcon;
_uwpTarget = target ?? throw new ArgumentNullException(nameof(target));
}
public UninstallApplicationConfirmation(Win32Program target)
{
Name = Resources.uninstall_application;
Icon = Icons.UninstallApplicationIcon;
_win32Target = target ?? throw new ArgumentNullException(nameof(target));
}
public override CommandResult Invoke()
{
UninstallApplicationCommand uninstallCommand;
var applicationTitle = Resources.uninstall_application;
if (_uwpTarget is not null)
{
uninstallCommand = new UninstallApplicationCommand(_uwpTarget);
applicationTitle = _uwpTarget.DisplayName;
}
else if (_win32Target is not null)
{
uninstallCommand = new UninstallApplicationCommand(_win32Target);
applicationTitle = _win32Target.Name;
}
else
{
Logger.LogError("UninstallApplicationCommand invoked with no target.");
return CommandResult.Dismiss();
}
var confirmArgs = new ConfirmationArgs()
{
Title = string.Format(CultureInfo.CurrentCulture, CompositeFormat.Parse(Resources.uninstall_application_confirm_title), applicationTitle),
Description = Resources.uninstall_application_confirm_description,
PrimaryCommand = uninstallCommand,
IsPrimaryCommandCritical = true,
};
return CommandResult.Confirm(confirmArgs);
}
}

View File

@@ -21,6 +21,4 @@ internal sealed class Icons
public static IconInfo UnpinIcon { get; } = new("\uE77A"); // Unpin icon
public static IconInfo PinIcon { get; } = new("\uE840"); // Pin icon
public static IconInfo UninstallApplicationIcon { get; } = new("\uE74D"); // Uninstall icon
}

View File

@@ -1,23 +0,0 @@
// 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.Common.Helpers;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Ext.Apps;
internal static class KeyChords
{
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
internal static KeyChord RunAsAdministrator { get; } = WellKnownKeyChords.RunAsAdministrator;
internal static KeyChord RunAsDifferentUser { get; } = WellKnownKeyChords.RunAsDifferentUser;
internal static KeyChord TogglePin { get; } = WellKnownKeyChords.TogglePin;
}

View File

@@ -25,7 +25,6 @@
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>

View File

@@ -87,7 +87,7 @@ public class UWPApplication : IUWPApplication
new CommandContextItem(
new RunAsAdminCommand(UniqueIdentifier, string.Empty, true))
{
RequestedShortcut = KeyChords.RunAsAdministrator,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
});
// We don't add context menu to 'run as different user', because UWP applications normally installed per user and not for all users.
@@ -97,7 +97,7 @@ public class UWPApplication : IUWPApplication
new CommandContextItem(
new CopyTextCommand(Location) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChords.CopyFilePath,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});
commands.Add(
@@ -107,24 +107,16 @@ public class UWPApplication : IUWPApplication
Name = Resources.open_containing_folder,
})
{
RequestedShortcut = KeyChords.OpenFileLocation,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
});
commands.Add(
new CommandContextItem(
new OpenInConsoleCommand(Package.Location))
{
RequestedShortcut = KeyChords.OpenInConsole,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
});
commands.Add(
new CommandContextItem(
new UninstallApplicationConfirmation(this))
{
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Delete),
IsCritical = true,
});
return commands;
}

View File

@@ -191,44 +191,34 @@ public class Win32Program : IProgram
commands.Add(new CommandContextItem(
new RunAsAdminCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory, false))
{
RequestedShortcut = KeyChords.RunAsAdministrator,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Enter),
});
commands.Add(new CommandContextItem(
new RunAsUserCommand(!string.IsNullOrEmpty(LnkFilePath) ? LnkFilePath : FullPath, ParentDirectory))
{
RequestedShortcut = KeyChords.RunAsDifferentUser,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.U),
});
}
commands.Add(new CommandContextItem(
new CopyTextCommand(FullPath) { Name = Resources.copy_path })
{
RequestedShortcut = KeyChords.CopyFilePath,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.C),
});
commands.Add(new CommandContextItem(
new OpenPathCommand(ParentDirectory))
{
RequestedShortcut = KeyChords.OpenFileLocation,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.E),
});
commands.Add(new CommandContextItem(
new OpenInConsoleCommand(ParentDirectory))
{
RequestedShortcut = KeyChords.OpenInConsole,
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.R),
});
if (AppType == ApplicationType.ShortcutApplication || AppType == ApplicationType.ApprefApplication || AppType == ApplicationType.Win32Application)
{
commands.Add(new CommandContextItem(
new UninstallApplicationConfirmation(this))
{
RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Delete),
IsCritical = true,
});
}
return commands;
}

View File

@@ -159,78 +159,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 0.
/// </summary>
internal static string limit_0 {
get {
return ResourceManager.GetString("limit_0", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1.
/// </summary>
internal static string limit_1 {
get {
return ResourceManager.GetString("limit_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 10.
/// </summary>
internal static string limit_10 {
get {
return ResourceManager.GetString("limit_10", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 20.
/// </summary>
internal static string limit_20 {
get {
return ResourceManager.GetString("limit_20", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 5.
/// </summary>
internal static string limit_5 {
get {
return ResourceManager.GetString("limit_5", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Limit the number of applications returned from the top level.
/// </summary>
internal static string limit_fallback_results_source {
get {
return ResourceManager.GetString("limit_fallback_results_source", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Limit fallback results to n apps.
/// </summary>
internal static string limit_fallback_results_source_description {
get {
return ResourceManager.GetString("limit_fallback_results_source_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unlimited.
/// </summary>
internal static string limit_none {
get {
return ResourceManager.GetString("limit_none", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open containing folder.
/// </summary>
@@ -330,51 +258,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Uninstall.
/// </summary>
internal static string uninstall_application {
get {
return ResourceManager.GetString("uninstall_application", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This app and its related information will be removed..
/// </summary>
internal static string uninstall_application_confirm_description {
get {
return ResourceManager.GetString("uninstall_application_confirm_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Uninstall &quot;{0}&quot;?.
/// </summary>
internal static string uninstall_application_confirm_title {
get {
return ResourceManager.GetString("uninstall_application_confirm_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error while uninstalling &apos;{0}&apos;.
/// </summary>
internal static string uninstall_application_failed {
get {
return ResourceManager.GetString("uninstall_application_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos; has been successfully uninstalled..
/// </summary>
internal static string uninstall_application_successful {
get {
return ResourceManager.GetString("uninstall_application_successful", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unpin.
/// </summary>

View File

@@ -198,43 +198,4 @@
<data name="unpin_app" xml:space="preserve">
<value>Unpin</value>
</data>
<data name="uninstall_application" xml:space="preserve">
<value>Uninstall</value>
</data>
<data name="uninstall_application_successful" xml:space="preserve">
<value>'{0}' has been successfully uninstalled.</value>
</data>
<data name="uninstall_application_failed" xml:space="preserve">
<value>Error while uninstalling '{0}'</value>
</data>
<data name="uninstall_application_confirm_description" xml:space="preserve">
<value>This app and its related information will be removed.</value>
</data>
<data name="uninstall_application_confirm_title" xml:space="preserve">
<value>Uninstall "{0}"?</value>
</data>
<data name="limit_1" xml:space="preserve">
<value>1</value>
</data>
<data name="limit_5" xml:space="preserve">
<value>5</value>
</data>
<data name="limit_10" xml:space="preserve">
<value>10</value>
</data>
<data name="limit_20" xml:space="preserve">
<value>20</value>
</data>
<data name="limit_fallback_results_source" xml:space="preserve">
<value>Limit the number of applications returned from the top level</value>
</data>
<data name="limit_fallback_results_source_description" xml:space="preserve">
<value>Limit fallback results to n apps</value>
</data>
<data name="limit_0" xml:space="preserve">
<value>0</value>
</data>
<data name="limit_none" xml:space="preserve">
<value>Unlimited</value>
</data>
</root>
</root>

View File

@@ -91,9 +91,9 @@ internal sealed partial class IndexerListItem : ListItem
}
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }) { RequestedShortcut = KeyChords.OpenFileLocation });
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }) { RequestedShortcut = KeyChords.CopyFilePath });
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)) { RequestedShortcut = KeyChords.OpenInConsole });
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }));
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }));
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)));
commands.Add(new CommandContextItem(new OpenPropertiesCommand(fullPath)));
if (IsActionsFeatureEnabled && ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))

View File

@@ -1,17 +0,0 @@
// 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.Common.Helpers;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.Ext.Indexer;
internal static class KeyChords
{
internal static KeyChord OpenFileLocation { get; } = WellKnownKeyChords.OpenFileLocation;
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
}

View File

@@ -17,7 +17,6 @@ internal sealed partial class FallbackSystemCommandItem : FallbackCommandItem
{
Title = string.Empty;
Subtitle = string.Empty;
Icon = Icons.LockIcon;
var isBootedInUefiMode = settings.GetSystemFirmwareType() == FirmwareType.Uefi;
var hideEmptyRB = settings.HideEmptyRecycleBin();

View File

@@ -22,7 +22,6 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
{
Title = string.Empty;
Subtitle = string.Empty;
Icon = Icons.TimeDateIcon;
_settingsManager = settings;
_timestamp = timestamp;

View File

@@ -79,22 +79,16 @@ public sealed partial class TimeDateCalculator
}
else
{
List<(int Score, AvailableResult Item)> itemScores = [];
// Generate filtered list of results
foreach (var f in availableFormats)
{
var score = f.Score(query, f.Label, f.AlternativeSearchTag);
if (score > 0)
{
itemScores.Add((score, f));
results.Add(f.ToListItem());
}
}
results = itemScores
.OrderByDescending(s => s.Score)
.Select(s => s.Item.ToListItem())
.ToList();
}
if (results.Count == 0)

View File

@@ -34,7 +34,7 @@ internal sealed partial class SearchWebCommand : InvokableCommand
return CommandResult.KeepOpen();
}
if (_settingsManager.HistoryItemCount != 0)
if (_settingsManager.ShowHistory != Resources.history_none)
{
_settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now));
}

View File

@@ -11,7 +11,7 @@ public interface ISettingsInterface
{
public bool GlobalIfURI { get; }
public uint HistoryItemCount { get; }
public string ShowHistory { get; }
public List<ListItem> LoadHistory();

View File

@@ -16,7 +16,6 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
public class SettingsManager : JsonSettingsManager, ISettingsInterface
{
private const string HistoryItemCountLegacySettingsKey = "ShowHistory";
private readonly string _historyPath;
private static readonly string _namespace = "websearch";
@@ -25,11 +24,11 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
private static readonly List<ChoiceSetSetting.Choice> _choices =
[
new ChoiceSetSetting.Choice(Resources.history_none, "None"),
new ChoiceSetSetting.Choice(Resources.history_1, "1"),
new ChoiceSetSetting.Choice(Resources.history_5, "5"),
new ChoiceSetSetting.Choice(Resources.history_10, "10"),
new ChoiceSetSetting.Choice(Resources.history_20, "20"),
new ChoiceSetSetting.Choice(Resources.history_none, Resources.history_none),
new ChoiceSetSetting.Choice(Resources.history_1, Resources.history_1),
new ChoiceSetSetting.Choice(Resources.history_5, Resources.history_5),
new ChoiceSetSetting.Choice(Resources.history_10, Resources.history_10),
new ChoiceSetSetting.Choice(Resources.history_20, Resources.history_20),
];
private readonly ToggleSetting _globalIfURI = new(
@@ -38,15 +37,15 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
Resources.plugin_global_if_uri,
false);
private readonly ChoiceSetSetting _historyItemCount = new(
Namespaced(HistoryItemCountLegacySettingsKey),
Resources.plugin_history_item_count,
Resources.plugin_history_item_count,
private readonly ChoiceSetSetting _showHistory = new(
Namespaced(nameof(ShowHistory)),
Resources.plugin_show_history,
Resources.plugin_show_history,
_choices);
public bool GlobalIfURI => _globalIfURI.Value;
public uint HistoryItemCount => uint.TryParse(_historyItemCount.Value, out var value) ? value : 0;
public string ShowHistory => _showHistory.Value ?? string.Empty;
internal static string SettingsJsonPath()
{
@@ -91,11 +90,11 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
// Add the new history item
historyItems.Add(historyItem);
// Determine the maximum number of items to keep based on HistoryItemCount
if (HistoryItemCount > 0)
// Determine the maximum number of items to keep based on ShowHistory
if (int.TryParse(ShowHistory, out var maxHistoryItems) && maxHistoryItems > 0)
{
// Keep only the most recent `maxHistoryItems` items
while (historyItems.Count > HistoryItemCount)
while (historyItems.Count > maxHistoryItems)
{
historyItems.RemoveAt(0); // Remove the oldest item
}
@@ -151,7 +150,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
_historyPath = HistoryStateJsonPath();
Settings.Add(_globalIfURI);
Settings.Add(_historyItemCount);
Settings.Add(_showHistory);
// Load settings from file upon initialization
LoadSettings();
@@ -189,11 +188,11 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
base.SaveSettings();
try
{
if (HistoryItemCount == 0)
if (ShowHistory == Resources.history_none)
{
ClearHistory();
}
else if (HistoryItemCount > 0)
else if (int.TryParse(ShowHistory, out var maxHistoryItems) && maxHistoryItems > 0)
{
// Trim the history file if there are more items than the new limit
if (File.Exists(_historyPath))
@@ -202,10 +201,10 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
var historyItems = JsonSerializer.Deserialize<List<HistoryItem>>(existingContent, WebSearchJsonSerializationContext.Default.ListHistoryItem) ?? [];
// Check if trimming is needed
if (historyItems.Count > HistoryItemCount)
if (historyItems.Count > maxHistoryItems)
{
// Trim the list to keep only the most recent `HistoryItemCount` items
historyItems = historyItems.Skip((int)(historyItems.Count - HistoryItemCount)).ToList();
// Trim the list to keep only the most recent `maxHistoryItems` items
historyItems = historyItems.Skip(historyItems.Count - maxHistoryItems).ToList();
// Save the trimmed history back to the file
var trimmedHistoryJson = JsonSerializer.Serialize(historyItems, WebSearchJsonSerializationContext.Default.ListHistoryItem);

View File

@@ -33,7 +33,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
_allItems = [];
Id = "com.microsoft.cmdpal.websearch";
_settingsManager = settingsManager;
_historyItems = _settingsManager.HistoryItemCount != 0 ? _settingsManager.LoadHistory() : null;
_historyItems = _settingsManager.ShowHistory != Resources.history_none ? _settingsManager.LoadHistory() : null;
if (_historyItems is not null)
{
_allItems.AddRange(_historyItems);
@@ -57,7 +57,7 @@ internal sealed partial class WebSearchListPage : DynamicListPage
if (_historyItems is not null)
{
filteredHistoryItems = _settingsManager.HistoryItemCount != 0 ? ListHelpers.FilterList(_historyItems, query).OfType<ListItem>() : null;
filteredHistoryItems = _settingsManager.ShowHistory != Resources.history_none ? ListHelpers.FilterList(_historyItems, query).OfType<ListItem>() : null;
}
var results = new List<ListItem>();

View File

@@ -168,15 +168,6 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Determines the number of history items to show from previous searches.
/// </summary>
public static string plugin_history_item_count {
get {
return ResourceManager.GetString("plugin_history_item_count", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to In the default browser.
/// </summary>
@@ -240,6 +231,15 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Determines the number of history items to show from previous searches.
/// </summary>
public static string plugin_show_history {
get {
return ResourceManager.GetString("plugin_show_history", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>

View File

@@ -172,7 +172,7 @@
<data name="plugin_search_failed" xml:space="preserve">
<value>Failed to open {0}.</value>
</data>
<data name="plugin_history_item_count" xml:space="preserve">
<data name="plugin_show_history" xml:space="preserve">
<value>Determines the number of history items to show from previous searches</value>
</data>
<data name="settings_page_name" xml:space="preserve">

View File

@@ -65,7 +65,6 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
if (_results is not null && _results.Count != 0)
{
var stopwatch = Stopwatch.StartNew();
var count = _results.Count;
var results = new ListItem[count];
var next = 0;
@@ -83,8 +82,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;
}
@@ -247,22 +244,15 @@ internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
// foreach (var catalog in connections)
{
Stopwatch findPackages_stopwatch = new();
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 searchResults = await internalSearchTask;
findPackages_stopwatch.Stop();
Logger.LogDebug($"FindPackages for \"{searchDebugText}\" took {findPackages_stopwatch.ElapsedMilliseconds}ms", memberName: nameof(DoSearchAsync));
// TODO more error handling like this:
if (searchResults.Status != FindPackagesResultStatus.Ok)
{
@@ -271,8 +261,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

View File

@@ -43,18 +43,13 @@ public partial class ListHelpers
}
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
{
return FilterListWithScores<T>(items, query, scoreFunction)
.Select(score => score.Item);
}
public static IEnumerable<Scored<T>> FilterListWithScores<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
{
var scores = items
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
.Where(score => score.Score > 0)
.OrderByDescending(score => score.Score);
return scores;
return scores
.Select(score => score.Item);
}
/// <summary>

View File

@@ -372,17 +372,20 @@ namespace UITests_FancyZones
// launch FancyZones settings page
private void LaunchFancyZones()
{
this.Find<NavigationViewItem>(By.AccessibilityId("WindowingAndLayoutsNavItem")).Click();
if (this.FindAll<NavigationViewItem>("FancyZones").Count == 0)
{
this.Find<NavigationViewItem>("Windowing & Layouts").Click();
}
this.Find<NavigationViewItem>(By.AccessibilityId("FancyZonesNavItem")).Click();
this.Find<ToggleSwitch>(By.AccessibilityId("EnableFancyZonesToggleSwitch")).Toggle(true);
this.Find<NavigationViewItem>("FancyZones").Click();
this.Find<ToggleSwitch>("Enable FancyZones").Toggle(true);
this.Session.SetMainWindowSize(WindowSize.Large);
Find<Element>(By.AccessibilityId("HeaderPresenter")).Click();
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
ZoneBehaviourSettings(TestContext.TestName);
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 500, 10000);
this.Session.Attach(PowerToysModule.FancyZone);
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
@@ -400,7 +403,7 @@ namespace UITests_FancyZones
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
this.Session.Attach(PowerToysModule.PowerToysSettings);
SetupCustomLayouts();
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 5000, 5000);
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
this.Session.Attach(PowerToysModule.FancyZone);
this.Find<Microsoft.PowerToys.UITest.Button>("Maximize").Click();

View File

@@ -43,32 +43,11 @@ private:
//contains the non localized key of the powertoy
std::wstring app_key;
// Update registration based on enabled state
void UpdateRegistration(bool enabled)
{
if (enabled)
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::EnsureRegistered();
Logger::info(L"ImageResizer context menu registered");
#endif
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::Unregister();
Logger::info(L"ImageResizer context menu unregistered");
#endif
}
}
public:
// Constructor
ImageResizerModule()
{
m_enabled = CSettingsInstance().GetEnabled();
UpdateRegistration(m_enabled);
app_name = GET_RESOURCE_STRING(IDS_IMAGERESIZER);
app_key = ImageResizerConstants::ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::imageResizerLoggerName);
@@ -133,7 +112,10 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
UpdateRegistration(m_enabled);
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::EnsureRegistered();
#endif
Trace::EnableImageResizer(m_enabled);
}
@@ -141,8 +123,11 @@ public:
virtual void disable()
{
m_enabled = false;
UpdateRegistration(m_enabled);
Trace::EnableImageResizer(m_enabled);
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
ImageResizerRuntimeRegistration::Unregister();
Logger::info(L"ImageResizer context menu unregistered (Win10)");
#endif
}
// Returns if the powertoys is enabled

View File

@@ -168,25 +168,6 @@ private:
//contains the non localized key of the powertoy
std::wstring app_key;
// Update registration based on enabled state
void UpdateRegistration(bool enabled)
{
if (enabled)
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::EnsureRegistered();
Logger::info(L"PowerRename context menu registered");
#endif
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::Unregister();
Logger::info(L"PowerRename context menu unregistered");
#endif
}
}
public:
// Return the localized display name of the powertoy
virtual PCWSTR get_name() override
@@ -221,7 +202,9 @@ public:
package::RegisterSparsePackage(path, packageUri);
}
}
UpdateRegistration(m_enabled);
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::EnsureRegistered();
#endif
}
// Disable the powertoy
@@ -229,7 +212,10 @@ public:
{
m_enabled = false;
Logger::info(L"PowerRename disabled");
UpdateRegistration(m_enabled);
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
PowerRenameRuntimeRegistration::Unregister();
Logger::info(L"PowerRename context menu unregistered (Win10)");
#endif
}
// Returns if the powertoy is enabled
@@ -329,7 +315,6 @@ public:
void init_settings()
{
m_enabled = CSettingsInstance().GetEnabled();
UpdateRegistration(m_enabled);
Trace::EnablePowerRename(m_enabled);
}

View File

@@ -15,10 +15,10 @@ namespace RegistryPreview
/// </summary>
private void AppWindow_Closing(Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowClosingEventArgs args)
{
jsonWindowPlacement.SetNamedValue("appWindow.Position.X", JsonValue.CreateNumberValue(AppWindow.Position.X));
jsonWindowPlacement.SetNamedValue("appWindow.Position.Y", JsonValue.CreateNumberValue(AppWindow.Position.Y));
jsonWindowPlacement.SetNamedValue("appWindow.Size.Width", JsonValue.CreateNumberValue(AppWindow.Size.Width));
jsonWindowPlacement.SetNamedValue("appWindow.Size.Height", JsonValue.CreateNumberValue(AppWindow.Size.Height));
jsonWindowPlacement.SetNamedValue("appWindow.Position.X", JsonValue.CreateNumberValue(appWindow.Position.X));
jsonWindowPlacement.SetNamedValue("appWindow.Position.Y", JsonValue.CreateNumberValue(appWindow.Position.Y));
jsonWindowPlacement.SetNamedValue("appWindow.Size.Width", JsonValue.CreateNumberValue(appWindow.Size.Width));
jsonWindowPlacement.SetNamedValue("appWindow.Size.Height", JsonValue.CreateNumberValue(appWindow.Size.Height));
}
/// <summary>

View File

@@ -15,19 +15,38 @@
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid x:Name="MainGrid" Loaded="Grid_Loaded">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TitleBar x:Name="titleBar">
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
<TitleBar.LeftHeader>
<ImageIcon
Height="16"
Margin="16,0,0,0"
Source="/Assets/RegistryPreview/RegistryPreview.ico" />
</TitleBar.LeftHeader>
</TitleBar>
<Grid
x:Name="titleBar"
Grid.Row="0"
Height="32"
Margin="16,0"
ColumnSpacing="16"
IsHitTestVisible="True">
<Grid.ColumnDefinitions>
<!--<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>-->
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<!--<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>-->
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="../Assets/RegistryPreview/RegistryPreview.ico" />
<TextBlock
x:Name="titleBarText"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding ApplicationTitle}" />
</Grid>
</Grid>
</winuiex:WindowEx>
</winuiex:WindowEx>

View File

@@ -6,7 +6,6 @@ using System;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
@@ -24,6 +23,7 @@ namespace RegistryPreview
private const string APPNAME = "RegistryPreview";
// private members
private Microsoft.UI.Windowing.AppWindow appWindow;
private JsonObject jsonWindowPlacement;
private string settingsFolder = string.Empty;
private string windowPlacementFile = "app-placement.json";
@@ -38,15 +38,20 @@ namespace RegistryPreview
settingsFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerToys\" + APPNAME;
OpenWindowPlacementFile(settingsFolder, windowPlacementFile);
// Update the Win32 looking window with the correct icon (and grab the appWindow handle for later)
IntPtr windowHandle = this.GetWindowHandle();
WindowId windowId = Win32Interop.GetWindowIdFromWindow(windowHandle);
appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
appWindow.SetIcon("Assets\\RegistryPreview\\RegistryPreview.ico");
// TODO(stefan)
AppWindow.Closing += AppWindow_Closing;
appWindow.Closing += AppWindow_Closing;
Activated += MainWindow_Activated;
// Extend the canvas to include the title bar so the app can support theming
ExtendsContentIntoTitleBar = true;
IntPtr windowHandle = this.GetWindowHandle();
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(windowHandle);
SetTitleBar(titleBar);
AppWindow.SetIcon("Assets\\RegistryPreview\\RegistryPreview.ico");
// if have settings, update the location of the window
if (jsonWindowPlacement != null)
@@ -61,7 +66,7 @@ namespace RegistryPreview
// check to make sure the size values are reasonable before attempting to restore the last saved size
if (size.Width >= 320 && size.Height >= 240)
{
AppWindow.Resize(size);
appWindow.Resize(size);
}
}
@@ -75,7 +80,7 @@ namespace RegistryPreview
// check to make sure the move values are reasonable before attempting to restore the last saved location
if (point.X >= 0 && point.Y >= 0)
{
AppWindow.Move(point);
appWindow.Move(point);
}
}
}
@@ -87,6 +92,20 @@ namespace RegistryPreview
PowerToysTelemetry.Log.WriteEvent(new RegistryPreviewEditorStartFinishEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() });
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
titleBarText.Foreground =
(SolidColorBrush)Application.Current.Resources["WindowCaptionForegroundDisabled"];
}
else
{
titleBarText.Foreground =
(SolidColorBrush)Application.Current.Resources["WindowCaptionForeground"];
}
}
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
MainGrid.Children.Add(MainPage);
@@ -99,23 +118,23 @@ namespace RegistryPreview
if (string.IsNullOrEmpty(filename))
{
titleBar.Title = APPNAME;
AppWindow.Title = APPNAME;
titleBarText.Text = APPNAME;
appWindow.Title = APPNAME;
}
else
{
string[] file = filename.Split('\\');
if (file.Length > 0)
{
titleBar.Title = file[file.Length - 1] + " - " + APPNAME;
titleBarText.Text = file[file.Length - 1] + " - " + APPNAME;
}
else
{
titleBar.Title = filename + " - " + APPNAME;
titleBarText.Text = filename + " - " + APPNAME;
}
// Continue to update the window's title, after updating the custom title bar
AppWindow.Title = titleBar.Title;
appWindow.Title = titleBarText.Text;
}
}
}

View File

@@ -19,13 +19,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
"ShellPage.xaml",
};
// Hardcoded panel-to-page mapping (temporary until generic panel host mapping is needed)
// Key: panel file base name (without .xaml), Value: owning page base name
private static readonly Dictionary<string, string> PanelPageMapping = new(StringComparer.OrdinalIgnoreCase)
{
{ "MouseJumpPanel", "MouseUtilsPage" },
};
private static JsonSerializerOptions serializeOption = new()
{
WriteIndented = true,
@@ -40,117 +33,32 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
Environment.Exit(1);
}
string xamlRootDirectory = args[0];
string xamlDirectory = args[0];
string outputFile = args[1];
if (!Directory.Exists(xamlRootDirectory))
if (!Directory.Exists(xamlDirectory))
{
Debug.WriteLine($"Error: Directory '{xamlRootDirectory}' does not exist.");
Debug.WriteLine($"Error: Directory '{xamlDirectory}' does not exist.");
Environment.Exit(1);
}
try
{
var searchableElements = new List<SettingEntry>();
var processedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var xamlFiles = Directory.GetFiles(xamlDirectory, "*.xaml", SearchOption.AllDirectories);
void ScanDirectory(string root)
foreach (var xamlFile in xamlFiles)
{
if (!Directory.Exists(root))
var fileName = Path.GetFileName(xamlFile);
if (ExcludedXamlFiles.Contains(fileName))
{
return;
// Skip ShellPage.xaml as it contains many elements not relevant for search
continue;
}
Debug.WriteLine($"[XamlIndexBuilder] Scanning root: {root}");
var xamlFilesLocal = Directory.GetFiles(root, "*.xaml", SearchOption.AllDirectories);
foreach (var xamlFile in xamlFilesLocal)
{
var fullPath = Path.GetFullPath(xamlFile);
if (processedFiles.Contains(fullPath))
{
continue; // already handled (can happen if overlapping directories)
}
var fileName = Path.GetFileName(xamlFile);
if (ExcludedXamlFiles.Contains(fileName))
{
continue; // explicitly excluded
}
Debug.WriteLine($"Processing: {fileName}");
var elements = ExtractSearchableElements(xamlFile);
// Apply hardcoded panel mapping override
var baseName = Path.GetFileNameWithoutExtension(xamlFile);
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
{
for (int i = 0; i < elements.Count; i++)
{
var entry = elements[i];
entry.PageTypeName = hostPage;
elements[i] = entry;
}
}
searchableElements.AddRange(elements);
processedFiles.Add(fullPath);
}
}
// Scan well-known subdirectories under the provided root
var subDirs = new[] { "Views", "Panels" };
foreach (var sub in subDirs)
{
ScanDirectory(Path.Combine(xamlRootDirectory, sub));
}
// Fallback: also scan root directly (in case some XAML lives at root level)
ScanDirectory(xamlRootDirectory);
// -----------------------------------------------------------------------------
// Explicit include section: add specific XAML files that we always want indexed
// even if future logic excludes them or they live outside typical scan patterns.
// Add future files to the ExplicitExtraXamlFiles array below.
// -----------------------------------------------------------------------------
string[] explicitExtraXamlFiles = new[]
{
"MouseJumpPanel.xaml", // Mouse Jump settings panel
};
foreach (var extraFileName in explicitExtraXamlFiles)
{
try
{
var matches = Directory.GetFiles(xamlRootDirectory, extraFileName, SearchOption.AllDirectories);
foreach (var match in matches)
{
var full = Path.GetFullPath(match);
if (processedFiles.Contains(full))
{
continue; // already processed in general scan
}
Debug.WriteLine($"Processing (explicit include): {extraFileName}");
var elements = ExtractSearchableElements(full);
var baseName = Path.GetFileNameWithoutExtension(full);
if (PanelPageMapping.TryGetValue(baseName, out var hostPage))
{
for (int i = 0; i < elements.Count; i++)
{
var entry = elements[i];
entry.PageTypeName = hostPage;
elements[i] = entry;
}
}
searchableElements.AddRange(elements);
processedFiles.Add(full);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Explicit include failed for {extraFileName}: {ex.Message}");
}
Debug.WriteLine($"Processing: {fileName}");
var elements = ExtractSearchableElements(xamlFile);
searchableElements.AddRange(elements);
}
searchableElements = searchableElements.OrderBy(e => e.PageTypeName).ThenBy(e => e.ElementName).ToList();
@@ -189,15 +97,15 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
.Where(e => e.Name.LocalName == "SettingsPageControl")
.Where(e => e.Attribute(x + "Uid") != null);
// Extract SettingsCard elements (support both Name and x:Name)
// Extract SettingsCard elements
var settingsElements = doc.Descendants()
.Where(e => e.Name.LocalName == "SettingsCard")
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
// Extract SettingsExpander elements (support both Name and x:Name)
// Extract SettingsExpander elements
var settingsExpanderElements = doc.Descendants()
.Where(e => e.Name.LocalName == "SettingsExpander")
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Name") != null || e.Attribute(x + "Uid") != null);
.Where(e => e.Attribute("Name") != null || e.Attribute(x + "Uid") != null);
// Process SettingsPageControl elements
foreach (var element in settingsPageElements)
@@ -277,36 +185,16 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
public static string GetElementName(XElement element, XNamespace x)
{
// Get Name attribute (we call it ElementName in our indexing system)
var name = element.Attribute("Name")?.Value;
if (string.IsNullOrEmpty(name))
{
name = element.Attribute(x + "Name")?.Value;
}
return name;
}
public static string GetElementUid(XElement element, XNamespace x)
{
// Try x:Uid on the element itself
// Try x:Uid
var uid = element.Attribute(x + "Uid")?.Value;
if (!string.IsNullOrWhiteSpace(uid))
{
return uid;
}
// Fallback: check the first direct child element's x:Uid
var firstChild = element.Elements().FirstOrDefault();
if (firstChild != null)
{
var childUid = firstChild.Attribute(x + "Uid")?.Value;
if (!string.IsNullOrWhiteSpace(childUid))
{
return childUid;
}
}
return null;
return uid;
}
public static string GetParentElementName(XElement element, XNamespace x)
@@ -323,11 +211,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
if (expanderParent?.Name.LocalName == "SettingsExpander")
{
var expanderName = expanderParent.Attribute("Name")?.Value;
if (string.IsNullOrEmpty(expanderName))
{
expanderName = expanderParent.Attribute(x + "Name")?.Value;
}
if (!string.IsNullOrEmpty(expanderName))
{
return expanderName;
@@ -338,11 +221,6 @@ namespace Microsoft.PowerToys.Tools.XamlIndexBuilder
{
// Direct child of SettingsExpander
var expanderName = current.Attribute("Name")?.Value;
if (string.IsNullOrEmpty(expanderName))
{
expanderName = current.Attribute(x + "Name")?.Value;
}
if (!string.IsNullOrEmpty(expanderName))
{
return expanderName;

View File

@@ -29,15 +29,16 @@
<!-- Remove UI library reference to avoid pulling WindowsDesktop runtime (WindowsBase) -->
<PropertyGroup>
<!-- Fallback to dotnet if not provided by the environment -->
<DotNetExe Condition="'$(DotNetExe)' == ''">dotnet</DotNetExe>
<XamlRootDir Condition="'$(XamlRootDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML</XamlRootDir>
<XamlRootDir Condition="'$(XamlViewsDir)' != ''">$([System.IO.Path]::GetDirectoryName('$(XamlViewsDir)'))</XamlRootDir>
<XamlViewsDir Condition="'$(XamlViewsDir)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\SettingsXAML\Views</XamlViewsDir>
<GeneratedJsonFile Condition="'$(GeneratedJsonFile)' == ''">$(MSBuildProjectDirectory)\..\Settings.UI\Assets\Settings\search.index.json</GeneratedJsonFile>
</PropertyGroup>
<Target Name="GenerateSearchIndexSelf" AfterTargets="Build">
<RemoveDir Directories="$(MSBuildProjectDirectory)\obj\ARM64;$(MSBuildProjectDirectory)\obj\x64;$(MSBuildProjectDirectory)\bin" />
<MakeDir Directories="$([System.IO.Path]::GetDirectoryName('$(GeneratedJsonFile)'))" />
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Root='$(XamlRootDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
<Exec Command="&quot;$(DotNetExe)&quot; &quot;$(TargetPath)&quot; &quot;$(XamlRootDir)&quot; &quot;$(GeneratedJsonFile)&quot;" />
<Message Importance="high" Text="[XamlIndexBuilder] Generating search index. Views='$(XamlViewsDir)'; Out='$(GeneratedJsonFile)'; Tool='$(TargetPath)'; DotNet='$(DotNetExe)'." />
<!-- Execute via dotnet so host architecture doesn't need to match -->
<Exec Command="&quot;$(DotNetExe)&quot; &quot;$(TargetPath)&quot; &quot;$(XamlViewsDir)&quot; &quot;$(GeneratedJsonFile)&quot;" />
</Target>
</Project>

View File

@@ -1,244 +0,0 @@
// 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;
using System.Numerics;
using System.Threading.Tasks;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
namespace Microsoft.PowerToys.Settings.UI.Helpers;
public abstract partial class NavigablePage : Page
{
private const int ExpandWaitDuration = 500;
private const int AnimationDuration = 1850;
private NavigationParams _pendingNavigationParams;
public NavigablePage()
{
Loaded += OnPageLoaded;
}
protected override void OnNavigatedTo(Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Handle both old string parameter and new NavigationParams
if (e.Parameter is NavigationParams navParams)
{
_pendingNavigationParams = navParams;
}
else if (e.Parameter is string elementKey)
{
_pendingNavigationParams = new NavigationParams(elementKey);
}
}
private async void OnPageLoaded(object sender, RoutedEventArgs e)
{
if (_pendingNavigationParams != null && !string.IsNullOrEmpty(_pendingNavigationParams.ElementName))
{
// First, expand parent if specified
if (!string.IsNullOrEmpty(_pendingNavigationParams.ParentElementName))
{
var parentElement = FindElementByName(_pendingNavigationParams.ParentElementName);
if (parentElement is SettingsExpander expander)
{
expander.IsExpanded = true;
// Give time for the expander to animate
await Task.Delay(ExpandWaitDuration);
}
}
// Then find and navigate to the target element
var target = FindElementByName(_pendingNavigationParams.ElementName);
target?.StartBringIntoView(new BringIntoViewOptions
{
VerticalOffset = -20,
AnimationDesired = true,
});
await OnTargetElementNavigatedAsync(target, _pendingNavigationParams.ElementName);
_pendingNavigationParams = null;
}
}
protected virtual async Task OnTargetElementNavigatedAsync(FrameworkElement target, string elementKey)
{
if (target == null)
{
return;
}
// Attempt to set keyboard focus so that screen readers announce the element and keyboard users land directly on it.
TrySetFocus(target);
// Get the visual and compositor
var visual = ElementCompositionPreview.GetElementVisual(target);
var compositor = visual.Compositor;
// Create a subtle glow effect using drop shadow
var dropShadow = compositor.CreateDropShadow();
dropShadow.Color = (Color)Application.Current.Resources["SystemAccentColorLight2"];
dropShadow.BlurRadius = 16f;
dropShadow.Opacity = 0f;
dropShadow.Offset = new Vector3(0, 0, 0);
var spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size = new Vector2((float)target.ActualWidth + 8, (float)target.ActualHeight + 8);
spriteVisual.Shadow = dropShadow;
spriteVisual.Offset = new Vector3(-4, -4, 0);
// Insert the shadow visual behind the target element
ElementCompositionPreview.SetElementChildVisual(target, spriteVisual);
// Create a simple fade in/out animation
var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeAnimation.InsertKeyFrame(0f, 0f);
fadeAnimation.InsertKeyFrame(0.5f, 0.3f);
fadeAnimation.InsertKeyFrame(1f, 0f);
fadeAnimation.Duration = TimeSpan.FromMilliseconds(AnimationDuration);
dropShadow.StartAnimation("Opacity", fadeAnimation);
await Task.Delay(AnimationDuration);
// Clean up the shadow visual
ElementCompositionPreview.SetElementChildVisual(target, null);
}
private static void TrySetFocus(FrameworkElement target)
{
try
{
// Prefer Control.Focus when available.
if (target is Control ctrl)
{
// Ensure it can receive focus.
if (!ctrl.IsTabStop)
{
ctrl.IsTabStop = true;
}
ctrl.Focus(FocusState.Programmatic);
}
// Target is not a Control. Find first focusable descendant Control.
var focusCandidate = FindFirstFocusableDescendant(target);
if (focusCandidate != null)
{
if (!focusCandidate.IsTabStop)
{
focusCandidate.IsTabStop = true;
}
focusCandidate.Focus(FocusState.Programmatic);
return;
}
// Fallback: attempt to focus parent control if no descendant found.
if (target.Parent is Control parent)
{
if (!parent.IsTabStop)
{
parent.IsTabStop = true;
}
parent.Focus(FocusState.Programmatic);
}
}
catch
{
// Swallow focus exceptions; not critical. Could log if logging enabled.
// Leave the default focus as it is.
}
}
private static Control FindFirstFocusableDescendant(FrameworkElement root)
{
if (root == null)
{
return null;
}
var queue = new System.Collections.Generic.Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (current is Control c && c.IsEnabled && c.Visibility == Visibility.Visible)
{
return c;
}
int count = VisualTreeHelper.GetChildrenCount(current);
for (int i = 0; i < count; i++)
{
queue.Enqueue(VisualTreeHelper.GetChild(current, i));
}
}
return null;
}
protected FrameworkElement FindElementByName(string name)
{
var element = this.FindName(name) as FrameworkElement;
if (element != null)
{
return element;
}
if (this.Content is DependencyObject root)
{
var found = FindInDescendants(root, name);
if (found != null)
{
return found;
}
}
return null;
}
private static FrameworkElement FindInDescendants(DependencyObject root, string name)
{
if (root == null || string.IsNullOrEmpty(name))
{
return null;
}
var queue = new System.Collections.Generic.Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (current is FrameworkElement fe)
{
var local = fe.FindName(name) as FrameworkElement;
if (local != null)
{
return local;
}
}
int count = VisualTreeHelper.GetChildrenCount(current);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(current, i);
queue.Enqueue(child);
}
}
return null;
}
}

View File

@@ -0,0 +1,144 @@
// 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;
using System.Numerics;
using System.Threading.Tasks;
using CommunityToolkit.WinUI.Controls;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Media;
namespace Microsoft.PowerToys.Settings.UI.Helpers;
public abstract partial class NavigatablePage : Page
{
private const int ExpandWaitDuration = 500;
private const int AnimationDuration = 1000;
private NavigationParams _pendingNavigationParams;
public NavigatablePage()
{
Loaded += OnPageLoaded;
}
protected override void OnNavigatedTo(Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Handle both old string parameter and new NavigationParams
if (e.Parameter is NavigationParams navParams)
{
_pendingNavigationParams = navParams;
}
else if (e.Parameter is string elementKey)
{
_pendingNavigationParams = new NavigationParams(elementKey);
}
}
private async void OnPageLoaded(object sender, RoutedEventArgs e)
{
if (_pendingNavigationParams != null && !string.IsNullOrEmpty(_pendingNavigationParams.ElementName))
{
// First, expand parent if specified
if (!string.IsNullOrEmpty(_pendingNavigationParams.ParentElementName))
{
var parentElement = FindElementByName(_pendingNavigationParams.ParentElementName);
if (parentElement is SettingsExpander expander)
{
expander.IsExpanded = true;
// Give time for the expander to animate
await Task.Delay(ExpandWaitDuration);
}
}
// Then find and navigate to the target element
var target = FindElementByName(_pendingNavigationParams.ElementName);
target?.StartBringIntoView(new BringIntoViewOptions
{
VerticalOffset = -20,
AnimationDesired = true,
});
await OnTargetElementNavigatedAsync(target, _pendingNavigationParams.ElementName);
_pendingNavigationParams = null;
}
}
protected virtual async Task OnTargetElementNavigatedAsync(FrameworkElement target, string elementKey)
{
if (target == null)
{
return;
}
// Get the visual and compositor
var visual = ElementCompositionPreview.GetElementVisual(target);
var compositor = visual.Compositor;
// Create a subtle glow effect using drop shadow
var dropShadow = compositor.CreateDropShadow();
dropShadow.Color = Microsoft.UI.Colors.Gray;
dropShadow.BlurRadius = 8f;
dropShadow.Opacity = 0f;
dropShadow.Offset = new Vector3(0, 0, 0);
var spriteVisual = compositor.CreateSpriteVisual();
spriteVisual.Size = new Vector2((float)target.ActualWidth + 16, (float)target.ActualHeight + 16);
spriteVisual.Shadow = dropShadow;
spriteVisual.Offset = new Vector3(-8, -8, 0);
// Insert the shadow visual behind the target element
ElementCompositionPreview.SetElementChildVisual(target, spriteVisual);
// Create a simple fade in/out animation
var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
fadeAnimation.InsertKeyFrame(0f, 0f);
fadeAnimation.InsertKeyFrame(0.5f, 0.3f);
fadeAnimation.InsertKeyFrame(1f, 0f);
fadeAnimation.Duration = TimeSpan.FromMilliseconds(AnimationDuration);
dropShadow.StartAnimation("Opacity", fadeAnimation);
if (target is Control ctrl)
{
// TODO: ability to adjust brush color and animation from settings.
var originalBackground = ctrl.Background;
var highlightBrush = new SolidColorBrush();
var grayColor = Microsoft.UI.Colors.Gray;
grayColor.A = 50; // Very subtle transparency
highlightBrush.Color = grayColor;
// Apply the highlight
ctrl.Background = highlightBrush;
// Wait for animation to complete
await Task.Delay(AnimationDuration);
// Restore original background
ctrl.Background = originalBackground;
}
else
{
// For non-control elements, just wait for the glow animation
await Task.Delay(AnimationDuration);
}
// Clean up the shadow visual
ElementCompositionPreview.SetElementChildVisual(target, null);
}
protected FrameworkElement FindElementByName(string name)
{
var element = this.FindName(name) as FrameworkElement;
return element;
}
}

View File

@@ -25,7 +25,6 @@
<None Remove="SettingsXAML\Controls\Dashboard\CheckUpdateControl.xaml" />
<None Remove="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml" />
<None Remove="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml" />
<None Remove="SettingsXAML\Controls\TitleBar\TitleBar.xaml" />
</ItemGroup>
<ItemGroup>
<Page Remove="SettingsXAML\App.xaml" />
@@ -64,6 +63,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Markdown" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.TitleBar" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Text.RegularExpressions" />
@@ -156,8 +156,7 @@
</None>
<None Update="Assets\Settings\Scripts\DisableModule.ps1">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> <Page Update="SettingsXAML\Controls\TitleBar\TitleBar.xaml">
</Page>
</None>
<Page Update="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -181,8 +180,14 @@
</Page>
</ItemGroup>
<!-- Removed nested publish/exec and copy targets. -->
<!-- Build XamlIndexBuilder before compiling Settings to ensure the search index exists without taking a project reference. -->
<Target Name="BuildXamlIndexBeforeSettings" BeforeTargets="CoreCompile">
<Message Importance="high" Text="[Settings] Building XamlIndexBuilder prior to compile. Views='$(MSBuildProjectDirectory)\SettingsXAML\Views' Out='$(GeneratedJsonFile)'" />
<MSBuild Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj" Targets="Build" Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
<MSBuild
Projects="..\Settings.UI.XamlIndexBuilder\Settings.UI.XamlIndexBuilder.csproj"
Targets="Build"
Properties="Configuration=$(Configuration);Platform=Any CPU;TargetFramework=net9.0;XamlViewsDir=$(MSBuildProjectDirectory)\SettingsXAML\Views;GeneratedJsonFile=$(GeneratedJsonFile)" />
</Target>
</Project>

View File

@@ -170,7 +170,7 @@ namespace Microsoft.PowerToys.Settings.UI.Services
if (string.IsNullOrEmpty(header))
{
header = GetString(resourceLoader, $"{elementUid}/Content");
Debug.WriteLine($"[SearchIndexService] WARNING: No header localization found for ElementUid: '{elementUid}'");
}
return (header, description);

View File

@@ -11,7 +11,6 @@
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml" />
<ResourceDictionary Source="/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml" />
<ResourceDictionary Source="/SettingsXAML/Controls/TitleBar/TitleBar.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/TextBlock.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/Button.xaml" />
<ResourceDictionary Source="/SettingsXAML/Styles/InfoBadge.xaml" />

View File

@@ -548,14 +548,18 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
}
var moduleNames = conflictingModules.ToArray();
if (string.Equals(moduleNames[0], "System", StringComparison.OrdinalIgnoreCase))
if (conflictingModules.Count > 0)
{
c.ConflictMessage = ResourceLoaderInstance.ResourceLoader.GetString("SysHotkeyConflictTooltipText");
var moduleNames = conflictingModules.ToArray();
var conflictMessage = moduleNames.Length == 1
? $"Conflict detected with {moduleNames[0]}"
: $"Conflicts detected with: {string.Join(", ", moduleNames)}";
c.ConflictMessage = conflictMessage;
}
else
{
c.ConflictMessage = ResourceLoaderInstance.ResourceLoader.GetString("InAppHotkeyConflictTooltipText");
c.ConflictMessage = "Conflict detected with unknown module";
}
c.HasConflict = true;

View File

@@ -64,7 +64,7 @@
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsWarningAltGr, Mode=OneWay}"
Severity="Warning" />
<InfoBar
x:Uid="WarningShortcutConflict"
Title="Hotkey Conflict"
IsClosable="False"
IsOpen="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"
IsTabStop="{Binding ElementName=ShortcutContentControl, Path=HasConflict, Mode=OneWay}"

View File

@@ -1,229 +0,0 @@
// 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;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Controls;
public partial class TitleBar : Control
{
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Icon"/> property.
/// </summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(TitleBar), new PropertyMetadata(null, IconChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Title"/> property.
/// </summary>
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(TitleBar), new PropertyMetadata(default(string)));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Subtitle"/> property.
/// </summary>
public static readonly DependencyProperty SubtitleProperty = DependencyProperty.Register(nameof(Subtitle), typeof(string), typeof(TitleBar), new PropertyMetadata(default(string)));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Content"/> property.
/// </summary>
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(TitleBar), new PropertyMetadata(null, ContentChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Footer"/> property.
/// </summary>
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(TitleBar), new PropertyMetadata(null, FooterChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="IsBackButtonVisible"/> property.
/// </summary>
public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register(nameof(IsBackButtonVisible), typeof(bool), typeof(TitleBar), new PropertyMetadata(false, IsBackButtonVisibleChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="IsPaneButtonVisible"/> property.
/// </summary>
public static readonly DependencyProperty IsPaneButtonVisibleProperty = DependencyProperty.Register(nameof(IsPaneButtonVisible), typeof(bool), typeof(TitleBar), new PropertyMetadata(false, IsPaneButtonVisibleChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="DisplayMode"/> property.
/// </summary>
public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(nameof(DisplayMode), typeof(DisplayMode), typeof(TitleBar), new PropertyMetadata(DisplayMode.Standard, DisplayModeChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="CompactStateBreakpoint
/// "/> property.
/// </summary>
public static readonly DependencyProperty CompactStateBreakpointProperty = DependencyProperty.Register(nameof(CompactStateBreakpoint), typeof(int), typeof(TitleBar), new PropertyMetadata(850));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="AutoConfigureCustomTitleBar"/> property.
/// </summary>
public static readonly DependencyProperty AutoConfigureCustomTitleBarProperty = DependencyProperty.Register(nameof(AutoConfigureCustomTitleBar), typeof(bool), typeof(TitleBar), new PropertyMetadata(true, AutoConfigureCustomTitleBarChanged));
/// <summary>
/// The backing <see cref="DependencyProperty"/> for the <see cref="Window"/> property.
/// </summary>
public static readonly DependencyProperty WindowProperty = DependencyProperty.Register(nameof(Window), typeof(Window), typeof(TitleBar), new PropertyMetadata(null));
/// <summary>
/// The event that gets fired when the back button is clicked
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public event EventHandler<RoutedEventArgs>? BackButtonClick;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The event that gets fired when the pane toggle button is clicked
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public event EventHandler<RoutedEventArgs>? PaneButtonClick;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets or sets the Icon
/// </summary>
public IconElement Icon
{
get => (IconElement)GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
/// <summary>
/// Gets or sets the Title
/// </summary>
public string Title
{
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
/// <summary>
/// Gets or sets the Subtitle
/// </summary>
public string Subtitle
{
get => (string)GetValue(SubtitleProperty);
set => SetValue(SubtitleProperty, value);
}
/// <summary>
/// Gets or sets the content shown at the center of the TitleBar. When setting this, using DisplayMode=Tall is recommended.
/// </summary>
public object Content
{
get => (object)GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
/// <summary>
/// Gets or sets the content shown at the right of the TitleBar, next to the caption buttons. When setting this, using DisplayMode=Tall is recommended.
/// </summary>
public object Footer
{
get => (object)GetValue(FooterProperty);
set => SetValue(FooterProperty, value);
}
/// <summary>
/// Gets or sets DisplayMode. Compact is default (32px), Tall is recommended when setting the Content or Footer.
/// </summary>
public DisplayMode DisplayMode
{
get => (DisplayMode)GetValue(DisplayModeProperty);
set => SetValue(DisplayModeProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets the visibility of the back button.
/// </summary>
public bool IsBackButtonVisible
{
get => (bool)GetValue(IsBackButtonVisibleProperty);
set => SetValue(IsBackButtonVisibleProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets the visibility of the pane toggle button.
/// </summary>
public bool IsPaneButtonVisible
{
get => (bool)GetValue(IsPaneButtonVisibleProperty);
set => SetValue(IsPaneButtonVisibleProperty, value);
}
/// <summary>
/// Gets or sets the breakpoint of when the compact state is triggered.
/// </summary>
public int CompactStateBreakpoint
{
get => (int)GetValue(CompactStateBreakpointProperty);
set => SetValue(CompactStateBreakpointProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets if the TitleBar should auto configure ExtendContentIntoTitleBar and CaptionButton background colors.
/// </summary>
public bool AutoConfigureCustomTitleBar
{
get => (bool)GetValue(AutoConfigureCustomTitleBarProperty);
set => SetValue(AutoConfigureCustomTitleBarProperty, value);
}
/// <summary>
/// Gets or sets the window the TitleBar should configure.
/// </summary>
public Window Window
{
get => (Window)GetValue(WindowProperty);
set => SetValue(WindowProperty, value);
}
private static void IsBackButtonVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void IsPaneButtonVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void DisplayModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void FooterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void IconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TitleBar)d).Update();
}
private static void AutoConfigureCustomTitleBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (((TitleBar)d).AutoConfigureCustomTitleBar)
{
((TitleBar)d).Configure();
}
else
{
((TitleBar)d).Reset();
}
}
}
public enum DisplayMode
{
Standard,
Tall,
}

View File

@@ -1,195 +0,0 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.Foundation;
using Windows.Graphics;
namespace Microsoft.PowerToys.Settings.UI.Controls;
[TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))]
[TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))]
public partial class TitleBar : Control
{
#pragma warning disable SA1306 // Field names should begin with lower-case letter
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1400 // Access modifier should be declared
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ContentPresenter? PART_ContentPresenter;
ContentPresenter? PART_FooterPresenter;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore SA1400 // Access modifier should be declared
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
private void SetWASDKTitleBar()
{
if (this.Window == null)
{
return;
}
if (AutoConfigureCustomTitleBar)
{
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
this.Window.SizeChanged -= Window_SizeChanged;
this.Window.SizeChanged += Window_SizeChanged;
this.Window.Activated -= Window_Activated;
this.Window.Activated += Window_Activated;
if (Window.Content is FrameworkElement rootElement)
{
UpdateCaptionButtons(rootElement);
rootElement.ActualThemeChanged += (s, e) =>
{
UpdateCaptionButtons(rootElement);
};
}
PART_ContentPresenter = GetTemplateChild(nameof(PART_ContentPresenter)) as ContentPresenter;
PART_FooterPresenter = GetTemplateChild(nameof(PART_FooterPresenter)) as ContentPresenter;
// Get caption button occlusion information.
int captionButtonOcclusionWidthRight = Window.AppWindow.TitleBar.RightInset;
int captionButtonOcclusionWidthLeft = Window.AppWindow.TitleBar.LeftInset;
PART_LeftPaddingColumn!.Width = new GridLength(captionButtonOcclusionWidthLeft);
PART_RightPaddingColumn!.Width = new GridLength(captionButtonOcclusionWidthRight);
if (DisplayMode == DisplayMode.Tall)
{
// Choose a tall title bar to provide more room for interactive elements
// like search box or person picture controls.
Window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
}
else
{
Window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Standard;
}
// Recalculate the drag region for the custom title bar
// if you explicitly defined new draggable areas.
SetDragRegionForCustomTitleBar();
_isAutoConfigCompleted = true;
}
}
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
UpdateVisualStateAndDragRegion(args.Size);
}
private void UpdateCaptionButtons(FrameworkElement rootElement)
{
Window.AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
Window.AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
if (rootElement.ActualTheme == ElementTheme.Dark)
{
Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.White;
Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray;
}
else
{
Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.Black;
Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray;
}
}
private void ResetWASDKTitleBar()
{
if (this.Window == null)
{
return;
}
// Only reset if we were the ones who configured
if (_isAutoConfigCompleted)
{
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = false;
this.Window.SizeChanged -= Window_SizeChanged;
this.Window.Activated -= Window_Activated;
SizeChanged -= this.TitleBar_SizeChanged;
Window.AppWindow.TitleBar.ResetToDefault();
}
}
private void Window_Activated(object sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
VisualStateManager.GoToState(this, WindowDeactivatedState, true);
}
else
{
VisualStateManager.GoToState(this, WindowActivatedState, true);
}
}
public void SetDragRegionForCustomTitleBar()
{
if (AutoConfigureCustomTitleBar && Window is not null)
{
ClearDragRegions(NonClientRegionKind.Passthrough);
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
var items = new FrameworkElement?[] { PART_ContentPresenter, PART_FooterPresenter, PART_ButtonHolder };
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
var validItems = items.Where(x => x is not null).Select(x => x!).ToArray(); // Prune null items
SetDragRegion(NonClientRegionKind.Passthrough, validItems);
}
}
public double GetRasterizationScaleForElement(UIElement element)
{
if (element.XamlRoot != null)
{
return element.XamlRoot.RasterizationScale;
}
return 0.0;
}
public void SetDragRegion(NonClientRegionKind nonClientRegionKind, params FrameworkElement[] frameworkElements)
{
List<RectInt32> rects = new List<RectInt32>();
var scale = GetRasterizationScaleForElement(this);
foreach (var frameworkElement in frameworkElements)
{
if (frameworkElement == null)
{
continue;
}
GeneralTransform transformElement = frameworkElement.TransformToVisual(null);
Rect bounds = transformElement.TransformBounds(new Rect(0, 0, frameworkElement.ActualWidth, frameworkElement.ActualHeight));
var transparentRect = 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));
rects.Add(transparentRect);
}
if (rects.Count > 0)
{
InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id).SetRegionRects(nonClientRegionKind, rects.ToArray());
}
}
public void ClearDragRegions(NonClientRegionKind nonClientRegionKind)
{
InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id).ClearRegionRects(nonClientRegionKind);
}
}

View File

@@ -1,220 +0,0 @@
// 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.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Microsoft.PowerToys.Settings.UI.Controls;
[TemplateVisualState(Name = BackButtonVisibleState, GroupName = BackButtonStates)]
[TemplateVisualState(Name = BackButtonCollapsedState, GroupName = BackButtonStates)]
[TemplateVisualState(Name = PaneButtonVisibleState, GroupName = PaneButtonStates)]
[TemplateVisualState(Name = PaneButtonCollapsedState, GroupName = PaneButtonStates)]
[TemplateVisualState(Name = WindowActivatedState, GroupName = ActivationStates)]
[TemplateVisualState(Name = WindowDeactivatedState, GroupName = ActivationStates)]
[TemplateVisualState(Name = StandardState, GroupName = DisplayModeStates)]
[TemplateVisualState(Name = TallState, GroupName = DisplayModeStates)]
[TemplateVisualState(Name = IconVisibleState, GroupName = IconStates)]
[TemplateVisualState(Name = IconCollapsedState, GroupName = IconStates)]
[TemplateVisualState(Name = ContentVisibleState, GroupName = ContentStates)]
[TemplateVisualState(Name = ContentCollapsedState, GroupName = ContentStates)]
[TemplateVisualState(Name = FooterVisibleState, GroupName = FooterStates)]
[TemplateVisualState(Name = FooterCollapsedState, GroupName = FooterStates)]
[TemplateVisualState(Name = WideState, GroupName = ReflowStates)]
[TemplateVisualState(Name = NarrowState, GroupName = ReflowStates)]
[TemplatePart(Name = PartBackButton, Type = typeof(Button))]
[TemplatePart(Name = PartPaneButton, Type = typeof(Button))]
[TemplatePart(Name = nameof(PART_LeftPaddingColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_RightPaddingColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_ButtonHolder), Type = typeof(StackPanel))]
public partial class TitleBar : Control
{
private const string PartBackButton = "PART_BackButton";
private const string PartPaneButton = "PART_PaneButton";
private const string BackButtonVisibleState = "BackButtonVisible";
private const string BackButtonCollapsedState = "BackButtonCollapsed";
private const string BackButtonStates = "BackButtonStates";
private const string PaneButtonVisibleState = "PaneButtonVisible";
private const string PaneButtonCollapsedState = "PaneButtonCollapsed";
private const string PaneButtonStates = "PaneButtonStates";
private const string WindowActivatedState = "Activated";
private const string WindowDeactivatedState = "Deactivated";
private const string ActivationStates = "WindowActivationStates";
private const string IconVisibleState = "IconVisible";
private const string IconCollapsedState = "IconCollapsed";
private const string IconStates = "IconStates";
private const string StandardState = "Standard";
private const string TallState = "Tall";
private const string DisplayModeStates = "DisplayModeStates";
private const string ContentVisibleState = "ContentVisible";
private const string ContentCollapsedState = "ContentCollapsed";
private const string ContentStates = "ContentStates";
private const string FooterVisibleState = "FooterVisible";
private const string FooterCollapsedState = "FooterCollapsed";
private const string FooterStates = "FooterStates";
private const string WideState = "Wide";
private const string NarrowState = "Narrow";
private const string ReflowStates = "ReflowStates";
#pragma warning disable SA1306 // Field names should begin with lower-case letter
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1400 // Access modifier should be declared
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ColumnDefinition? PART_RightPaddingColumn;
ColumnDefinition? PART_LeftPaddingColumn;
StackPanel? PART_ButtonHolder;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore SA1400 // Access modifier should be declared
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
// We only want to reset TitleBar configuration in app, if we're the TitleBar instance that's managing that state.
private bool _isAutoConfigCompleted;
public TitleBar()
{
this.DefaultStyleKey = typeof(TitleBar);
}
protected override void OnApplyTemplate()
{
PART_LeftPaddingColumn = GetTemplateChild(nameof(PART_LeftPaddingColumn)) as ColumnDefinition;
PART_RightPaddingColumn = GetTemplateChild(nameof(PART_RightPaddingColumn)) as ColumnDefinition;
ConfigureButtonHolder();
Configure();
if (GetTemplateChild(PartBackButton) is Button backButton)
{
backButton.Click -= BackButton_Click;
backButton.Click += BackButton_Click;
}
if (GetTemplateChild(PartPaneButton) is Button paneButton)
{
paneButton.Click -= PaneButton_Click;
paneButton.Click += PaneButton_Click;
}
SizeChanged -= this.TitleBar_SizeChanged;
SizeChanged += this.TitleBar_SizeChanged;
Update();
base.OnApplyTemplate();
}
private void TitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateVisualStateAndDragRegion(e.NewSize);
}
private void UpdateVisualStateAndDragRegion(Size size)
{
if (size.Width <= CompactStateBreakpoint)
{
if (Content != null || Footer != null)
{
VisualStateManager.GoToState(this, NarrowState, true);
}
}
else
{
VisualStateManager.GoToState(this, WideState, true);
}
SetDragRegionForCustomTitleBar();
}
private void BackButton_Click(object sender, RoutedEventArgs e)
{
BackButtonClick?.Invoke(this, new RoutedEventArgs());
}
private void PaneButton_Click(object sender, RoutedEventArgs e)
{
PaneButtonClick?.Invoke(this, new RoutedEventArgs());
}
private void ConfigureButtonHolder()
{
if (PART_ButtonHolder != null)
{
PART_ButtonHolder.SizeChanged -= PART_ButtonHolder_SizeChanged;
}
PART_ButtonHolder = GetTemplateChild(nameof(PART_ButtonHolder)) as StackPanel;
if (PART_ButtonHolder != null)
{
PART_ButtonHolder.SizeChanged += PART_ButtonHolder_SizeChanged;
}
}
private void PART_ButtonHolder_SizeChanged(object sender, SizeChangedEventArgs e)
{
SetDragRegionForCustomTitleBar();
}
private void Configure()
{
SetWASDKTitleBar();
}
public void Reset()
{
ResetWASDKTitleBar();
}
private void Update()
{
if (Icon != null)
{
VisualStateManager.GoToState(this, IconVisibleState, true);
}
else
{
VisualStateManager.GoToState(this, IconCollapsedState, true);
}
VisualStateManager.GoToState(this, IsBackButtonVisible ? BackButtonVisibleState : BackButtonCollapsedState, true);
VisualStateManager.GoToState(this, IsPaneButtonVisible ? PaneButtonVisibleState : PaneButtonCollapsedState, true);
if (DisplayMode == DisplayMode.Tall)
{
VisualStateManager.GoToState(this, TallState, true);
}
else
{
VisualStateManager.GoToState(this, StandardState, true);
}
if (Content != null)
{
VisualStateManager.GoToState(this, ContentVisibleState, true);
}
else
{
VisualStateManager.GoToState(this, ContentCollapsedState, true);
}
if (Footer != null)
{
VisualStateManager.GoToState(this, FooterVisibleState, true);
}
else
{
VisualStateManager.GoToState(this, FooterCollapsedState, true);
}
SetDragRegionForCustomTitleBar();
}
}

View File

@@ -1,371 +0,0 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls">
<x:Double x:Key="TitleBarCompactHeight">32</x:Double>
<x:Double x:Key="TitleBarTallHeight">48</x:Double>
<x:Double x:Key="TitleBarContentMinWidth">360</x:Double>
<Style BasedOn="{StaticResource DefaultTitleBarStyle}" TargetType="local:TitleBar" />
<Style x:Key="DefaultTitleBarStyle" TargetType="local:TitleBar">
<Setter Property="MinHeight" Value="{ThemeResource TitleBarCompactHeight}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TitleBar">
<Grid
x:Name="PART_RootGrid"
Height="{TemplateBinding MinHeight}"
Padding="4,0,0,0"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="PART_LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="PART_ButtonsHolderColumn" Width="Auto" />
<ColumnDefinition x:Name="PART_IconColumn" Width="Auto" />
<ColumnDefinition x:Name="PART_TitleColumn" Width="Auto" />
<ColumnDefinition
x:Name="PART_LeftDragColumn"
Width="*"
MinWidth="4" />
<ColumnDefinition x:Name="PART_ContentColumn" Width="Auto" />
<ColumnDefinition
x:Name="PART_RightDragColumn"
Width="*"
MinWidth="4" />
<ColumnDefinition x:Name="PART_FooterColumn" Width="Auto" />
<ColumnDefinition x:Name="PART_RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Border
x:Name="PART_IconHolder"
Grid.Column="2"
Margin="12,0,0,0"
VerticalAlignment="Center">
<Viewbox
x:Name="PART_Icon"
MaxWidth="16"
MaxHeight="16">
<ContentPresenter
x:Name="PART_IconPresenter"
AutomationProperties.AccessibilityView="Raw"
Content="{TemplateBinding Icon}"
HighContrastAdjustment="None" />
</Viewbox>
</Border>
<StackPanel
x:Name="PART_TitleHolder"
Grid.Column="3"
Margin="16,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="4">
<TextBlock
x:Name="PART_TitleText"
MinWidth="48"
Margin="0,0,0,1"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{TemplateBinding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock
x:Name="PART_SubtitleText"
MinWidth="48"
Margin="0,0,0,1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{TemplateBinding Subtitle}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
</StackPanel>
<Grid
x:Name="PART_DragRegion"
Grid.Column="2"
Grid.ColumnSpan="6"
Background="Transparent" />
<StackPanel
x:Name="PART_ButtonHolder"
Grid.Column="1"
Orientation="Horizontal">
<Button
x:Name="PART_BackButton"
Style="{ThemeResource TitleBarBackButtonStyle}"
ToolTipService.ToolTip="Back" />
<Button
x:Name="PART_PaneButton"
Style="{StaticResource TitleBarPaneToggleButtonStyle}"
ToolTipService.ToolTip="Toggle menu" />
</StackPanel>
<ContentPresenter
x:Name="PART_ContentPresenter"
Grid.Column="5"
MinWidth="{ThemeResource TitleBarContentMinWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
HorizontalContentAlignment="Stretch"
Content="{TemplateBinding Content}" />
<ContentPresenter
x:Name="PART_FooterPresenter"
Grid.Column="7"
Margin="4,0,8,0"
HorizontalContentAlignment="Right"
Content="{TemplateBinding Footer}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="BackButtonStates">
<VisualState x:Name="BackButtonVisible" />
<VisualState x:Name="BackButtonCollapsed">
<VisualState.Setters>
<Setter Target="PART_BackButton.Visibility" Value="Collapsed" />
<Setter Target="PART_BackButton.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="PaneButtonStates">
<VisualState x:Name="PaneButtonVisible" />
<VisualState x:Name="PaneButtonCollapsed">
<VisualState.Setters>
<Setter Target="PART_PaneButton.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="IconStates">
<VisualState x:Name="IconVisible" />
<VisualState x:Name="IconCollapsed">
<VisualState.Setters>
<Setter Target="PART_IconHolder.Visibility" Value="Collapsed" />
<Setter Target="PART_TitleHolder.Margin" Value="4,0,0,0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ContentStates">
<VisualState x:Name="ContentVisible" />
<VisualState x:Name="ContentCollapsed">
<VisualState.Setters>
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FooterStates">
<VisualState x:Name="FooterVisible" />
<VisualState x:Name="FooterCollapsed">
<VisualState.Setters>
<Setter Target="PART_FooterPresenter.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ReflowStates">
<VisualState x:Name="Wide" />
<VisualState x:Name="Narrow">
<VisualState.Setters>
<Setter Target="PART_TitleHolder.Visibility" Value="Collapsed" />
<Setter Target="PART_LeftDragColumn.Width" Value="Auto" />
<Setter Target="PART_RightDragColumn.Width" Value="Auto" />
<Setter Target="PART_RightDragColumn.MinWidth" Value="16" />
<Setter Target="PART_LeftDragColumn.MinWidth" Value="16" />
<Setter Target="PART_ContentColumn.Width" Value="*" />
<!-- Content can stretch now -->
<Setter Target="PART_ContentPresenter.MinWidth" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="WindowActivationStates">
<VisualState x:Name="Activated" />
<VisualState x:Name="Deactivated">
<VisualState.Setters>
<Setter Target="PART_TitleText.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="PART_SubtitleText.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="PART_BackButton.IsEnabled" Value="False" />
<Setter Target="PART_PaneButton.IsEnabled" Value="False" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisplayModeStates">
<VisualState x:Name="Standard" />
<VisualState x:Name="Tall">
<VisualState.Setters>
<Setter Target="PART_RootGrid.MinHeight" Value="{ThemeResource TitleBarTallHeight}" />
<Setter Target="PART_RootGrid.Padding" Value="4" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Copy of WinUI NavigationBackButtonNormalStyle - cannot use it as it picks up the generic.xaml version, not the WinUI version -->
<Style x:Key="TitleBarBackButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource NavigationViewBackButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource NavigationViewItemForegroundChecked}" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="Content" Value="&#xE72B;" />
<Setter Property="Padding" Value="12,4,12,4" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid
x:Name="RootGrid"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}">
<AnimatedIcon
x:Name="Content"
Width="16"
Height="16"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw">
<animatedvisuals:AnimatedBackVisualSource />
<AnimatedIcon.FallbackIconSource>
<FontIconSource
FontFamily="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FontFamily}"
FontSize="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FontSize}"
Glyph="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
MirroredWhenRightToLeft="True" />
</AnimatedIcon.FallbackIconSource>
</AnimatedIcon>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="Content.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="Content.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Copy of WinUI PaneToggleButtonStyle - cannot use it as it picks up the generic.xaml version, not the WinUI version -->
<Style x:Key="TitleBarPaneToggleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource NavigationViewBackButtonBackground}" />
<Setter Property="Foreground" Value="{ThemeResource NavigationViewItemForegroundChecked}" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="FontSize" Value="16" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="Content" Value="&#xE700;" />
<Setter Property="Padding" Value="12,4,12,4" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid
x:Name="RootGrid"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}">
<AnimatedIcon
x:Name="Content"
Width="16"
Height="16"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw">
<animatedvisuals:AnimatedGlobalNavigationButtonVisualSource />
<AnimatedIcon.FallbackIconSource>
<FontIconSource
FontFamily="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FontFamily}"
FontSize="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FontSize}"
Glyph="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"
MirroredWhenRightToLeft="True" />
</AnimatedIcon.FallbackIconSource>
</AnimatedIcon>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="Content.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="Content.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource NavigationViewButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -24,7 +24,6 @@
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump">
<tkcontrols:SettingsCard
x:Name="MouseUtilsEnableMouseJump"
x:Uid="MouseUtils_Enable_MouseJump"
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}"
IsEnabled="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
@@ -43,7 +42,6 @@
</InfoBar>
<tkcontrols:SettingsCard
x:Name="MouseUtilsMouseJumpActivationShortcut"
x:Uid="MouseUtils_MouseJump_ActivationShortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}">
@@ -128,7 +126,6 @@
</tkcontrols:SettingsCard>
<tkcontrols:SettingsExpander
x:Name="MouseUtilsMouseJumpAppearance"
x:Uid="MouseUtils_MouseJump_Appearance"
HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}"

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.AdvancedPastePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -12,7 +12,7 @@
x:Name="RootPage"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<local:NavigablePage.Resources>
<local:NavigatablePage.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
@@ -38,7 +38,7 @@
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</local:NavigablePage.Resources>
</local:NavigatablePage.Resources>
<Grid>
<controls:SettingsPageControl x:Uid="AdvancedPaste" ModuleImageSource="ms-appx:///Assets/Settings/Modules/AdvancedPaste.png">
<controls:SettingsPageControl.ModuleContent>
@@ -425,4 +425,4 @@
</StackPanel>
</ContentDialog>
</Grid>
</local:NavigablePage>
</local:NavigatablePage>

View File

@@ -15,7 +15,7 @@ using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class AdvancedPastePage : NavigablePage, IRefreshablePage
public sealed partial class AdvancedPastePage : NavigatablePage, IRefreshablePage
{
private AdvancedPasteViewModel ViewModel { get; set; }

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.AlwaysOnTopPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -136,4 +136,4 @@
<controls:PageLink x:Uid="LearnMore_AlwaysOnTop" Link="https://aka.ms/PowerToysOverview_AlwaysOnTop" />
</controls:SettingsPageControl.PrimaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>
</local:NavigatablePage>

View File

@@ -9,7 +9,7 @@ using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class AlwaysOnTopPage : NavigablePage, IRefreshablePage
public sealed partial class AlwaysOnTopPage : NavigatablePage, IRefreshablePage
{
private AlwaysOnTopViewModel ViewModel { get; set; }

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.AwakePage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -14,9 +14,9 @@
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<local:NavigablePage.Resources>
<local:NavigatablePage.Resources>
<converters:AwakeModeToIntConverter x:Key="AwakeModeToIntConverter" />
</local:NavigablePage.Resources>
</local:NavigatablePage.Resources>
<controls:SettingsPageControl
x:Uid="Awake"
@@ -119,4 +119,4 @@
<controls:PageLink x:Uid="SecondaryLink_Awake" Link="https://awake.den.dev" />
</controls:SettingsPageControl.SecondaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>
</local:NavigatablePage>

View File

@@ -17,7 +17,7 @@ using PowerToys.GPOWrapper;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class AwakePage : NavigablePage, IRefreshablePage
public sealed partial class AwakePage : NavigatablePage, IRefreshablePage
{
private readonly string _appName = "Awake";
private readonly SettingsUtils _settingsUtils;

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.CmdNotFoundPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -170,4 +170,4 @@
<controls:PageLink x:Uid="LearnMore_CmdNotFound" Link="https://aka.ms/PowerToysOverview_CmdNotFound" />
</controls:SettingsPageControl.PrimaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>
</local:NavigatablePage>

View File

@@ -8,7 +8,7 @@ using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class CmdNotFoundPage : NavigablePage
public sealed partial class CmdNotFoundPage : NavigatablePage
{
private CmdNotFoundViewModel ViewModel { get; set; }

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.CmdPalPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -52,4 +52,4 @@
<controls:PageLink x:Uid="LearnMore_CmdPal" Link="https://aka.ms/PowerToysOverview_CmdPal" />
</controls:SettingsPageControl.PrimaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>
</local:NavigatablePage>

View File

@@ -13,7 +13,7 @@ using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class CmdPalPage : NavigablePage, IRefreshablePage
public sealed partial class CmdPalPage : NavigatablePage, IRefreshablePage
{
private CmdPalViewModel ViewModel { get; set; }

View File

@@ -1,4 +1,4 @@
<local:NavigablePage
<local:NavigatablePage
x:Class="Microsoft.PowerToys.Settings.UI.Views.ColorPickerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -232,4 +232,4 @@
<controls:PageLink Link="https://medium.com/@Niels9001/a-fluent-color-meter-for-powertoys-20407ededf0c" Text="Niels Laute's UX concept" />
</controls:SettingsPageControl.SecondaryLinks>
</controls:SettingsPageControl>
</local:NavigablePage>
</local:NavigatablePage>

Some files were not shown because too many files have changed in this diff Show More