diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 120000 index 0000000000..ff80726687 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +../.github/copilot-instructions.md \ No newline at end of file diff --git a/.claude/agents b/.claude/agents new file mode 120000 index 0000000000..fa084a095e --- /dev/null +++ b/.claude/agents @@ -0,0 +1 @@ +../.github/agents \ No newline at end of file diff --git a/.claude/commands b/.claude/commands new file mode 120000 index 0000000000..95a795b09e --- /dev/null +++ b/.claude/commands @@ -0,0 +1 @@ +../.github/prompts \ No newline at end of file diff --git a/.claude/rules b/.claude/rules new file mode 120000 index 0000000000..89b1ff5da7 --- /dev/null +++ b/.claude/rules @@ -0,0 +1 @@ +../.github/instructions \ No newline at end of file diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000000..3e73f3a383 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.github/skills \ No newline at end of file diff --git a/.config/configuration.vsEnterprise.winget b/.config/configuration.vsEnterprise.winget index 08c9983562..da062e121a 100644 --- a/.config/configuration.vsEnterprise.winget +++ b/.config/configuration.vsEnterprise.winget @@ -1,5 +1,5 @@ # yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 -# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys +# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started properties: resources: - resource: Microsoft.Windows.Settings/WindowsSettings @@ -13,11 +13,11 @@ properties: - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: - description: Install Visual Studio 2022 Enterprise (Any edition will work) + description: Install Visual Studio 2026 Enterprise (Any edition will work) # Requires elevation for the set operation securityContext: elevated settings: - id: Microsoft.VisualStudio.2022.Enterprise + id: Microsoft.VisualStudio.Enterprise source: winget - resource: Microsoft.VisualStudio.DSC/VSComponents dependsOn: @@ -29,7 +29,7 @@ properties: securityContext: elevated settings: productId: Microsoft.VisualStudio.Product.Enterprise - channelId: VisualStudio.17.Release + channelId: VisualStudio.18.Release vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' configurationVersion: 0.2.0 diff --git a/.config/configuration.vsProfessional.winget b/.config/configuration.vsProfessional.winget index 4d7e4a31c3..93eabc621d 100644 --- a/.config/configuration.vsProfessional.winget +++ b/.config/configuration.vsProfessional.winget @@ -1,5 +1,5 @@ # yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 -# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys +# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started properties: resources: - resource: Microsoft.Windows.Settings/WindowsSettings @@ -13,11 +13,11 @@ properties: - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: - description: Install Visual Studio 2022 Professional (Any edition will work) + description: Install Visual Studio 2026 Professional (Any edition will work) # Requires elevation for the set operation securityContext: elevated settings: - id: Microsoft.VisualStudio.2022.Professional + id: Microsoft.VisualStudio.Professional source: winget - resource: Microsoft.VisualStudio.DSC/VSComponents dependsOn: @@ -29,7 +29,7 @@ properties: securityContext: elevated settings: productId: Microsoft.VisualStudio.Product.Professional - channelId: VisualStudio.17.Release + channelId: VisualStudio.18.Release vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' configurationVersion: 0.2.0 diff --git a/.config/configuration.winget b/.config/configuration.winget index 2016dcdc33..03aaf55e36 100644 --- a/.config/configuration.winget +++ b/.config/configuration.winget @@ -1,5 +1,5 @@ # yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 -# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys +# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started properties: resources: - resource: Microsoft.Windows.Settings/WindowsSettings @@ -13,11 +13,11 @@ properties: - resource: Microsoft.WinGet.DSC/WinGetPackage id: vsPackage directives: - description: Install Visual Studio 2022 Community (Any edition will work) + description: Install Visual Studio 2026 Community (Any edition will work) # Requires elevation for the set operation securityContext: elevated settings: - id: Microsoft.VisualStudio.2022.Community + id: Microsoft.VisualStudio.Community source: winget - resource: Microsoft.VisualStudio.DSC/VSComponents dependsOn: @@ -29,7 +29,7 @@ properties: securityContext: elevated settings: productId: Microsoft.VisualStudio.Product.Community - channelId: VisualStudio.17.Release + channelId: VisualStudio.18.Release vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig' configurationVersion: 0.2.0 diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 3e7341d5c3..fee0314208 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -38,6 +38,7 @@ Gbps gcode Heatshrink Mbits +Kbits MBs mkv msix @@ -97,6 +98,7 @@ Yubico Perplexity Groq svgl +devhome # KEYS @@ -322,6 +324,7 @@ REGSTR # Misc Win32 APIs and PInvokes INVOKEIDLIST +MEMORYSTATUSEX # PowerRename metadata pattern abbreviations (used in tests and regex patterns) DDDD @@ -342,3 +345,7 @@ reportbug #ffmpeg crf nostdin + +# Performance counter keys +engtype +Nonpaged diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt index ab2446f8ae..4df6c5c3e1 100644 --- a/.github/actions/spell-check/allow/names.txt +++ b/.github/actions/spell-check/allow/names.txt @@ -192,6 +192,7 @@ ycv yeelam Yuniardi yuyoyuppe +zadjii Zeol Zhao Zhaopeng @@ -228,6 +229,7 @@ regedit roslyn Skia Spotify +taskmgr tldr Vanara wangyi @@ -243,4 +245,3 @@ xamlstyler Xavalon Xbox Youdao -zadjii diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index 9e587fa284..b029f1dbcb 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -111,6 +111,7 @@ ^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$ ^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$ ^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/.*\.TestData\.cs$ +^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/Text/.*\.cs$ ^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$ ^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/ ^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index a43e81d077..4a208a400a 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -197,6 +197,7 @@ Canvascustomlayout CAPTUREBLT CAPTURECHANGED CARETBLINKING +carlos Carlseibert CAtl caub @@ -217,6 +218,7 @@ certmgr cfp CHANGECBCHAIN changecursor +chatasweetie checkmarks CHILDACTIVATE CHILDWINDOW @@ -1495,6 +1497,7 @@ psz ptb ptc PTCHAR +ptcontrols ptd PTOKEN PToy @@ -1527,6 +1530,7 @@ randi RAquadrant rasterization Rasterize +rasterizing RAWINPUTDEVICE RAWINPUTHEADER RAWMODE @@ -2241,6 +2245,7 @@ YSpeed YStr YTimer YVIRTUALSCREEN +zamora ZEROINIT zonability zonable diff --git a/Cpp.Build.props b/Cpp.Build.props index cca7eb0f91..ac2ad61cdb 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -75,7 +75,6 @@ true stdcpplatest false - /await %(AdditionalOptions) _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions) diff --git a/Directory.Build.props b/Directory.Build.props index 99379ecefc..e9127613df 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,8 @@ - + + $(MSBuildThisFileDirectory) + + Copyright (C) Microsoft Corporation. All rights reserved. Copyright (C) Microsoft Corporation. All rights reserved. @@ -61,7 +64,7 @@ <_PropertySheetDisplayName>PowerToys.Root.Props - $(MsbuildThisFileDirectory)\Cpp.Build.props + $(RepoRoot)Cpp.Build.props @@ -70,8 +73,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all diff --git a/Directory.Packages.props b/Directory.Packages.props index 750a57fdfc..95d23e622b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -44,7 +44,7 @@ - + diff --git a/PowerToys.slnx b/PowerToys.slnx index 53eb43de15..d4cf89c1ca 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -4,10 +4,6 @@ - - - - @@ -17,6 +13,10 @@ + + + + @@ -208,6 +208,10 @@ + + + + diff --git a/README.md b/README.md index 1931682d23..6626eaa6a8 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,38 @@ There are For the first-time setup, please run the installer as an administrator. This ensures that the Wix tool can move wix.target to the desired location and trust the certificate used to sign the MSIX packages. @@ -109,7 +109,7 @@ dotnet tool install --global wix --version 5.0.2 ##### From the command line -1. From the start menu, open a `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` +1. From the start menu, open a `Developer Command Prompt for VS` 1. Ensure `nuget.exe` is in your `%path%` 1. In the repo root, run these commands: @@ -140,7 +140,7 @@ If you prefer, you can alternatively build prerequisite projects for the install The resulting installer will be available in the `installer\PowerToysSetupVNext\x64\Release\` folder. -To build the installer from the command line, run `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`. +To build the installer from the command line, run `Developer Command Prompt for VS` in admin mode and execute the following commands. The generated installer package will be located at `\installer\PowerToysSetupVNext\{platform}\Release\MachineSetup`. ``` git clean -xfd -e *exe -- .\installer\ diff --git a/doc/devdocs/development/debugging.md b/doc/devdocs/development/debugging.md index 8b6d4de27e..c3e254e0a2 100644 --- a/doc/devdocs/development/debugging.md +++ b/doc/devdocs/development/debugging.md @@ -15,7 +15,7 @@ Before you can start debugging PowerToys, you need to set up your development en You can build the entire solution from the command line, which is sometimes faster than building within Visual Studio: -1. Open `Developer Command Prompt for VS 2022` or `Developer Command Prompt for VS` +1. Open `Developer Command Prompt for VS` 2. Navigate to the repository root directory 3. Run the following command(don't forget to set the correct platform): ```pwsh @@ -105,7 +105,7 @@ If you encounter build errors about missing image files (e.g., `.png`, `.ico`, o 1. **Clean the solution in Visual Studio**: Build > Clean Solution - Or from the command line (Developer Command Prompt for VS 2022 or Developer Command Prompt for VS): + Or from the command line (`Developer Command Prompt for VS`): ```pwsh msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug ``` diff --git a/doc/devdocs/development/dev-with-vscode.md b/doc/devdocs/development/dev-with-vscode.md index 8228e3e339..877e4c04cb 100644 --- a/doc/devdocs/development/dev-with-vscode.md +++ b/doc/devdocs/development/dev-with-vscode.md @@ -15,9 +15,27 @@ VS Code extensions Needed: --- ## Building in VS Code -### Configure Developer Powershell for VS 2022 or Developer Powershell for VS for more convenient dev in vscode. -1. Configure profile in in settings, entry: "terminal.integrated.profiles.windows" -2. Add below config as entry (choose VS 2022 or VS 2026 based on your installation): +### Configure Developer PowerShell for VS for more convenient development experience in VS Code +1. Configure profile in settings, entry: `terminal.integrated.profiles.windows` +2. Add below config as entry (choose VS 2026 or VS 2022 based on your installation): + +**For Visual Studio 2026 (recommended):** +```json + "Developer PowerShell for VS": { + // Configure based on your preference + "path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe", + "args": [ + "-NoExit", + "-Command", + "& {", + "$orig = Get-Location;", + // Adjust path based on your edition (Community/Professional/Enterprise) + "& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';", + "Set-Location $orig", + "}" + ] + }, +``` **For Visual Studio 2022:** ```json @@ -37,24 +55,6 @@ VS Code extensions Needed: }, ``` -**For Visual Studio 2026:** -```json - "Developer PowerShell for VS": { - // Configure based on your preference - "path": "C:\\Program Files\\WindowsApps\\Microsoft.PowerShell_7.5.2.0_arm64__8wekyb3d8bbwe\\pwsh.exe", - "args": [ - "-NoExit", - "-Command", - "& {", - "$orig = Get-Location;", - // Adjust path based on your edition (Community/Professional/Enterprise) - "& 'C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\Common7\\Tools\\Launch-VsDevShell.ps1';", - "Set-Location $orig", - "}" - ] - }, -``` - 3. [Optional] Set your Developer PowerShell profile as the default, so that you can get a deep integration with vscode coding agent. 4. Now you can build with plain `msbuild` or configure tasks.json in below section. diff --git a/doc/devdocs/development/new-powertoy.md b/doc/devdocs/development/new-powertoy.md index 1e0f7bccfa..33a2cd63f0 100644 --- a/doc/devdocs/development/new-powertoy.md +++ b/doc/devdocs/development/new-powertoy.md @@ -12,22 +12,11 @@ A PowerToy module is a self-contained utility integrated into the PowerToys ecos ### Requirements -- [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) and the following workloads/individual components: - - Desktop Development with C++ - - WinUI application development - - .NET desktop development - - Windows 10 SDK (10.0.22621.0) - - Windows 11 SDK (10.0.26100.3916) -- .NET 8 SDK -- Fork the [PowerToys repository](https://github.com/microsoft/PowerToys/tree/main) locally -- [Validate that you are able to build and run](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/development/debugging.md) `PowerToys.slnx`. +Follow the [Getting Started](../readme.md#getting-started) guide to set up your development environment, then [validate that you are able to build and run](debugging.md) `PowerToys.slnx`. Optional: - [WiX v5 toolset](https://github.com/microsoft/PowerToys/tree/main) for the installer -> [!NOTE] -> To ensure all the correct VS Workloads are installed, use [the WinGet configuration files](https://github.com/microsoft/PowerToys/tree/e13d6a78aafbcf32a4bb5f8581d041e1d057c3f1/.config) in the project repository. (Use the one that matches your VS distribution. ie: VS Community would use `configuration.winget`) - ### Folder structure ``` diff --git a/doc/devdocs/events.md b/doc/devdocs/events.md new file mode 100644 index 0000000000..1bc2d5bba6 --- /dev/null +++ b/doc/devdocs/events.md @@ -0,0 +1,197 @@ +# Telemetry Events + +PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release. + +**⚠️ Important**: Telemetry must never include personal information, file paths, or user‑generated content. + +## Developer Effort Overview (What to Expect) + +Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation. + +At a high level, developers should expect to: + +1. Within one PR: + 1. Add a new telemetry event(s) to module + 1. Add the new event(s) DATA_AND_PRIVACY.md +1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s) + +### Privacy Guidelines + +**NEVER** log: + +- User data (text, files, emails, etc.) +- File paths or filenames +- Personal information +- Sensitive system information +- Anything that could identify a specific user + +DO log: + +- Feature usage (which features, how often) +- Success/failure status +- Timing/performance metrics +- Error types (not error messages with user data) +- Aggregate counts + +### Event Naming Convention + +Follow this pattern: `UtilityName_EventDescription` + +Examples: + +- `ColorPicker_Session` +- `FancyZones_LayoutApplied` +- `PowerRename_Rename` +- `AdvancedPaste_FormatClicked` +- `CmdPal_ExtensionInvoked` + +## Adding Telemetry Events to PowerToys + +PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is: + +- Opt-in by default (disabled since v0.86) +- Privacy-focused - never logs personal info, file paths, or user-generated content +- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics + +### C++ Telemetry Implementation + +**Core Components** + +| File | Purpose | +| ------------- |:-------------:| +| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider | +| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check | +| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros + + +#### Pattern for C++ Modules + +1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h): + + ```c + // trace.h + #pragma once + #include + + class Trace : public telemetry::TraceBase + { + public: + static void MyEvent(/* parameters */); + }; + ``` + +2. Implement events using `TraceLoggingWriteWrapper`: + + ```cpp + // trace.cpp + #include "trace.h" + #include + + TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + + void Trace::MyEvent(bool enabled) + { + TraceLoggingWriteWrapper( + g_hProvider, + "ModuleName_EventName", // Event name + TraceLoggingBoolean(enabled, "Enabled"), // Event data + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); + } + ``` + +**Key C++ Telemetry Macros** + +| Macro | Purpose | +| ------------- |:-------------:| +| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check | +| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification | + +### C# Telemetry Implementation + +**Core Components** + +| File | Purpose | +| ------------- |:-------------:| +| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent()` method | +| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) | +| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property | +| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants | +| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check + +#### Pattern for C# Modules + +1. Create an event class inheriting from `EventBase` and implementing `IEvent`: + + ```csharp + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Tracing; + using Microsoft.PowerToys.Telemetry; + using Microsoft.PowerToys.Telemetry.Events; + + namespace MyModule.Telemetry + { + [EventData] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] + public class MyModuleEvent : EventBase, IEvent + { + // Event properties (logged as telemetry data) + public string SomeProperty { get; set; } + public int SomeValue { get; set; } + + // Required: Privacy tag + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + + // Optional: Set EventName in constructor (defaults to class name) + public MyModuleEvent(string prop, int val) + { + EventName = "MyModule_EventName"; + SomeProperty = prop; + SomeValue = val; + } + } + } + ``` + +2. Log the event: + +```csharp +PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42)); +``` + +**Privacy Tags (C#)** + +| Tag | Use Case | +| ------------- |:-------------:| +| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events +| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events + +### Update DATA_AND_PRIVACY.md file + +Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md). + +## Launch Product Version Containing the new events + +Events do not become active until they ship in a released PowerToys version. After your PRs are merged: + +- The event will begin firing once users install the version that includes it +- In order for PowerToys to process these events, you must complete the next section + +## Next Steps + +Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s). + +## Summary + +Required steps: + +1. In one PR: + - Add the event(s) in code + - Document event(s) in DATA_AND_PRIVACY.md +1. Ship the change in a PowerToys release +1. Reach out for next steps diff --git a/doc/devdocs/modules/fancyzones.md b/doc/devdocs/modules/fancyzones.md index 0f3c812131..b1bdf4b3d0 100644 --- a/doc/devdocs/modules/fancyzones.md +++ b/doc/devdocs/modules/fancyzones.md @@ -152,7 +152,7 @@ FancyZones is divided into several projects: ## Development Environment Setup ### Prerequisites -- Visual Studio 2022 or 2026: Required for building and debugging +- Visual Studio 2026 (or 2022 17.4+): Required for building and debugging - Windows 10 SDK: Ensure the latest version is installed - PowerToys Repository: Clone from GitHub @@ -183,7 +183,7 @@ FancyZones is divided into several projects: ## Debugging ### Setup for Debugging -1. In Visual Studio 2022 or 2026, set FancyZonesEditor as the startup project +1. In Visual Studio, set FancyZonesEditor as the startup project 2. Set breakpoints in the code where needed 3. Click Run to start debugging diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md index 35814fe03c..e7c96c7776 100644 --- a/doc/devdocs/readme.md +++ b/doc/devdocs/readme.md @@ -2,90 +2,31 @@ Welcome to the PowerToys developer documentation. This documentation provides information for developers who want to contribute to PowerToys or understand how it works. -## Core Architecture +## Getting Started -- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface -- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process -- [Settings](core/settings/readme.md) - Documentation on the settings system -- [Installer](core/installer.md) - Information about the PowerToys installer -- [Modules](modules/readme.md) - Documentation for individual PowerToys modules +### Prerequisites -## Common Components +1. Windows 10 April 2018 Update (version 1803) or newer +1. [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) (recommended) or Visual Studio 2022 17.4+ with the following workloads/components: + - Desktop Development with C++ + - WinUI application development + - .NET desktop development + - Windows 10 SDK (10.0.22621.0) + - Windows 11 SDK (10.0.26100.3916) +1. .NET 8 SDK +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) -- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers -- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules -- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry -- [Localization](development/localization.md) - How to support multiple languages +> **Tip:** You can install Visual Studio with all required workloads automatically using the [WinGet configuration files](https://github.com/microsoft/PowerToys/tree/main/.config) in the repository: +> ```powershell +> winget configure .config\configuration.winget +> ``` +> Pick the file that matches your VS edition (e.g., `configuration.vsProfessional.winget` or `configuration.vsEnterprise.winget`). -## Development Guidelines - -- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices -- [Coding Style](development/style.md) - Code formatting and style conventions -- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys -- [Debugging](development/debugging.md) - Techniques for debugging PowerToys - -## Tools - -- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys -- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys -- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information -- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging -- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules - -## Processes - -- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published -- [Update Process](processes/update-process.md) - How PowerToys updates work -- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details - -## Other Resources - -- [aka.ms links](akaLinks.md) - List of short links -- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests - -## Fork, Clone, Branch and Create your PR - -Once you've discussed your proposed feature/fix/etc. with a team member, and an approach or a spec has been written and approved, it's time to start development: +### Fork, Clone, and Set Up 1. Fork the repo on GitHub if you haven't already 1. Clone your fork locally -1. Create a feature branch -1. Work on your changes -1. Create a [Draft Pull Request (PR)](https://github.blog/2019-02-14-introducing-draft-pull-requests/) -1. When ready, mark your PR as "ready for review". - -## Rules - -- **Follow the pattern of what you already see in the code.** -- [Coding style](development/style.md). -- Try to package new functionality/components into libraries that have nicely defined interfaces. -- Package new functionality into classes or refactor existing functionality into a class as you extend the code. -- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests. - -## GitHub Workflow - -- Before starting to work on a fix/feature, make sure there is an open issue to track the work. -- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set. -- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date. -- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item. -- When opening a PR, follow the PR template. -- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. -- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it. -- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`. -- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue. - -## Compiling PowerToys - -### Prerequisites for Compiling PowerToys - -1. Windows 10 April 2018 Update (version 1803) or newer -1. Visual Studio Community/Professional/Enterprise 2022 17.4 or newer, or Visual Studio 2026 -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) - -### Automated Setup (Recommended) - -Run the setup script to automatically configure your development environment: +1. Run the automated setup script (**recommended**): ```powershell .\tools\build\setup-dev-environment.ps1 @@ -97,15 +38,10 @@ This script will: - Guide you through installing required Visual Studio components from `.vsconfig` - Initialize git submodules -Run with `-Help` to see all available options: +Run with `-Help` to see all available options. -```powershell -.\tools\build\setup-dev-environment.ps1 -Help -``` - -### Manual Setup - -If you prefer to set up manually, follow these steps: +
+Manual setup (if you prefer not to use the script) #### Install Visual Studio dependencies @@ -114,15 +50,17 @@ If you prefer to set up manually, follow these steps: Alternatively, import the `.vsconfig` file from the repository root using Visual Studio Installer to install all required workloads. -#### Get Submodules to compile +#### Initialize submodules -We have submodules that need to be initialized before you can compile most parts of PowerToys. This should be a one-time step. +This is a one-time step required before you can compile most parts of PowerToys. 1. Open a terminal 1. Navigate to the folder you cloned PowerToys to. 1. Run `git submodule update --init --recursive` -### Compiling Source Code +
+ +### Building #### Using Visual Studio @@ -150,7 +88,77 @@ You can also build from the command line using the provided scripts in `tools\bu .\tools\build\build-installer.ps1 ``` -## Compile the installer +### Debugging + +See [Debugging](development/debugging.md) for detailed debugging techniques, including Visual Studio setup, attaching to child processes, and troubleshooting build errors. + +### Creating a New PowerToy + +See [Creating a New PowerToy](development/new-powertoy.md) for an end-to-end guide covering module architecture, settings integration, installer packaging, and testing. + +## Development Guidelines + +- [Coding Guidelines](development/guidelines.md) - Development guidelines and best practices +- [Coding Style](development/style.md) - Code formatting and style conventions +- [Logging and Telemetry](development/logging.md) - How to use logging and telemetry +- [Localization](development/localization.md) - How to support multiple languages +- [UI Testing](development/ui-tests.md) - How to write UI tests for PowerToys +- [Developing with VS Code](development/dev-with-vscode.md) - Build, debug, and contribute using VS Code + +## Rules + +- **Follow the pattern of what you already see in the code.** +- [Coding style](development/style.md). +- Try to package new functionality/components into libraries that have nicely defined interfaces. +- Package new functionality into classes or refactor existing functionality into a class as you extend the code. +- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests. + +## GitHub Workflow + +- Before starting to work on a fix/feature, make sure there is an open issue to track the work. +- Add the `In progress` label to the issue, if not already present. Also add a `Cost-Small/Medium/Large` estimate and make sure all appropriate labels are set. +- If you are a community contributor, you will not be able to add labels to the issue; in that case just add a comment saying that you have started work on the issue and try to give an estimate for the delivery date. +- If the work item has a medium/large cost, using the markdown task list, list each sub item and update the list with a check mark after completing each sub item. +- **Before opening a PR, ensure your changes build successfully locally and functionality tests pass.** This is especially important for AI-assisted (vibe coding) contributions—always verify AI-generated code works as intended. Exploratory PRs or draft PRs for discussion are exceptions. +- When opening a PR, follow the PR template. +- When you'd like the team to take a look (even if the work is not yet fully complete) mark the PR as 'Ready For Review' so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. +- When the PR is approved, let the owner of the PR merge it. For community contributions, the reviewer who approved the PR can also merge it. +- Use the `Squash and merge` option to merge a PR. If you don't want to squash it because there are logically different commits, use `Rebase and merge`. +- Close issues automatically when referenced in a PR. You can use [closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the body of the PR to have GitHub automatically link your PR to the issue. + +## Core Architecture + +- [Architecture Overview](core/architecture.md) - Overview of the PowerToys architecture and module interface +- [Runner and System tray](core/runner.md) - Details about the PowerToys Runner process +- [Settings](core/settings/readme.md) - Documentation on the settings system +- [Installer](core/installer.md) - Information about the PowerToys installer +- [Modules](modules/readme.md) - Documentation for individual PowerToys modules + +## Common Components + +- [Context Menu Handlers](common/context-menus.md) - How PowerToys implements and registers Explorer context menu handlers +- [Monaco Editor](common/monaco-editor.md) - How PowerToys uses the Monaco code editor component across modules + +## Tools + +- [Tools Overview](tools/readme.md) - Overview of tools in PowerToys +- [Build Tools](tools/build-tools.md) - Tools that help building PowerToys +- [Bug Report Tool](tools/bug-report-tool.md) - Tool for collecting logs and system information +- [Debugging Tools](tools/debugging-tools.md) - Specialized tools for debugging +- [Fuzzing Testing](tools/fuzzingtesting.md) - How to implement and run fuzz testing for PowerToys modules + +## Processes + +- [Release Process](processes/release-process.md) - How PowerToys releases are prepared and published +- [Update Process](processes/update-process.md) - How PowerToys updates work +- [GPO Implementation](processes/gpo.md) - Group Policy Objects implementation details + +## Other Resources + +- [aka.ms links](akaLinks.md) - List of short links +- [Issue/PR commands](commands.md) - Special commands for managing issues and pull requests + +## Building the Installer Our installer is two parts, an EXE and an MSI. The EXE (Bootstrapper) contains the MSI and handles more complex installation logic. - The EXE installs all prerequisites and installs PowerToys via the MSI. It has additional features such as the installation flags (see below). @@ -164,8 +172,3 @@ The installer can only be compiled in `Release` mode; steps 1 and 2 must be perf 1. Compile `PowerToysSetup.slnx` Path from root: `installer\PowerToysSetup.slnx` (details listed below) See [Installer](core/installer.md) for more details on building and debugging the installer. - -## How to create new PowerToys - -See the instructions on [how to install the PowerToys Module project template](/tools/project_template).
-Specifications for the [PowerToys settings API](core/settings/readme.md). diff --git a/doc/images/overview/CmdPal_Hero.gif b/doc/images/overview/CmdPal_Hero.gif deleted file mode 100644 index 53177eeeaf..0000000000 Binary files a/doc/images/overview/CmdPal_Hero.gif and /dev/null differ diff --git a/doc/images/runner/tray.png b/doc/images/runner/tray.png deleted file mode 100644 index f4632d8107..0000000000 Binary files a/doc/images/runner/tray.png and /dev/null differ diff --git a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj index 9e07a41049..6c24369efc 100644 --- a/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj +++ b/installer/PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj @@ -1,8 +1,8 @@ - + - + {B3A354B0-1E54-4B55-A962-FB5AF9330C19} @@ -88,7 +88,7 @@ inc;..\..\src\;..\..\src\common\Telemetry;telemetry;%(AdditionalIncludeDirectories) - /await /Zc:twoPhase- /Wv:18 %(AdditionalOptions) + /Zc:twoPhase- /Wv:18 %(AdditionalOptions) Level4 ProgramDatabase @@ -165,14 +165,14 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + diff --git a/installer/PowerToysSetupCustomActionsVNext/packages.config b/installer/PowerToysSetupCustomActionsVNext/packages.config index 926fbe5dbf..471922dc14 100644 --- a/installer/PowerToysSetupCustomActionsVNext/packages.config +++ b/installer/PowerToysSetupCustomActionsVNext/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/installer/PowerToysSetupVNext/Product.wxs b/installer/PowerToysSetupVNext/Product.wxs index 5256af42fd..1a5f8010f7 100644 --- a/installer/PowerToysSetupVNext/Product.wxs +++ b/installer/PowerToysSetupVNext/Product.wxs @@ -147,7 +147,7 @@ - + - - - - enable - enable - PowerToys.AllExperiments - .\Microsoft.VariantAssignment\ - - - - - - - - - - - - - - - - - - diff --git a/src/common/AllExperiments/Experiments.cs b/src/common/AllExperiments/Experiments.cs deleted file mode 100644 index d527c52c81..0000000000 --- a/src/common/AllExperiments/Experiments.cs +++ /dev/null @@ -1,214 +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.Globalization; -using System.Text.Json; - -using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; -using Microsoft.PowerToys.Telemetry; -using Microsoft.VariantAssignment.Client; -using Microsoft.VariantAssignment.Contract; -using Windows.System.Profile; - -namespace AllExperiments -{ - // The dependencies required to build this project are only available in the official build pipeline and are internal to Microsoft. - // However, this project is not required to build a test version of the application. - public class Experiments - { - public enum ExperimentState - { - Enabled, - Disabled, - NotLoaded, - } - -#pragma warning disable SA1401 // Need to use LandingPageExperiment as a static property in OobeShellPage.xaml.cs -#pragma warning disable CA2211 // Non-constant fields should not be visible - public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded; -#pragma warning restore CA2211 -#pragma warning restore SA1401 - - public async Task EnableLandingPageExperimentAsync() - { - if (Experiments.LandingPageExperiment != ExperimentState.NotLoaded) - { - return Experiments.LandingPageExperiment == ExperimentState.Enabled; - } - - Experiments varServ = new Experiments(); - await varServ.VariantAssignmentProvider_Initialize(); - var landingPageExperiment = varServ.IsExperiment; - - Experiments.LandingPageExperiment = landingPageExperiment ? ExperimentState.Enabled : ExperimentState.Disabled; - - return landingPageExperiment; - } - - private async Task VariantAssignmentProvider_Initialize() - { - IsExperiment = false; - string jsonFilePath = CreateFilePath(); - - var vaSettings = new VariantAssignmentClientSettings - { - Endpoint = new Uri("https://default.exp-tas.com/exptas77/a7a397e7-6fbe-4f21-a4e9-3f542e4b000e-exppowertoys/api/v1/tas"), - EnableCaching = true, - ResponseCacheTime = TimeSpan.FromMinutes(5), - }; - - try - { - var vaClient = vaSettings.GetTreatmentAssignmentServiceClient(); - var vaRequest = GetVariantAssignmentRequest(); - using var variantAssignments = await vaClient.GetVariantAssignmentsAsync(vaRequest).ConfigureAwait(false); - - if (variantAssignments.AssignedVariants.Count != 0) - { - var dataVersion = variantAssignments.DataVersion; - var featureVariables = variantAssignments.GetFeatureVariables(); - var assignmentContext = variantAssignments.GetAssignmentContext(); - var featureFlagValue = featureVariables[0].GetStringValue(); - - var experimentGroup = string.Empty; - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = JsonSerializer.Deserialize>(json); - - if (jsonDictionary != null) - { - if (!jsonDictionary.TryGetValue("dataversion", out object? value)) - { - value = dataVersion; - jsonDictionary.Add("dataversion", value); - } - - if (!jsonDictionary.ContainsKey("variantassignment")) - { - jsonDictionary.Add("variantassignment", featureFlagValue); - } - else - { - var jsonDataVersion = value.ToString(); - if (jsonDataVersion != null && int.Parse(jsonDataVersion, CultureInfo.InvariantCulture) < dataVersion) - { - jsonDictionary["dataversion"] = dataVersion; - jsonDictionary["variantassignment"] = featureFlagValue; - } - } - - experimentGroup = jsonDictionary["variantassignment"].ToString(); - - string output = JsonSerializer.Serialize(jsonDictionary); - File.WriteAllText(jsonFilePath, output); - } - - if (experimentGroup == "alternate" && AssignmentUnit != string.Empty) - { - IsExperiment = true; - } - - PowerToysTelemetry.Log.WriteEvent(new OobeVariantAssignmentEvent() { AssignmentContext = assignmentContext, ClientID = AssignmentUnit }); - } - } - catch (HttpRequestException ex) - { - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = JsonSerializer.Deserialize>(json); - - if (jsonDictionary != null) - { - if (jsonDictionary.TryGetValue("variantassignment", out object? value)) - { - if (value.ToString() == "alternate" && AssignmentUnit != string.Empty) - { - IsExperiment = true; - } - } - else - { - jsonDictionary["variantassignment"] = "current"; - } - } - - string output = JsonSerializer.Serialize(jsonDictionary); - File.WriteAllText(jsonFilePath, output); - - Logger.LogError("Error getting to TAS endpoint", ex); - } - catch (Exception ex) - { - Logger.LogError("Error getting variant assignments for experiment", ex); - } - } - - public bool IsExperiment { get; set; } - - private string? AssignmentUnit { get; set; } - - private VariantAssignmentRequest GetVariantAssignmentRequest() - { - var jsonFilePath = CreateFilePath(); - try - { - if (!File.Exists(jsonFilePath)) - { - AssignmentUnit = Guid.NewGuid().ToString(); - var data = new Dictionary() - { - ["clientid"] = AssignmentUnit, - }; - string jsonData = JsonSerializer.Serialize(data); - File.WriteAllText(jsonFilePath, jsonData); - } - else - { - string json = File.ReadAllText(jsonFilePath); - var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize>(json); - if (jsonDictionary != null) - { - AssignmentUnit = jsonDictionary["clientid"]?.ToString(); - } - } - } - catch (Exception ex) - { - Logger.LogError("Error creating/getting AssignmentUnit", ex); - } - - var attrNames = new List { "FlightRing", "c:InstallLanguage" }; - var attrData = AnalyticsInfo.GetSystemPropertiesAsync(attrNames).AsTask().GetAwaiter().GetResult(); - - var flightRing = string.Empty; - var installLanguage = string.Empty; - - if (attrData.ContainsKey("FlightRing")) - { - flightRing = attrData["FlightRing"]; - } - - if (attrData.ContainsKey("InstallLanguage")) - { - installLanguage = attrData["InstallLanguage"]; - } - - return new VariantAssignmentRequest - { - Parameters = - { - { "installLanguage", installLanguage }, - { "flightRing", flightRing }, - { "clientid", AssignmentUnit }, - }, - }; - } - - private string CreateFilePath() - { - var exeDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var settingsPath = @"Microsoft\PowerToys\experimentation.json"; - var filePath = Path.Combine(exeDir, settingsPath); - return filePath; - } - } -} diff --git a/src/common/AllExperiments/Logger.cs b/src/common/AllExperiments/Logger.cs deleted file mode 100644 index b9e1f20969..0000000000 --- a/src/common/AllExperiments/Logger.cs +++ /dev/null @@ -1,81 +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.IO.Abstractions; - -namespace AllExperiments -{ - public static class Logger - { - private static readonly IFileSystem FileSystem = new FileSystem(); - private static readonly IPath Path = FileSystem.Path; - private static readonly IDirectory Directory = FileSystem.Directory; - - private static readonly string ApplicationLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\Settings Logs\\Experimentation"); - - static Logger() - { - if (!Directory.Exists(ApplicationLogPath)) - { - Directory.CreateDirectory(ApplicationLogPath); - } - - // Using InvariantCulture since this is used for a log file name - var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log"); - - Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); - - Trace.AutoFlush = true; - } - - public static void LogInfo(string message) - { - Log(message, "INFO"); - } - - public static void LogError(string message) - { - Log(message, "ERROR"); -#if DEBUG - Debugger.Break(); -#endif - } - - public static void LogError(string message, Exception e) - { - Log( - message + Environment.NewLine + - e?.Message + Environment.NewLine + - "Inner exception: " + Environment.NewLine + - e?.InnerException?.Message + Environment.NewLine + - "Stack trace: " + Environment.NewLine + - e?.StackTrace, - "ERROR"); -#if DEBUG - Debugger.Break(); -#endif - } - - private static void Log(string message, string type) - { - Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay); - Trace.Indent(); - Trace.WriteLine(GetCallerInfo()); - Trace.WriteLine(message); - Trace.Unindent(); - } - - private static string GetCallerInfo() - { - StackTrace stackTrace = new StackTrace(); - - var methodName = stackTrace.GetFrame(3)?.GetMethod(); - var className = methodName?.DeclaringType?.Name; - return "[Method]: " + methodName?.Name + " [Class]: " + className; - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs deleted file mode 100644 index ee08acd718..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentClientExtensionMethods.cs +++ /dev/null @@ -1,21 +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.VariantAssignment.Contract; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Client -{ -#pragma warning disable SA1200 // Using directives should be placed correctly - using TreatmentAssignmentServiceClient = VariantAssignmentServiceClient; -#pragma warning restore SA1200 // Using directives should be placed correctly - - public static class VariantAssignmentClientExtensionMethods - { - public static IVariantAssignmentProvider GetTreatmentAssignmentServiceClient(this VariantAssignmentClientSettings settings) - { - return new TreatmentAssignmentServiceClient(); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs deleted file mode 100644 index 373651f83a..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Client/VariantAssignmentServiceClient.cs +++ /dev/null @@ -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.VariantAssignment.Contract; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Client -{ - internal sealed partial class VariantAssignmentServiceClient : IVariantAssignmentProvider, IDisposable - where TServerResponse : VariantAssignmentServiceResponse - { - public void Dispose() - { - throw new NotImplementedException(); - } - - public Task GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default) - { - return Task.FromResult(EmptyVariantAssignmentResponse.Instance); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs deleted file mode 100644 index 0e0cd54094..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/EmptyVariantAssignmentResponse.cs +++ /dev/null @@ -1,54 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public class EmptyVariantAssignmentResponse : IVariantAssignmentResponse - { - /// - /// Singleton instance of . - /// - public static readonly IVariantAssignmentResponse Instance = new EmptyVariantAssignmentResponse(); - - public EmptyVariantAssignmentResponse() - { - } - - public long DataVersion => 0; - - public string Thumbprint => string.Empty; - - /// - public IReadOnlyCollection AssignedVariants => Array.Empty(); - - /// -#pragma warning disable CS8603 // Possible null reference return. - public IFeatureVariable GetFeatureVariable(IReadOnlyList path) => null; -#pragma warning restore CS8603 // Possible null reference return. - - /// - public IReadOnlyList GetFeatureVariables(IReadOnlyList prefix) => Array.Empty(); - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - - string IVariantAssignmentResponse.GetAssignmentContext() - { - throw new NotImplementedException(); - } - - IReadOnlyList IVariantAssignmentResponse.GetFeatureVariables() - { - throw new NotImplementedException(); - } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs deleted file mode 100644 index 6c9a31e8ce..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IAssignedVariant.cs +++ /dev/null @@ -1,11 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IAssignedVariant - { - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs deleted file mode 100644 index fc9193ed0d..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IFeatureVariable.cs +++ /dev/null @@ -1,16 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IFeatureVariable - { - /// - /// Gets the variable's value as a string. - /// - /// String value of the variable. - string GetStringValue(); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs deleted file mode 100644 index dad9d39038..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentProvider.cs +++ /dev/null @@ -1,18 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IVariantAssignmentProvider : IDisposable - { - /// - /// Computes variant assignments based on data. - /// - /// Variant assignment parameters. - /// Propagates notification that operations should be canceled. - /// An awaitable task that returns a . - Task GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs deleted file mode 100644 index 9639a3a58d..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentRequest.cs +++ /dev/null @@ -1,15 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public interface IVariantAssignmentRequest - { - /// - /// Gets inputs used for evaluating filters, assignment units, etc. - /// - IReadOnlyCollection<(string Key, string Value)> Parameters { get; } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs deleted file mode 100644 index 29ee2209de..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/IVariantAssignmentResponse.cs +++ /dev/null @@ -1,48 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Snapshot of variant assignments. - /// - public interface IVariantAssignmentResponse : IDisposable - { - ///// - ///// Gets the serial number of variant assignment configuration snapshot used when assigning variants. - ///// - long DataVersion { get; } - - ///// - ///// Get a hash of the response suitable for caching. - ///// - // string Thumbprint { get; } - - /// - /// Gets the variants assigned based on request parameters and a variant configuration snapshot. - /// - IReadOnlyCollection AssignedVariants { get; } - - /// - /// Gets feature variables assigned by variants in this response. - /// - /// (Optional) Filter feature variables where contains the . - /// Range of matching feature variables. - IReadOnlyList GetFeatureVariables(IReadOnlyList prefix); - - // this actually part of the interface but gets the job done - IReadOnlyList GetFeatureVariables(); - - // this actually part of the interface but gets the job done - string GetAssignmentContext(); - - /// - /// Gets a single feature variable assigned by variants in this response. - /// - /// Exact feature variable path. - /// Matching feature variable or null. - IFeatureVariable GetFeatureVariable(IReadOnlyList path); - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs deleted file mode 100644 index 6db91f6ffd..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/TreatmentAssignmentServiceResponse.cs +++ /dev/null @@ -1,11 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - internal sealed class TreatmentAssignmentServiceResponse : VariantAssignmentServiceResponse - { - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs deleted file mode 100644 index f57986368c..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentClientSettings.cs +++ /dev/null @@ -1,31 +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.ComponentModel.DataAnnotations; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Configuration for variant assignment service client. - /// - public class VariantAssignmentClientSettings - { - /// - /// Gets or sets the variant assignment service endpoint URL. - /// - [Required] - public Uri? Endpoint { get; set; } - - /// - /// Gets or sets a value indicating whether gets or sets a value whether client side request caching should be enabled. - /// - public bool EnableCaching { get; set; } - - /// - /// Gets or sets the maximum time a cached variant assignment response may be used without re-validating. - /// - public TimeSpan ResponseCacheTime { get; set; } - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs deleted file mode 100644 index 976ce53531..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentRequest.cs +++ /dev/null @@ -1,21 +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.Collections.Specialized; - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - public class VariantAssignmentRequest : IVariantAssignmentRequest - { - private NameValueCollection _parameters = new NameValueCollection(); - - /// - /// Gets or sets mutable . - /// - public NameValueCollection Parameters { get => _parameters; set => _parameters = value; } - - IReadOnlyCollection<(string Key, string Value)> IVariantAssignmentRequest.Parameters => (IReadOnlyCollection<(string Key, string Value)>)_parameters; - } -} diff --git a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs b/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs deleted file mode 100644 index e87425f4d3..0000000000 --- a/src/common/AllExperiments/Microsoft.VariantAssignment/Contract/VariantAssignmentServiceResponse.cs +++ /dev/null @@ -1,48 +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. - -// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects -namespace Microsoft.VariantAssignment.Contract -{ - /// - /// Mutable implementation of for (de)serialization. - /// - internal class VariantAssignmentServiceResponse : IVariantAssignmentResponse, IDisposable - { - /// - public virtual long DataVersion { get; set; } - - public virtual IReadOnlyCollection AssignedVariants { get; set; } = Array.Empty(); - - public IFeatureVariable GetFeatureVariable(IReadOnlyList path) - { - throw new NotImplementedException(); - } - - public IReadOnlyList GetFeatureVariables(IReadOnlyList prefix) - { - throw new NotImplementedException(); - } - - protected virtual void Dispose(bool disposing) - { - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public IReadOnlyList GetFeatureVariables() - { - throw new NotImplementedException(); - } - - public string GetAssignmentContext() - { - return string.Empty; - } - } -} diff --git a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj index a711a52347..b98ee549ec 100644 --- a/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj +++ b/src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj @@ -1,6 +1,7 @@ - + + true true @@ -37,7 +38,6 @@ <_NoWinAPIFamilyApp>true - DynamicLibrary @@ -66,7 +66,7 @@ CalculatorEngineCommon - ..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ @@ -133,16 +133,16 @@ - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - + enable diff --git a/src/common/Common.UI.Controls/Common.UI.Controls.csproj b/src/common/Common.UI.Controls/Common.UI.Controls.csproj new file mode 100644 index 0000000000..9f2f1b490c --- /dev/null +++ b/src/common/Common.UI.Controls/Common.UI.Controls.csproj @@ -0,0 +1,30 @@ + + + + + + Library + net9.0-windows10.0.26100.0 + Microsoft.PowerToys.Common.UI.Controls + PowerToys.Common.UI.Controls + true + true + true + PowerToys.Common.UI.Controls.pri + enable + x64;ARM64 + + + + + + + + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/CheckBoxWithDescriptionControl.cs b/src/common/Common.UI.Controls/Controls/CheckBoxWithDescriptionControl/CheckBoxWithDescriptionControl.cs similarity index 81% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/CheckBoxWithDescriptionControl.cs rename to src/common/Common.UI.Controls/Controls/CheckBoxWithDescriptionControl/CheckBoxWithDescriptionControl.cs index d8b5b6e31a..3157515b0a 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/CheckBoxWithDescriptionControl.cs +++ b/src/common/Common.UI.Controls/Controls/CheckBoxWithDescriptionControl/CheckBoxWithDescriptionControl.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -8,15 +8,12 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; -namespace Microsoft.PowerToys.Settings.UI.Controls +namespace Microsoft.PowerToys.Common.UI.Controls { public partial class CheckBoxWithDescriptionControl : CheckBox { - private CheckBoxWithDescriptionControl _checkBoxSubTextControl; - public CheckBoxWithDescriptionControl() { - _checkBoxSubTextControl = (CheckBoxWithDescriptionControl)this; this.Loaded += CheckBoxSubTextControl_Loaded; } @@ -42,20 +39,20 @@ namespace Microsoft.PowerToys.Settings.UI.Controls // Add text box only if the description is not empty. Required for additional plugin options. if (!string.IsNullOrWhiteSpace(Description)) { - panel.Children.Add(new IsEnabledTextBlock() { Style = (Style)App.Current.Resources["SecondaryIsEnabledTextBlockStyle"], Text = Description }); + panel.Children.Add(new IsEnabledTextBlock() { Style = (Style)Application.Current.Resources["SecondaryIsEnabledTextBlockStyle"], Text = Description }); } - _checkBoxSubTextControl.Content = panel; + this.Content = panel; } public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( - "Header", + nameof(Header), typeof(string), typeof(CheckBoxWithDescriptionControl), new PropertyMetadata(default(string))); public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( - "Description", + nameof(Description), typeof(string), typeof(CheckBoxWithDescriptionControl), new PropertyMetadata(default(string))); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml b/src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml similarity index 87% rename from src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml rename to src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml index 8218d5bf21..2ecd30ee4b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IsEnabledTextBlock.xaml +++ b/src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml @@ -1,7 +1,9 @@ + xmlns:controls="using:Microsoft.PowerToys.Common.UI.Controls"> + + + - \ No newline at end of file + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml.cs b/src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml.cs similarity index 66% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml.cs rename to src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml.cs index 82c6b3a986..7c1d64f0f9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/IsEnabledTextBlock.xaml.cs +++ b/src/common/Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -7,7 +7,7 @@ using System.ComponentModel; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -namespace Microsoft.PowerToys.Settings.UI.Controls +namespace Microsoft.PowerToys.Common.UI.Controls { [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")] @@ -15,7 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls { public IsEnabledTextBlock() { - this.Style = (Style)App.Current.Resources["DefaultIsEnabledTextBlockStyle"]; + this.DefaultStyleKey = typeof(IsEnabledTextBlock); } protected override void OnApplyTemplate() @@ -26,11 +26,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls base.OnApplyTemplate(); } - public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - "Text", - typeof(string), - typeof(IsEnabledTextBlock), - null); + public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(IsEnabledTextBlock), new PropertyMetadata(null)); [Localizable(true)] public string Text @@ -39,6 +35,14 @@ namespace Microsoft.PowerToys.Settings.UI.Controls set => SetValue(TextProperty, value); } + public static readonly DependencyProperty IsTextSelectionEnabledProperty = DependencyProperty.Register(nameof(IsTextSelectionEnabled), typeof(bool), typeof(IsEnabledTextBlock), new PropertyMetadata(false)); + + public bool IsTextSelectionEnabled + { + get => (bool)GetValue(IsTextSelectionEnabledProperty); + set => SetValue(IsTextSelectionEnabledProperty, value); + } + private void IsEnabledTextBlock_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) { SetEnabledState(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml similarity index 99% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml rename to src/common/Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml index a28874b4ee..a162a67110 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml +++ b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml @@ -2,7 +2,7 @@ - \ No newline at end of file + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml.cs similarity index 97% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs rename to src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml.cs index 87dc9a4c21..1439a6e1d9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyVisual.xaml.cs +++ b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// 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. @@ -6,7 +6,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Windows.System; -namespace Microsoft.PowerToys.Settings.UI.Controls +namespace Microsoft.PowerToys.Common.UI.Controls { [TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))] [TemplateVisualState(Name = NormalState, GroupName = "CommonStates")] @@ -20,7 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls private const string DisabledState = "Disabled"; private const string InvalidState = "Invalid"; private const string WarningState = "Warning"; - private KeyCharPresenter _keyPresenter; + private KeyCharPresenter _keyPresenter = null!; public object Content { diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml b/src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml similarity index 98% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml rename to src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml index 719091a787..c846010509 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml +++ b/src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml @@ -2,7 +2,7 @@ diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs b/src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml.cs similarity index 98% rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs rename to src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml.cs index 7e4d31c28b..1a2913d254 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs +++ b/src/common/Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml.cs @@ -7,7 +7,7 @@ using CommunityToolkit.WinUI.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; -namespace Microsoft.PowerToys.Settings.UI.Controls +namespace Microsoft.PowerToys.Common.UI.Controls { public sealed partial class ShortcutWithTextLabelControl : Control { diff --git a/src/settings-ui/Settings.UI/Converters/BoolToKeyVisualStateConverter.cs b/src/common/Common.UI.Controls/Converters/BoolToKeyVisualStateConverter.cs similarity index 81% rename from src/settings-ui/Settings.UI/Converters/BoolToKeyVisualStateConverter.cs rename to src/common/Common.UI.Controls/Converters/BoolToKeyVisualStateConverter.cs index 04a62b02c7..47c9305cb3 100644 --- a/src/settings-ui/Settings.UI/Converters/BoolToKeyVisualStateConverter.cs +++ b/src/common/Common.UI.Controls/Converters/BoolToKeyVisualStateConverter.cs @@ -1,16 +1,11 @@ -// Copyright (c) Microsoft Corporation +// 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 System.Text; -using System.Threading.Tasks; -using Microsoft.PowerToys.Settings.UI.Controls; using Microsoft.UI.Xaml.Data; -namespace Microsoft.PowerToys.Settings.UI.Converters +namespace Microsoft.PowerToys.Common.UI.Controls { public partial class BoolToKeyVisualStateConverter : IValueConverter { diff --git a/src/common/Common.UI.Controls/Themes/Generic.xaml b/src/common/Common.UI.Controls/Themes/Generic.xaml new file mode 100644 index 0000000000..201150458c --- /dev/null +++ b/src/common/Common.UI.Controls/Themes/Generic.xaml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/common/Common.UI/Common.UI.csproj b/src/common/Common.UI/Common.UI.csproj index 19fe2f1ee4..671e03f7ec 100644 --- a/src/common/Common.UI/Common.UI.csproj +++ b/src/common/Common.UI/Common.UI.csproj @@ -1,7 +1,7 @@ - + - - + + true diff --git a/src/common/Display/Display.vcxproj b/src/common/Display/Display.vcxproj index c5802f0ad8..cbe24d73cf 100644 --- a/src/common/Display/Display.vcxproj +++ b/src/common/Display/Display.vcxproj @@ -1,6 +1,6 @@ - + 16.0 {CABA8DFB-823B-4BF2-93AC-3F31984150D9} @@ -25,7 +25,7 @@ NotUsing - ..\..\..\;..\..\common;.\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\;..\..\common;.\;%(AdditionalIncludeDirectories) _LIB;%(PreprocessorDefinitions) @@ -45,13 +45,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/Display/packages.config b/src/common/Display/packages.config index 2e5039eb82..97349a856f 100644 --- a/src/common/Display/packages.config +++ b/src/common/Display/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/common/FilePreviewCommon/FilePreviewCommon.csproj b/src/common/FilePreviewCommon/FilePreviewCommon.csproj index 560fbf3287..b968ba5ab4 100644 --- a/src/common/FilePreviewCommon/FilePreviewCommon.csproj +++ b/src/common/FilePreviewCommon/FilePreviewCommon.csproj @@ -1,8 +1,8 @@ - + - - - + + + PowerToys FilePreviewCommon diff --git a/src/common/GPOWrapper/GPOWrapper.vcxproj b/src/common/GPOWrapper/GPOWrapper.vcxproj index ea0b32c358..1e9d256c90 100644 --- a/src/common/GPOWrapper/GPOWrapper.vcxproj +++ b/src/common/GPOWrapper/GPOWrapper.vcxproj @@ -1,6 +1,7 @@ - + + true true @@ -16,7 +17,6 @@ Windows Store 10.0 - DynamicLibrary @@ -111,13 +111,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/GPOWrapper/packages.config b/src/common/GPOWrapper/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/GPOWrapper/packages.config +++ b/src/common/GPOWrapper/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/GPOWrapperProjection/GPOWrapperProjection.csproj b/src/common/GPOWrapperProjection/GPOWrapperProjection.csproj index 4afd5d9393..a3f43f48fb 100644 --- a/src/common/GPOWrapperProjection/GPOWrapperProjection.csproj +++ b/src/common/GPOWrapperProjection/GPOWrapperProjection.csproj @@ -1,6 +1,6 @@ - + enable diff --git a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs index 5158e4334e..ae39080706 100644 --- a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs +++ b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs @@ -34,8 +34,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider } // Check if model is in catalog - var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false; - if (!isInCatalog) + if (!EnsureModelInCatalog(modelId)) { var errorMessage = $"{modelId} is not supported in Foundry Local. Please configure supported models in Settings."; Logger.LogError($"[FoundryLocal] {errorMessage}"); @@ -43,15 +42,28 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider } // Ensure the model is loaded before returning chat client - var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); + var isLoaded = EnsureModelLoadedWithRefresh(modelId); if (!isLoaded) { Logger.LogError($"[FoundryLocal] Failed to load model: {modelId}"); throw new InvalidOperationException($"Failed to load the model '{modelId}'."); } + var client = _foundryClient; + if (client == null) + { + const string message = "Foundry Local client could not be created. Please make sure Foundry Local is installed and running."; + Logger.LogError($"[FoundryLocal] {message}"); + throw new InvalidOperationException(message); + } + // Use ServiceUri instead of Endpoint since Endpoint already includes /v1 - var baseUri = _foundryClient.GetServiceUri(); + var baseUri = client.GetServiceUri(); + if (baseUri == null && TryRefreshClient("Service URI was not available")) + { + baseUri = _foundryClient?.GetServiceUri(); + } + if (baseUri == null) { const string message = "Foundry Local service URL is not available. Please make sure Foundry Local is installed and running."; @@ -124,6 +136,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider if (_foundryClient != null && _catalogModels != null && _catalogModels.Any()) { await _foundryClient.EnsureRunning().ConfigureAwait(false); + _serviceUrl = await _foundryClient.GetServiceUrl().ConfigureAwait(false); return; } @@ -153,4 +166,75 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider Logger.LogInfo($"[FoundryLocal] Available: {available}"); return available; } + + private bool EnsureModelInCatalog(string modelId) + { + var isInCatalog = _catalogModels?.Any(m => m.Name == modelId) ?? false; + if (isInCatalog) + { + return true; + } + + Logger.LogWarning($"[FoundryLocal] Model not found in catalog. Refreshing client for model: {modelId}"); + if (!TryRefreshClient("Model not in catalog")) + { + return false; + } + + return _catalogModels?.Any(m => m.Name == modelId) ?? false; + } + + private bool EnsureModelLoadedWithRefresh(string modelId) + { + var isLoaded = false; + + try + { + isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Logger.LogWarning($"[FoundryLocal] EnsureModelLoaded failed: {ex.Message}"); + } + + if (isLoaded) + { + return true; + } + + if (!TryRefreshClient("EnsureModelLoaded failed")) + { + return false; + } + + try + { + return _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Logger.LogError($"[FoundryLocal] EnsureModelLoaded failed after refresh: {ex.Message}", ex); + return false; + } + } + + private bool TryRefreshClient(string reason) + { + Logger.LogInfo($"[FoundryLocal] Refreshing Foundry Local client: {reason}"); + + try + { + _foundryClient = null; + _catalogModels = null; + _serviceUrl = null; + + InitializeAsync().GetAwaiter().GetResult(); + return _foundryClient != null; + } + catch (Exception ex) + { + Logger.LogError($"[FoundryLocal] Failed to refresh Foundry Local client: {ex.Message}", ex); + return false; + } + } } diff --git a/src/common/LanguageModelProvider/LanguageModelProvider.csproj b/src/common/LanguageModelProvider/LanguageModelProvider.csproj index 4dba9247a3..8d9d6754aa 100644 --- a/src/common/LanguageModelProvider/LanguageModelProvider.csproj +++ b/src/common/LanguageModelProvider/LanguageModelProvider.csproj @@ -1,6 +1,6 @@ - + enable diff --git a/src/common/ManagedCommon/ManagedCommon.csproj b/src/common/ManagedCommon/ManagedCommon.csproj index 8be6b9bb5f..1dcc2bf327 100644 --- a/src/common/ManagedCommon/ManagedCommon.csproj +++ b/src/common/ManagedCommon/ManagedCommon.csproj @@ -1,7 +1,7 @@ - + - - + + PowerToys ManagedCommon diff --git a/src/common/ManagedCommon/WindowHelpers.cs b/src/common/ManagedCommon/WindowHelpers.cs index c5ee13f69c..442ca34e27 100644 --- a/src/common/ManagedCommon/WindowHelpers.cs +++ b/src/common/ManagedCommon/WindowHelpers.cs @@ -41,6 +41,7 @@ namespace ManagedCommon /// black. Calls DwmExtendFrameIntoClientArea() with a cyTopHeight of 2 to force /// the window's top border to be visible.

/// Is a no-op on versions other than Windows 10. + /// WinUI issue: https://github.com/microsoft/microsoft-ui-xaml/issues/6901 /// public static void ForceTopBorder1PixelInsetOnWindows10(IntPtr handle) { diff --git a/src/common/ManagedCsWin32/ManagedCsWin32.csproj b/src/common/ManagedCsWin32/ManagedCsWin32.csproj index a80c111ad0..65b06d5f9c 100644 --- a/src/common/ManagedCsWin32/ManagedCsWin32.csproj +++ b/src/common/ManagedCsWin32/ManagedCsWin32.csproj @@ -1,6 +1,6 @@  - + PowerToys ManagedCsWin32 diff --git a/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj b/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj index 3929c60618..c73bd6ca6f 100644 --- a/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj +++ b/src/common/ManagedTelemetry/Telemetry/ManagedTelemetry.csproj @@ -1,6 +1,6 @@ - + - + PowerToys Telemetry diff --git a/src/common/PowerToys.ModuleContracts/PowerToys.ModuleContracts.csproj b/src/common/PowerToys.ModuleContracts/PowerToys.ModuleContracts.csproj index aa80bb05fb..eec8c621b2 100644 --- a/src/common/PowerToys.ModuleContracts/PowerToys.ModuleContracts.csproj +++ b/src/common/PowerToys.ModuleContracts/PowerToys.ModuleContracts.csproj @@ -1,7 +1,7 @@ - - + + enable diff --git a/src/common/SettingsAPI/SettingsAPI.vcxproj b/src/common/SettingsAPI/SettingsAPI.vcxproj index 315fb5a82f..d9a6e96016 100644 --- a/src/common/SettingsAPI/SettingsAPI.vcxproj +++ b/src/common/SettingsAPI/SettingsAPI.vcxproj @@ -1,6 +1,8 @@ - + + + 16.0 {6955446D-23F7-4023-9BB3-8657F904AF99} @@ -8,7 +10,6 @@ SettingsAPI SettingsAPI - StaticLibrary @@ -21,7 +22,7 @@ - ..\;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + ..\;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) _LIB;%(PreprocessorDefinitions) @@ -48,17 +49,17 @@ - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/common/SettingsAPI/packages.config b/src/common/SettingsAPI/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/SettingsAPI/packages.config +++ b/src/common/SettingsAPI/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/Telemetry/EtwTrace/EtwTrace.vcxproj b/src/common/Telemetry/EtwTrace/EtwTrace.vcxproj index b213541eda..4c2a4e48e6 100644 --- a/src/common/Telemetry/EtwTrace/EtwTrace.vcxproj +++ b/src/common/Telemetry/EtwTrace/EtwTrace.vcxproj @@ -1,14 +1,14 @@ - + + 17.0 Win32Proj {8f021b46-362b-485c-bfba-ccf83e820cbd} EtwTrace - StaticLibrary @@ -37,15 +37,15 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/common/Telemetry/EtwTrace/packages.config b/src/common/Telemetry/EtwTrace/packages.config index ff4b059648..d3882436a5 100644 --- a/src/common/Telemetry/EtwTrace/packages.config +++ b/src/common/Telemetry/EtwTrace/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/common/Themes/Themes.vcxproj b/src/common/Themes/Themes.vcxproj index be37d7d15c..bace40814b 100644 --- a/src/common/Themes/Themes.vcxproj +++ b/src/common/Themes/Themes.vcxproj @@ -1,6 +1,7 @@ - + + 16.0 {98537082-0FDB-40DE-ABD8-0DC5A4269BAB} @@ -8,7 +9,6 @@ Themes Themes - StaticLibrary @@ -24,7 +24,7 @@ - ..\..\..\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\;%(AdditionalIncludeDirectories) _LIB;%(PreprocessorDefinitions) NotUsing @@ -46,13 +46,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/Themes/packages.config b/src/common/Themes/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/Themes/packages.config +++ b/src/common/Themes/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/UITestAutomation/UITestAutomation.csproj b/src/common/UITestAutomation/UITestAutomation.csproj index 549b8a430b..0b40b9fcd3 100644 --- a/src/common/UITestAutomation/UITestAutomation.csproj +++ b/src/common/UITestAutomation/UITestAutomation.csproj @@ -1,6 +1,6 @@ - + - + Library diff --git a/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj index e0842faf83..b2fea03866 100644 --- a/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj +++ b/src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj @@ -1,6 +1,7 @@ - + + 16.0 {1A066C63-64B3-45F8-92FE-664E1CCE8077} @@ -9,7 +10,6 @@ NativeUnitTestProject Common.Lib.UnitTests - DynamicLibrary false @@ -58,13 +58,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/UnitTests-CommonLib/packages.config b/src/common/UnitTests-CommonLib/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/UnitTests-CommonLib/packages.config +++ b/src/common/UnitTests-CommonLib/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj b/src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj index 8b2b8f0053..75cbdf8c35 100644 --- a/src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj +++ b/src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj @@ -1,6 +1,6 @@ - + 16.0 {8B5CFB38-CCBA-40A8-AD7A-89C57B070884} @@ -83,13 +83,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/UnitTests-CommonUtils/packages.config b/src/common/UnitTests-CommonUtils/packages.config index 2e5039eb82..97349a856f 100644 --- a/src/common/UnitTests-CommonUtils/packages.config +++ b/src/common/UnitTests-CommonUtils/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/common/interop/HotkeyManager.cpp b/src/common/interop/HotkeyManager.cpp index adcbb9ada0..a9a6a19e6a 100644 --- a/src/common/interop/HotkeyManager.cpp +++ b/src/common/interop/HotkeyManager.cpp @@ -14,7 +14,7 @@ namespace winrt::PowerToys::Interop::implementation } // When all Shortcut keys are pressed, fire the HotkeyCallback event. - void HotkeyManager::KeyboardEventProc(KeyboardEvent ev) + void HotkeyManager::KeyboardEventProc(KeyboardEvent /*ev*/) { // pressedKeys always stores the latest keyboard state auto pressedKeysHandle = GetHotkeyHandle(pressedKeys); diff --git a/src/common/interop/PowerToys.Interop.vcxproj b/src/common/interop/PowerToys.Interop.vcxproj index 5e2c91de35..6aa4b96037 100644 --- a/src/common/interop/PowerToys.Interop.vcxproj +++ b/src/common/interop/PowerToys.Interop.vcxproj @@ -1,6 +1,7 @@ - + - + + PowerToys.Interop @@ -38,7 +39,6 @@ Windows Store 10.0 - DynamicLibrary Unicode @@ -55,7 +55,7 @@ PowerToys.Interop - ..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ true @@ -79,7 +79,7 @@ $(IntDir)pch.pch _WINRT_DLL;WINRT_LEAN_AND_MEAN;PowerToysInterop;%(PreprocessorDefinitions) - $(SolutionDir)src\common\interop;../../;../;%(AdditionalIncludeDirectories) + $(RepoRoot)src\common\interop;../../;../;%(AdditionalIncludeDirectories) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false %(AdditionalOptions) /bigobj /Zc:twoPhase- @@ -194,15 +194,15 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/common/interop/interop-tests/Common.Interop.UnitTests.csproj b/src/common/interop/interop-tests/Common.Interop.UnitTests.csproj index 9370dae75a..f68411165b 100644 --- a/src/common/interop/interop-tests/Common.Interop.UnitTests.csproj +++ b/src/common/interop/interop-tests/Common.Interop.UnitTests.csproj @@ -1,6 +1,6 @@ - + - + false diff --git a/src/common/interop/packages.config b/src/common/interop/packages.config index 6199e2345c..d3882436a5 100644 --- a/src/common/interop/packages.config +++ b/src/common/interop/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/src/common/logger/logger.vcxproj b/src/common/logger/logger.vcxproj index f1eae72063..3286277a85 100644 --- a/src/common/logger/logger.vcxproj +++ b/src/common/logger/logger.vcxproj @@ -1,6 +1,7 @@ - + + Debug @@ -33,14 +34,12 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} logger - StaticLibrary - - ..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ - + @@ -80,13 +79,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/logger/packages.config b/src/common/logger/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/logger/packages.config +++ b/src/common/logger/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj index a201dc0fa5..65a087a4c1 100644 --- a/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj +++ b/src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj @@ -1,6 +1,7 @@ - + + true true @@ -15,7 +16,6 @@ Windows Store 10.0 - StaticLibrary @@ -44,7 +44,8 @@ - notifications + + BackgroundActivator @@ -60,7 +61,8 @@ $(IntDir)pch.pch Level4 - %(AdditionalOptions) /bigobj + + %(AdditionalOptions) /bigobj /FS _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) @@ -96,13 +98,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/notifications/BackgroundActivator/packages.config b/src/common/notifications/BackgroundActivator/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/notifications/BackgroundActivator/packages.config +++ b/src/common/notifications/BackgroundActivator/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/notifications/BackgroundActivatorDLL/BackgroundActivatorDLL.vcxproj b/src/common/notifications/BackgroundActivatorDLL/BackgroundActivatorDLL.vcxproj index f82c50ca2d..994208d4f3 100644 --- a/src/common/notifications/BackgroundActivatorDLL/BackgroundActivatorDLL.vcxproj +++ b/src/common/notifications/BackgroundActivatorDLL/BackgroundActivatorDLL.vcxproj @@ -1,13 +1,13 @@ - + + 16.0 {031AC72E-FA28-4AB7-B690-6F7B9C28AA73} Win32Proj BackgroundActivatorDLL - DynamicLibrary @@ -64,9 +64,12 @@ - + + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + {031AC72E-FA28-4AB7-B690-6F7B9C28AA73} @@ -78,7 +81,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/common/notifications/BackgroundActivatorDLL/packages.config b/src/common/notifications/BackgroundActivatorDLL/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/common/notifications/BackgroundActivatorDLL/packages.config +++ b/src/common/notifications/BackgroundActivatorDLL/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/common/notifications/notifications.vcxproj b/src/common/notifications/notifications.vcxproj index 33ee65cdc3..bb124ab4ba 100644 --- a/src/common/notifications/notifications.vcxproj +++ b/src/common/notifications/notifications.vcxproj @@ -1,6 +1,7 @@ - + + 16.0 {1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525} @@ -8,7 +9,6 @@ notifications Notifications - StaticLibrary @@ -21,7 +21,8 @@ - ..\;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + ..\;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) _LIB;%(PreprocessorDefinitions) @@ -44,15 +45,15 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/common/notifications/packages.config b/src/common/notifications/packages.config index ff4b059648..d3882436a5 100644 --- a/src/common/notifications/packages.config +++ b/src/common/notifications/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/common/utils/HttpClient.h b/src/common/utils/HttpClient.h index 8726368fbb..ff9b47917f 100644 --- a/src/common/utils/HttpClient.h +++ b/src/common/utils/HttpClient.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include #include #include @@ -21,15 +22,15 @@ namespace http headers.UserAgent().TryParseAdd(USER_AGENT); } - std::future request(const winrt::Windows::Foundation::Uri& url) + winrt::Windows::Foundation::IAsyncOperation request(winrt::Windows::Foundation::Uri url) { auto response = co_await m_client.GetAsync(url); (void)response.EnsureSuccessStatusCode(); auto body = co_await response.Content().ReadAsStringAsync(); - co_return std::wstring(body); + co_return body; } - std::future download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath) + winrt::Windows::Foundation::IAsyncAction download(winrt::Windows::Foundation::Uri url, std::wstring dstFilePath) { auto response = co_await m_client.GetAsync(url); (void)response.EnsureSuccessStatusCode(); @@ -38,7 +39,7 @@ namespace http file_stream.Close(); } - std::future download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath, const std::function& progressUpdateCallback) + winrt::Windows::Foundation::IAsyncAction download(winrt::Windows::Foundation::Uri url, std::wstring dstFilePath, std::function progressUpdateCallback) { auto response = co_await m_client.GetAsync(url, HttpCompletionOption::ResponseHeadersRead); response.EnsureSuccessStatusCode(); diff --git a/src/common/version/version.vcxproj b/src/common/version/version.vcxproj index fff8efc983..ed5ff0d799 100644 --- a/src/common/version/version.vcxproj +++ b/src/common/version/version.vcxproj @@ -57,7 +57,7 @@ - ..\;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + ..\;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) _LIB;%(PreprocessorDefinitions) NotUsing diff --git a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj index b36e602d25..ab943c5090 100644 --- a/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj +++ b/src/dsc/PowerToys.Settings.DSC.Schema.Generator/PowerToys.Settings.DSC.Schema.Generator.csproj @@ -1,7 +1,7 @@ - + - - + + Exe diff --git a/src/dsc/v3/PowerToys.DSC.UnitTests/PowerToys.DSC.UnitTests.csproj b/src/dsc/v3/PowerToys.DSC.UnitTests/PowerToys.DSC.UnitTests.csproj index d7a8c8c2f8..3995b78254 100644 --- a/src/dsc/v3/PowerToys.DSC.UnitTests/PowerToys.DSC.UnitTests.csproj +++ b/src/dsc/v3/PowerToys.DSC.UnitTests/PowerToys.DSC.UnitTests.csproj @@ -1,10 +1,10 @@ - + - + false - ..\..\..\..\$(Configuration)\$(Platform)\tests\PowerToys.DSC.Tests\ + $(RepoRoot)$(Configuration)\$(Platform)\tests\PowerToys.DSC.Tests\ diff --git a/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj b/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj index a87508604f..003a1f1d58 100644 --- a/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj +++ b/src/dsc/v3/PowerToys.DSC/PowerToys.DSC.csproj @@ -1,11 +1,11 @@ - + - - + + Exe - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) false false PowerToys.DSC diff --git a/src/logging/logging.vcxproj b/src/logging/logging.vcxproj index b1a584ec94..5203df355e 100644 --- a/src/logging/logging.vcxproj +++ b/src/logging/logging.vcxproj @@ -1,5 +1,6 @@ - + + Debug @@ -31,13 +32,12 @@ Win32Proj spdlog - - + StaticLibrary MultiByte - ..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ @@ -65,98 +65,98 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste.FuzzTests/AdvancedPaste.FuzzTests.csproj b/src/modules/AdvancedPaste/AdvancedPaste.FuzzTests/AdvancedPaste.FuzzTests.csproj index 2d0b5f1ad1..557803aa64 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste.FuzzTests/AdvancedPaste.FuzzTests.csproj +++ b/src/modules/AdvancedPaste/AdvancedPaste.FuzzTests/AdvancedPaste.FuzzTests.csproj @@ -1,14 +1,14 @@ - - + + latest enable enable - ..\..\..\..\$(Platform)\$(Configuration)\tests\AdvancedPaste.FuzzTests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\AdvancedPaste.FuzzTests\ diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/AdvancedPaste.UnitTests.csproj b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/AdvancedPaste.UnitTests.csproj index 15b998a245..df90a2fa5d 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/AdvancedPaste.UnitTests.csproj +++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/AdvancedPaste.UnitTests.csproj @@ -1,6 +1,6 @@ - + - + false diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj index fd4a48b64d..54089122c7 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj @@ -1,11 +1,11 @@ - + - - + + WinExe - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps true Assets\AdvancedPaste\AdvancedPaste.ico app.manifest diff --git a/src/modules/AdvancedPaste/UITest-AdvancedPaste/AdvancedPaste-UITests.csproj b/src/modules/AdvancedPaste/UITest-AdvancedPaste/AdvancedPaste-UITests.csproj index 82a599d660..6848b5f1a6 100644 --- a/src/modules/AdvancedPaste/UITest-AdvancedPaste/AdvancedPaste-UITests.csproj +++ b/src/modules/AdvancedPaste/UITest-AdvancedPaste/AdvancedPaste-UITests.csproj @@ -1,6 +1,6 @@ - + - + {2B1505FA-132A-460B-B22B-7CC3FFAB0C5D} @@ -14,7 +14,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\tests\UITests-AdvancedPaste\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\UITests-AdvancedPaste\ diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj index 6d33ed8c4e..e931d3bb85 100644 --- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj +++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj @@ -1,6 +1,7 @@ - + + true true @@ -13,7 +14,6 @@ 10.0.26100.0 10.0.19041.0 - Debug @@ -60,14 +60,14 @@ PowerToys.$(MSBuildProjectName) - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ _CONSOLE;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) Level4 %(AdditionalOptions) /bigobj - $(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(RepoRoot)src\common\Telemetry;$(RepoRoot)src;%(AdditionalIncludeDirectories) Windows @@ -143,33 +143,33 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} - + - - - + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/src/modules/CropAndLock/CropAndLock/packages.config b/src/modules/CropAndLock/CropAndLock/packages.config index 691158d1b2..51fbe043d6 100644 --- a/src/modules/CropAndLock/CropAndLock/packages.config +++ b/src/modules/CropAndLock/CropAndLock/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariables.csproj b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariables.csproj index ff33c1ea56..9e8ce77167 100644 --- a/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariables.csproj +++ b/src/modules/EnvironmentVariables/EnvironmentVariables/EnvironmentVariables.csproj @@ -1,7 +1,7 @@ - + - - + + WinExe @@ -16,7 +16,7 @@ false true DISABLE_XAML_GENERATED_MAIN,TRACE - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps PowerToys.EnvironmentVariables Assets\EnvironmentVariables\EnvironmentVariables.ico diff --git a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesUILib.csproj b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesUILib.csproj index 3973f8d2eb..858f43313f 100644 --- a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesUILib.csproj +++ b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesUILib.csproj @@ -1,6 +1,6 @@ - + - + Library diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj index 86af5e3f1d..64cd1805e8 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj @@ -1,6 +1,6 @@ - + 17.0 Win32Proj @@ -102,13 +102,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/packages.config b/src/modules/FileLocksmith/FileLocksmithCLI/packages.config index 2e5039eb82..97349a856f 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithCLI/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj index 2b91c2491b..33cef29df4 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj @@ -1,6 +1,7 @@ - + + {A1B2C3D4-E5F6-7890-1234-567890ABCDEF} Win32Proj @@ -8,8 +9,7 @@ FileLocksmithCLI.UnitTests 10.0 - - + DynamicLibrary Unicode @@ -24,11 +24,11 @@ - ..\..\..\..\..\$(Platform)\$(Configuration)\tests\FileLocksmithCLI\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\FileLocksmithCLI\ - ..\;..\..\;..\..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + ..\;..\..\;$(RepoRoot)src\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WIN32;UNIT_TEST;%(PreprocessorDefinitions) true Use @@ -56,25 +56,28 @@ {9d52fd25-ef90-4f9a-a015-91efc5daf54f} - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} + + {1248566c-272a-43c0-88d6-e6675d569a09} + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config b/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config index 2e5039eb82..97349a856f 100644 --- a/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config @@ -1,4 +1,4 @@ - + diff --git a/src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj b/src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj index 10b6ed7736..eefb123196 100644 --- a/src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithContextMenu/FileLocksmithContextMenu.vcxproj @@ -1,8 +1,9 @@ - + + - + 17.0 @@ -10,12 +11,11 @@ {799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca} FileLocksmithContextMenu - PowerToys.FileLocksmithContextMenu $(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\ - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ DynamicLibrary @@ -125,12 +125,15 @@ MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + {9d52fd25-ef90-4f9a-a015-91efc5daf54f} @@ -138,18 +141,18 @@ MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithContextMenu/packages.config b/src/modules/FileLocksmith/FileLocksmithContextMenu/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/FileLocksmith/FileLocksmithContextMenu/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithContextMenu/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj index 8bca058c43..02d4697938 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj @@ -1,18 +1,18 @@ - + + - + 16.0 Win32Proj {57175ec7-92a5-4c1e-8244-e3fbca2a81de} FileLocksmithExt - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.FileLocksmithExt - DynamicLibrary true @@ -98,16 +98,16 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} @@ -115,15 +115,15 @@ - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/packages.config b/src/modules/FileLocksmith/FileLocksmithExt/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/FileLocksmith/FileLocksmithExt/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithExt/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj index e64b569387..4547182612 100644 --- a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj @@ -1,6 +1,7 @@ - + + 17.0 Win32Proj @@ -8,7 +9,6 @@ FileLocksmithLib 10.0 - StaticLibrary true @@ -86,13 +86,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLib/packages.config b/src/modules/FileLocksmith/FileLocksmithLib/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/FileLocksmith/FileLocksmithLib/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithLib/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj index fd995a2d97..4f1b5bcd85 100644 --- a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj @@ -1,6 +1,7 @@ - + + Debug @@ -26,10 +27,9 @@ PowerToys.FileLocksmithLib.Interop PowerToys.FileLocksmithLib.Interop net8.0-windows10.0.22621.0 - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.FileLocksmithLib.Interop - DynamicLibrary @@ -116,7 +116,7 @@ - + {f055103b-f80b-4d0c-bf48-057c55620033} @@ -133,13 +133,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/packages.config b/src/modules/FileLocksmith/FileLocksmithLibInterop/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/FileLocksmith/FileLocksmithLibInterop/packages.config +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj index f4b28d3922..600216fdbf 100644 --- a/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj +++ b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj @@ -1,13 +1,13 @@ - + - - + + PowerToys.FileLocksmith PowerToys File Locksmith WinExe - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps PowerToys.FileLocksmithUI PowerToys.FileLocksmithUI app.manifest diff --git a/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj b/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj index 51dee7a40b..89edfbfc98 100644 --- a/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj +++ b/src/modules/Hosts/Hosts.FuzzTests/HostsEditor.FuzzTests.csproj @@ -1,7 +1,7 @@ - + - - + + latest @@ -10,7 +10,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\tests\Hosts.FuzzTests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\Hosts.FuzzTests\ diff --git a/src/modules/Hosts/Hosts.Tests/HostsEditor.UnitTests.csproj b/src/modules/Hosts/Hosts.Tests/HostsEditor.UnitTests.csproj index 0f0a57ba6b..7b5aa8b532 100644 --- a/src/modules/Hosts/Hosts.Tests/HostsEditor.UnitTests.csproj +++ b/src/modules/Hosts/Hosts.Tests/HostsEditor.UnitTests.csproj @@ -1,6 +1,6 @@ - + - + false diff --git a/src/modules/Hosts/Hosts.UITests/HostsEditor.UITests.csproj b/src/modules/Hosts/Hosts.UITests/HostsEditor.UITests.csproj index c8a45ea3aa..e3fe2d8f65 100644 --- a/src/modules/Hosts/Hosts.UITests/HostsEditor.UITests.csproj +++ b/src/modules/Hosts/Hosts.UITests/HostsEditor.UITests.csproj @@ -1,6 +1,6 @@ - + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} @@ -15,7 +15,7 @@ false - ..\..\..\..\$(Platform)\$(Configuration)\tests\Hosts.UITests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\Hosts.UITests\ diff --git a/src/modules/Hosts/Hosts/Hosts.csproj b/src/modules/Hosts/Hosts/Hosts.csproj index b979a7604c..2e8df27eaa 100644 --- a/src/modules/Hosts/Hosts/Hosts.csproj +++ b/src/modules/Hosts/Hosts/Hosts.csproj @@ -1,7 +1,7 @@ - + - - + + WinExe @@ -13,7 +13,7 @@ false false true - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps PowerToys.Hosts DISABLE_XAML_GENERATED_MAIN,TRACE Assets/Hosts/Hosts.ico diff --git a/src/modules/Hosts/HostsUILib/HostsUILib.csproj b/src/modules/Hosts/HostsUILib/HostsUILib.csproj index 21e7822000..267e5e137d 100644 --- a/src/modules/Hosts/HostsUILib/HostsUILib.csproj +++ b/src/modules/Hosts/HostsUILib/HostsUILib.csproj @@ -1,7 +1,7 @@ - + - - + + Library diff --git a/src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj b/src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj index e6030450f7..c0c7cb2e5a 100644 --- a/src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj +++ b/src/modules/LightSwitch/LightSwitchLib/LightSwitchLib.vcxproj @@ -1,6 +1,6 @@ - + Debug @@ -114,6 +114,6 @@ - + diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj index f10364e4e3..86bc711c49 100644 --- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj +++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj @@ -1,6 +1,7 @@ - + + Debug @@ -27,7 +28,7 @@ 10.0.26100.0 LightSwitchService - + Application true @@ -43,7 +44,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(RepoRoot)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ PowerToys.LightSwitchService @@ -57,11 +58,11 @@ ./../; ..\LightSwitchLib; ..\..\..\common; - ..\..\..\common\logger; - ..\..\..\common\utils; - ..\..\..\common\SettingsAPI; - ..\..\..\common\Telemetry; - ..\..\..\; + $(RepoRoot)src\common\logger; + $(RepoRoot)src\common\utils; + $(RepoRoot)src\common\SettingsAPI; + $(RepoRoot)src\common\Telemetry; + $(RepoRoot)src\; ..\..\..\..\deps\spdlog\include; ./; %(AdditionalIncludeDirectories) @@ -98,16 +99,16 @@ - + {4aed67b6-55fd-486f-b917-e543dee2cb3c} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {1d5be09d-78c0-4fd7-af00-ae7c1af7c525} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} @@ -115,9 +116,9 @@ - + - - + + diff --git a/src/modules/LightSwitch/LightSwitchService/packages.config b/src/modules/LightSwitch/LightSwitchService/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/LightSwitch/LightSwitchService/packages.config +++ b/src/modules/LightSwitch/LightSwitchService/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/LightSwitch/Tests/LightSwitch.UITests/LightSwitch.UITests.csproj b/src/modules/LightSwitch/Tests/LightSwitch.UITests/LightSwitch.UITests.csproj index a1ec81d30c..c230c1c958 100644 --- a/src/modules/LightSwitch/Tests/LightSwitch.UITests/LightSwitch.UITests.csproj +++ b/src/modules/LightSwitch/Tests/LightSwitch.UITests/LightSwitch.UITests.csproj @@ -1,5 +1,5 @@ - - + + PowerToys.LightSwitch.UITests LightSwitch.UITests diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj index c71c81acec..a61b2e58bb 100644 --- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj +++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj @@ -1,4 +1,4 @@ - + @@ -30,7 +30,7 @@ Windows Store 10.0 true - true + false true @@ -52,13 +52,13 @@ - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ Level4 %(AdditionalOptions) /bigobj - $(SolutionDir)src\;..\..\..\common\Telemetry;..\..\..\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\;$(RepoRoot)src\common\Telemetry;$(RepoRoot)src\;%(AdditionalIncludeDirectories) Windows @@ -125,19 +125,19 @@ - + {caba8dfb-823b-4bf2-93ac-3f31984150d9} - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} @@ -145,5 +145,5 @@ - + \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj b/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj index 3e92bd42f3..f2424eda21 100644 --- a/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj +++ b/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj @@ -1,13 +1,13 @@ - + - - + + PowerToys.MeasureTool PowerToys MeasureTool WinExe - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps PowerToys.MeasureToolUI PowerToys.MeasureToolUI app.manifest diff --git a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml index 4a4f3dd8b0..5ae5e6e4c1 100644 --- a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml +++ b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml @@ -10,7 +10,6 @@ IsMaximizable="False" IsMinimizable="False" IsResizable="False" - IsShownInSwitchers="False" IsTitleBarVisible="False" mc:Ignorable="d"> diff --git a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml.cs b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml.cs index 4f807655da..afc59eb8d3 100644 --- a/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml.cs +++ b/src/modules/MeasureTool/MeasureToolUI/MeasureToolXAML/MainWindow.xaml.cs @@ -52,12 +52,23 @@ namespace MeasureToolUI var presenter = _appWindow.Presenter as OverlappedPresenter; presenter.IsAlwaysOnTop = true; this.SetIsAlwaysOnTop(true); - this.SetIsShownInSwitchers(false); this.SetIsResizable(false); this.SetIsMinimizable(false); this.SetIsMaximizable(false); IsTitleBarVisible = false; + try + { + this.SetIsShownInSwitchers(false); + } + catch (NotImplementedException) + { + // WinUI will throw if explorer is not running, safely ignore + } + catch (Exception) + { + } + // Remove the caption style from the window style. Windows App SDK 1.6 added it, which made the title bar and borders appear for Measure Tool. This code removes it. var windowStyle = GetWindowLong(hwnd, GWL_STYLE); windowStyle = windowStyle & (~WS_CAPTION); diff --git a/src/modules/MeasureTool/Tests/ScreenRuler.UITests/ScreenRuler.UITests.csproj b/src/modules/MeasureTool/Tests/ScreenRuler.UITests/ScreenRuler.UITests.csproj index be1da425a7..4e59a4c5b4 100644 --- a/src/modules/MeasureTool/Tests/ScreenRuler.UITests/ScreenRuler.UITests.csproj +++ b/src/modules/MeasureTool/Tests/ScreenRuler.UITests/ScreenRuler.UITests.csproj @@ -1,5 +1,5 @@ - - + + PowerToys.ScreenRuler.UITests ScreenRuler.UITests diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj b/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj index 1e5df5417c..0dc65fc86e 100644 --- a/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj +++ b/src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj @@ -1,6 +1,7 @@ - + + 15.0 {48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5} @@ -8,8 +9,7 @@ CursorWrap CursorWrap - - + DynamicLibrary true @@ -33,7 +33,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ PowerToys.CursorWrap @@ -80,7 +80,7 @@
- ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) @@ -104,10 +104,10 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -116,13 +116,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + +
\ No newline at end of file diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp b/src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp index bea59e6186..c1d4a9b36b 100644 --- a/src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp +++ b/src/modules/MouseUtils/CursorWrap/CursorWrapCore.cpp @@ -163,8 +163,22 @@ void CursorWrapCore::UpdateMonitorInfo() Logger::info(L"======= UPDATE MONITOR INFO END ======="); } -POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode) +POINT CursorWrapCore::HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor) { + // Check if wrapping should be disabled on single monitor + if (disableOnSingleMonitor && m_monitors.size() <= 1) + { +#ifdef _DEBUG + static bool loggedOnce = false; + if (!loggedOnce) + { + OutputDebugStringW(L"[CursorWrap] Single monitor detected - cursor wrapping disabled\n"); + loggedOnce = true; + } +#endif + return currentPos; + } + // Check if wrapping should be disabled during drag if (disableWrapDuringDrag && (GetAsyncKeyState(VK_LBUTTON) & 0x8000)) { diff --git a/src/modules/MouseUtils/CursorWrap/CursorWrapCore.h b/src/modules/MouseUtils/CursorWrap/CursorWrapCore.h index 6c19a26e39..d8472efd08 100644 --- a/src/modules/MouseUtils/CursorWrap/CursorWrapCore.h +++ b/src/modules/MouseUtils/CursorWrap/CursorWrapCore.h @@ -18,9 +18,11 @@ public: // Handle mouse move with wrap mode filtering // wrapMode: 0=Both, 1=VerticalOnly, 2=HorizontalOnly - POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode); + // disableOnSingleMonitor: if true, cursor wrapping is disabled when only one monitor is connected + POINT HandleMouseMove(const POINT& currentPos, bool disableWrapDuringDrag, int wrapMode, bool disableOnSingleMonitor); const std::vector& GetMonitors() const { return m_monitors; } + size_t GetMonitorCount() const { return m_monitors.size(); } const MonitorTopology& GetTopology() const { return m_topology; } private: diff --git a/src/modules/MouseUtils/CursorWrap/dllmain.cpp b/src/modules/MouseUtils/CursorWrap/dllmain.cpp index dbb3702da8..9db1e2f9f4 100644 --- a/src/modules/MouseUtils/CursorWrap/dllmain.cpp +++ b/src/modules/MouseUtils/CursorWrap/dllmain.cpp @@ -34,7 +34,363 @@ static CursorWrap* g_cursorWrapInstance = nullptr; // Implement the PowerToy Module Interface and all the required methods. class CursorWrap { +private: + // The PowerToy state. + bool m_enabled = false; + bool m_autoActivate = false; + bool m_disableWrapDuringDrag = true; // Default to true to prevent wrap during drag + int m_wrapMode = 0; // 0=Both (default), 1=VerticalOnly, 2=HorizontalOnly + + // Mouse hook + HHOOK m_mouseHook = nullptr; + std::atomic m_hookActive{ false }; + + // Core wrapping engine (edge-based polygon model) + CursorWrapCore m_core; + + // Hotkey + Hotkey m_activationHotkey{}; + + // Event-driven trigger support (for CmdPal/automation) + HANDLE m_triggerEventHandle = nullptr; + HANDLE m_terminateEventHandle = nullptr; + std::thread m_eventThread; + std::atomic_bool m_listening{ false }; + + // Display change notification + HWND m_messageWindow = nullptr; + HDEVNOTIFY m_deviceNotify = nullptr; + static constexpr UINT_PTR TIMER_UPDATE_MONITORS = 1; + static constexpr UINT DEBOUNCE_DELAY_MS = 500; + public: + // Constructor + CursorWrap() + { + LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::cursorWrapLoggerName); + init_settings(); + m_core.UpdateMonitorInfo(); + g_cursorWrapInstance = this; // Set global instance pointer + }; + + // Destroy the powertoy and free memory + virtual void destroy() override + { + // Ensure hooks/threads/handles are torn down before deletion + disable(); + g_cursorWrapInstance = nullptr; // Clear global instance pointer + delete this; + } + + // Return the localized display name of the powertoy + virtual const wchar_t* get_name() override + { + return MODULE_NAME; + } + + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return MODULE_NAME; + } + + // Return the configured status for the gpo policy for the module + virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override + { + return powertoys_gpo::getConfiguredCursorWrapEnabledValue(); + } + + // Return JSON with the configuration options. + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + PowerToysSettings::Settings settings(hinstance, get_name()); + + settings.set_description(IDS_CURSORWRAP_NAME); + settings.set_icon_key(L"pt-cursor-wrap"); + + // Create HotkeyObject from the Hotkey struct for the settings + auto hotkey_object = PowerToysSettings::HotkeyObject::from_settings( + m_activationHotkey.win, + m_activationHotkey.ctrl, + m_activationHotkey.alt, + m_activationHotkey.shift, + m_activationHotkey.key); + + settings.add_hotkey(JSON_KEY_ACTIVATION_SHORTCUT, IDS_CURSORWRAP_NAME, hotkey_object); + settings.add_bool_toggle(JSON_KEY_AUTO_ACTIVATE, IDS_CURSORWRAP_NAME, m_autoActivate); + settings.add_bool_toggle(JSON_KEY_DISABLE_WRAP_DURING_DRAG, IDS_CURSORWRAP_NAME, m_disableWrapDuringDrag); + + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* /*action*/) override {} + + // Called by the runner to pass the updated settings values as a serialized JSON. + virtual void set_config(const wchar_t* config) override + { + try + { + // Parse the input JSON string. + PowerToysSettings::PowerToyValues values = + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + + parse_settings(values); + } + catch (std::exception&) + { + Logger::error("Invalid json when trying to parse CursorWrap settings json."); + } + } + + // Enable the powertoy + virtual void enable() + { + m_enabled = true; + Trace::EnableCursorWrap(true); + + // Start listening for external trigger event so we can invoke the same logic as the activation hotkey. + m_triggerEventHandle = CreateEventW(nullptr, false, false, CommonSharedConstants::CURSOR_WRAP_TRIGGER_EVENT); + m_terminateEventHandle = CreateEventW(nullptr, false, false, nullptr); + if (m_triggerEventHandle && m_terminateEventHandle) + { + m_listening = true; + m_eventThread = std::thread([this]() { + HANDLE handles[2] = { m_triggerEventHandle, m_terminateEventHandle }; + + // WH_MOUSE_LL callbacks are delivered to the thread that installed the hook. + // Ensure this thread has a message queue and pumps messages while the hook is active. + MSG msg; + PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + // Create message window for display change notifications + RegisterForDisplayChanges(); + + StartMouseHook(); + Logger::info("CursorWrap enabled - mouse hook started"); + + while (m_listening) + { + auto res = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT); + if (!m_listening) + { + break; + } + + if (res == WAIT_OBJECT_0) + { + ToggleMouseHook(); + } + else if (res == WAIT_OBJECT_0 + 1) + { + break; + } + else + { + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + // Cleanup display change notifications + UnregisterDisplayChanges(); + + StopMouseHook(); + Logger::info("CursorWrap event listener stopped"); + }); + } + } + + // Disable the powertoy + virtual void disable() + { + m_enabled = false; + Trace::EnableCursorWrap(false); + + m_listening = false; + if (m_terminateEventHandle) + { + SetEvent(m_terminateEventHandle); + } + if (m_eventThread.joinable()) + { + m_eventThread.join(); + } + if (m_triggerEventHandle) + { + CloseHandle(m_triggerEventHandle); + m_triggerEventHandle = nullptr; + } + if (m_terminateEventHandle) + { + CloseHandle(m_terminateEventHandle); + m_terminateEventHandle = nullptr; + } + } + + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + + // Returns whether the PowerToys should be enabled by default + virtual bool is_enabled_by_default() const override + { + return false; + } + + // Legacy hotkey support + virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override + { + if (buffer && buffer_size >= 1) + { + buffer[0] = m_activationHotkey; + } + return 1; + } + + virtual bool on_hotkey(size_t hotkeyId) override + { + if (!m_enabled || hotkeyId != 0) + { + return false; + } + + // Toggle on the thread that owns the WH_MOUSE_LL hook (the event listener thread). + if (m_triggerEventHandle) + { + return SetEvent(m_triggerEventHandle); + } + + return false; + } + + // Called when display configuration changes - update monitor topology + void OnDisplayChange() + { +#ifdef _DEBUG + OutputDebugStringW(L"[CursorWrap] Display configuration changed, updating monitor topology\n"); +#endif + Logger::info("Display configuration changed, updating monitor topology"); + m_core.UpdateMonitorInfo(); + } + +private: + void ToggleMouseHook() + { + // Toggle cursor wrapping. + if (m_hookActive) + { + StopMouseHook(); + } + else + { + StartMouseHook(); + } + } + + // Load the settings file. + void init_settings() + { + try + { + // Load and parse the settings file for this PowerToy. + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(CursorWrap::get_key()); + parse_settings(settings); + } + catch (std::exception&) + { + Logger::error("Invalid json when trying to load the CursorWrap settings json from file."); + } + } + + void parse_settings(PowerToysSettings::PowerToyValues& settings) + { + auto settingsObject = settings.get_raw_json(); + if (settingsObject.GetView().Size()) + { + try + { + // Parse activation HotKey + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT); + auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject); + + m_activationHotkey.win = hotkey.win_pressed(); + m_activationHotkey.ctrl = hotkey.ctrl_pressed(); + m_activationHotkey.shift = hotkey.shift_pressed(); + m_activationHotkey.alt = hotkey.alt_pressed(); + m_activationHotkey.key = static_cast(hotkey.get_code()); + } + catch (...) + { + Logger::warn("Failed to initialize CursorWrap activation shortcut"); + } + + try + { + // Parse auto activate + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_AUTO_ACTIVATE); + m_autoActivate = jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize CursorWrap auto activate from settings. Will use default value"); + } + + try + { + // Parse disable wrap during drag + auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); + if (propertiesObject.HasKey(JSON_KEY_DISABLE_WRAP_DURING_DRAG)) + { + auto disableDragObject = propertiesObject.GetNamedObject(JSON_KEY_DISABLE_WRAP_DURING_DRAG); + m_disableWrapDuringDrag = disableDragObject.GetNamedBoolean(JSON_KEY_VALUE); + } + } + catch (...) + { + Logger::warn("Failed to initialize CursorWrap disable wrap during drag from settings. Will use default value (true)"); + } + + try + { + // Parse wrap mode + auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); + if (propertiesObject.HasKey(JSON_KEY_WRAP_MODE)) + { + auto wrapModeObject = propertiesObject.GetNamedObject(JSON_KEY_WRAP_MODE); + m_wrapMode = static_cast(wrapModeObject.GetNamedNumber(JSON_KEY_VALUE)); + } + } + catch (...) + { + Logger::warn("Failed to initialize CursorWrap wrap mode from settings. Will use default value (0=Both)"); + } + } + else + { + Logger::info("CursorWrap settings are empty"); + } + + // Set default hotkey if not configured + if (m_activationHotkey.key == 0) + { + m_activationHotkey.win = true; + m_activationHotkey.alt = true; + m_activationHotkey.ctrl = false; + m_activationHotkey.shift = false; + m_activationHotkey.key = 'U'; // Win+Alt+U + } + } + void StartMouseHook() { if (m_mouseHook || m_hookActive) @@ -349,7 +705,8 @@ private: POINT newPos = g_cursorWrapInstance->m_core.HandleMouseMove( currentPos, g_cursorWrapInstance->m_disableWrapDuringDrag, - g_cursorWrapInstance->m_wrapMode); + g_cursorWrapInstance->m_wrapMode, + g_cursorWrapInstance->m_disableOnSingleMonitor); if (newPos.x != currentPos.x || newPos.y != currentPos.y) { diff --git a/src/modules/MouseUtils/CursorWrap/packages.config b/src/modules/MouseUtils/CursorWrap/packages.config index 2c5d71ae86..7d3cbd2b91 100644 --- a/src/modules/MouseUtils/CursorWrap/packages.config +++ b/src/modules/MouseUtils/CursorWrap/packages.config @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj index c0691fba59..7df0fd0547 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -19,7 +19,7 @@ false false false - true + false false
@@ -51,7 +51,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ PowerToys.FindMyMouse @@ -99,7 +99,7 @@ - $(GeneratedFilesDir);$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;$(MSBuildThisFileDirectory)..\..\..\..\src\;$(MSBuildThisFileDirectory)..\..\..\..\src\modules;$(MSBuildThisFileDirectory)..\..\..\..\src\common\Telemetry;%(AdditionalIncludeDirectories) + $(GeneratedFilesDir);$(RepoRoot)src\;$(RepoRoot)src\modules;$(RepoRoot)src\common\Telemetry;$(MSBuildThisFileDirectory)..\..\..\..\src\;$(MSBuildThisFileDirectory)..\..\..\..\src\modules;$(MSBuildThisFileDirectory)..\..\..\..\src\common\Telemetry;%(AdditionalIncludeDirectories) @@ -118,10 +118,10 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -146,5 +146,5 @@ - +
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj index 4463846c20..728f9ae131 100644 --- a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj @@ -1,6 +1,7 @@ - + + 15.0 {782a61be-9d85-4081-b35c-1ccc9dcc1e88} @@ -8,7 +9,6 @@ MouseHighlighter MouseHighlighter - DynamicLibrary true @@ -32,7 +32,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ PowerToys.MouseHighlighter @@ -79,7 +79,7 @@ - $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories) + $(RepoRoot)src\;$(RepoRoot)src\modules;$(RepoRoot)src\common\Telemetry;%(AdditionalIncludeDirectories) @@ -100,10 +100,10 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -111,15 +111,15 @@ - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/packages.config b/src/modules/MouseUtils/MouseHighlighter/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/MouseUtils/MouseHighlighter/packages.config +++ b/src/modules/MouseUtils/MouseHighlighter/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj b/src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj index c97b009bb6..c247b3e7fa 100644 --- a/src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj +++ b/src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj @@ -1,6 +1,6 @@ - + - + @@ -9,7 +9,7 @@ PowerToys.MouseJump.Common.UnitTests PowerToys MouseJump.Common.UnitTests Library - ..\..\..\..\$(Platform)\$(Configuration)\tests\MouseJump.Common.UnitTests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\MouseJump.Common.UnitTests\ false false diff --git a/src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj b/src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj index 4b56443fa7..79d6b7b47e 100644 --- a/src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj +++ b/src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj @@ -1,6 +1,6 @@ - + - + @@ -9,7 +9,7 @@ PowerToys.MouseJump.Common PowerToys MouseJump.Common Library - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) false false diff --git a/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj b/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj index 6e17e4142b..a51535eaab 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj +++ b/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj @@ -1,7 +1,7 @@ - + - - + + @@ -9,7 +9,7 @@ PowerToys.MouseJumpUI PowerToys MouseJumpUI WinExe - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) false false diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj b/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj index 0823564a44..09dfcb5861 100644 --- a/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj +++ b/src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj @@ -1,6 +1,7 @@ - + + 15.0 {eae14c0e-7a6b-45da-9080-a7d8c077ba6e} @@ -8,8 +9,7 @@ MousePointerCrosshairs MousePointerCrosshairs - - + DynamicLibrary true @@ -33,7 +33,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ PowerToys.MousePointerCrosshairs @@ -80,7 +80,7 @@ - ..\..\..\;..\..\..\modules;..\..\..\common\Telemetry;%(AdditionalIncludeDirectories) + $(RepoRoot)src\;..\..\..\modules;$(RepoRoot)src\common\Telemetry;%(AdditionalIncludeDirectories) @@ -101,10 +101,10 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -113,13 +113,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MousePointerCrosshairs/packages.config b/src/modules/MouseUtils/MousePointerCrosshairs/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/MouseUtils/MousePointerCrosshairs/packages.config +++ b/src/modules/MouseUtils/MousePointerCrosshairs/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj b/src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj index 911de3e8e1..33d722718f 100644 --- a/src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj +++ b/src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj @@ -1,6 +1,6 @@ - + - + {4E0AE3A4-2EE0-44D7-A2D0-8769977254A1} diff --git a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj index b20df28956..9c00294792 100644 --- a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj +++ b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj @@ -1,7 +1,7 @@ - + - - + + WinExe @@ -11,7 +11,7 @@ PowerToys.MouseWithoutBordersHelper true true - ..\..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) true ..\Logo.ico true diff --git a/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj b/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj index b677a3d937..065a67a1b9 100644 --- a/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj +++ b/src/modules/MouseWithoutBorders/App/MouseWithoutBorders.csproj @@ -1,7 +1,7 @@ - + - - + + WinExe @@ -11,7 +11,7 @@ PowerToys.MouseWithoutBorders true true - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) true Logo.ico true diff --git a/src/modules/MouseWithoutBorders/App/Service/MouseWithoutBordersService.csproj b/src/modules/MouseWithoutBorders/App/Service/MouseWithoutBordersService.csproj index ba4f059305..524a313f31 100644 --- a/src/modules/MouseWithoutBorders/App/Service/MouseWithoutBordersService.csproj +++ b/src/modules/MouseWithoutBorders/App/Service/MouseWithoutBordersService.csproj @@ -1,7 +1,7 @@ - + - - + + true @@ -12,7 +12,7 @@ PowerToys.MouseWithoutBordersService true true - ..\..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) true ..\Logo.ico true diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/MouseWithoutBorders.UnitTests.csproj b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/MouseWithoutBorders.UnitTests.csproj index ad95d818a1..6020214279 100644 --- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/MouseWithoutBorders.UnitTests.csproj +++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/MouseWithoutBorders.UnitTests.csproj @@ -1,7 +1,7 @@ - + - - + + enable diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj index fb1bf3c06b..895430df0b 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/NewPlus.ShellExtension.win10.vcxproj @@ -1,8 +1,9 @@ - + + - + 17.0 @@ -11,7 +12,6 @@ NewPlusShellExtensionWin10 10.0.26100.0 - DynamicLibrary @@ -34,7 +34,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.NewPlus.ShellExtension.win10 @@ -46,7 +46,7 @@ _DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use - ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory) + $(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory) false stdcpplatest @@ -67,7 +67,7 @@ NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use - ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory) + $(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory) false stdcpplatest @@ -125,13 +125,13 @@ - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} @@ -140,17 +140,17 @@ - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config +++ b/src/modules/NewPlus/NewShellExtensionContextMenu.win10/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj index b0da79f1c6..f899012bf2 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/NewShellExtensionContextMenu.vcxproj @@ -1,8 +1,9 @@ - + + - + 17.0 @@ -12,7 +13,6 @@ 10.0.20348.0 NewPlus.ShellExtension - DynamicLibrary true @@ -37,14 +37,14 @@ .dll - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.NewPlus.ShellExtension $(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\ - ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.NewPlus.ShellExtension $(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\ @@ -59,7 +59,7 @@ Use pch.h stdcpplatest - ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) Windows @@ -90,7 +90,7 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv Use pch.h stdcpplatest - ..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + $(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) Windows @@ -167,18 +167,21 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + @@ -228,15 +231,15 @@ MakeAppx.exe pack /d . /p $(OutDir)NewPlusPackage.msix /nv - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config b/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config +++ b/src/modules/NewPlus/NewShellExtensionContextMenu/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/PowerOCR/PowerOCR-UITests/PowerOCR.UITests.csproj b/src/modules/PowerOCR/PowerOCR-UITests/PowerOCR.UITests.csproj index c2a51bb332..572cf5ab69 100644 --- a/src/modules/PowerOCR/PowerOCR-UITests/PowerOCR.UITests.csproj +++ b/src/modules/PowerOCR/PowerOCR-UITests/PowerOCR.UITests.csproj @@ -1,5 +1,5 @@ - + PowerOCR.UITests enable @@ -9,7 +9,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\tests\PowerOCR.UITests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\PowerOCR.UITests\ diff --git a/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj b/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj index 1c95308d25..8586acaa2e 100644 --- a/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj +++ b/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj @@ -1,11 +1,11 @@ - + - - + + PowerToys.PowerOCR - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) false false true diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj index 95dcaebc1c..374dc951de 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj @@ -1,8 +1,9 @@ - + + - + true @@ -14,7 +15,6 @@ Win32Proj ShortcutGuide - Application @@ -47,11 +47,11 @@ PowerToys.$(MSBuildProjectName) - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ - ;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\;%(AdditionalIncludeDirectories) + ;$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;$(RepoRoot)src\;..\;%(AdditionalIncludeDirectories) ole32.lib;Shell32.lib;OleAut32.lib;Dbghelp.lib;Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) @@ -149,19 +149,19 @@ - + {caba8dfb-823b-4bf2-93ac-3f31984150d9} - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} - + {98537082-0fdb-40de-abd8-0dc5a4269bab} @@ -179,15 +179,15 @@ - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/packages.config b/src/modules/ShortcutGuide/ShortcutGuide/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/packages.config +++ b/src/modules/ShortcutGuide/ShortcutGuide/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj b/src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj index f835138a27..74bc59de0c 100644 --- a/src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj +++ b/src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj @@ -1,7 +1,7 @@ - + - - + + enable diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj b/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj index 501f6b4f0c..b88e6a9f9c 100644 --- a/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/WorkspacesCsharpLibrary.csproj @@ -1,7 +1,7 @@ - + - - + + PowerToys.WorkspacesCsharpLibrary @@ -13,7 +13,7 @@ false false true - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) PowerToys.WorkspacesCsharpLibrary diff --git a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj index 71e91979bd..9fa94a892b 100644 --- a/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj +++ b/src/modules/Workspaces/WorkspacesEditor/WorkspacesEditor.csproj @@ -1,7 +1,7 @@ - + - - + + PowerToys.WorkspacesEditor @@ -13,7 +13,7 @@ false false true - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) diff --git a/src/modules/Workspaces/WorkspacesEditorUITest/Workspaces.Editor.UITests.csproj b/src/modules/Workspaces/WorkspacesEditorUITest/Workspaces.Editor.UITests.csproj index f0d2181522..f80bc502d5 100644 --- a/src/modules/Workspaces/WorkspacesEditorUITest/Workspaces.Editor.UITests.csproj +++ b/src/modules/Workspaces/WorkspacesEditorUITest/Workspaces.Editor.UITests.csproj @@ -1,5 +1,5 @@ - + latest @@ -13,7 +13,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\tests\Workspaces.UITests\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\Workspaces.UITests\ diff --git a/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj b/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj index 8faf362704..f32e679128 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj +++ b/src/modules/Workspaces/WorkspacesLauncher/WorkspacesLauncher.vcxproj @@ -2,7 +2,7 @@ - + @@ -13,7 +13,6 @@ false true stdcpplatest - /await %(AdditionalOptions) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -174,15 +173,15 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLauncher/packages.config b/src/modules/Workspaces/WorkspacesLauncher/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/packages.config +++ b/src/modules/Workspaces/WorkspacesLauncher/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj b/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj index f55d30205f..0f9cc3c1c0 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj +++ b/src/modules/Workspaces/WorkspacesLauncherUI/WorkspacesLauncherUI.csproj @@ -1,7 +1,7 @@ - + - - + + PowerToys.WorkspacesLauncherUI @@ -13,7 +13,7 @@ false false true - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) diff --git a/src/modules/Workspaces/WorkspacesLib.UnitTests/WorkspacesLibUnitTests.vcxproj b/src/modules/Workspaces/WorkspacesLib.UnitTests/WorkspacesLibUnitTests.vcxproj index 1c7098f3b3..2b762fac88 100644 --- a/src/modules/Workspaces/WorkspacesLib.UnitTests/WorkspacesLibUnitTests.vcxproj +++ b/src/modules/Workspaces/WorkspacesLib.UnitTests/WorkspacesLibUnitTests.vcxproj @@ -1,13 +1,13 @@ - + + {A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C} Win32Proj WorkspacesLibUnitTests Workspaces.Lib.UnitTests - @@ -24,16 +24,16 @@ - ..\..\..\..\$(Platform)\$(Configuration)\tests\Workspaces\ + $(RepoRoot)$(Platform)\$(Configuration)\tests\Workspaces\ - ..\;..\WorkspacesLib\;$(SolutionDir)src\;$(SolutionDir)src\common;$(SolutionDir)src\common\Telemetry;..\..\;..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + ..\;..\WorkspacesLib\;$(RepoRoot)src\;$(RepoRoot)src\common;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WIN32;%(PreprocessorDefinitions) true - $(VCInstallDir)UnitTest\\lib;$(SolutionDir)$(Platform)\\$(Configuration)\\;%(AdditionalLibraryDirectories) + $(VCInstallDir)UnitTest\\lib;$(RepoRoot)$(Platform)\\$(Configuration)\\;%(AdditionalLibraryDirectories) propsys.lib;comctl32.lib;pathcch.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;Pathcch.lib;%(AdditionalDependencies) @@ -55,7 +55,7 @@ - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -64,13 +64,13 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib.UnitTests/packages.config b/src/modules/Workspaces/WorkspacesLib.UnitTests/packages.config index 2c5d71ae86..7d3cbd2b91 100644 --- a/src/modules/Workspaces/WorkspacesLib.UnitTests/packages.config +++ b/src/modules/Workspaces/WorkspacesLib.UnitTests/packages.config @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj index e968036d59..5c300ae8a0 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj @@ -1,6 +1,7 @@ - + + 16.0 {b31fcc55-b5a4-4ea7-b414-2dceae6af332} @@ -8,7 +9,6 @@ WorkspacesLib WorkspacesLib - StaticLibrary @@ -23,12 +23,12 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ _LIB;%(PreprocessorDefinitions) - ..\;$(SolutionDir)src\;$(SolutionDir)src\common;$(SolutionDir)src\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + ..\;$(RepoRoot)src\;$(RepoRoot)src\common;$(RepoRoot)src\common\Telemetry;..\..\;$(RepoRoot)src\;%(AdditionalIncludeDirectories) @@ -68,10 +68,10 @@ - + {f055103b-f80b-4d0c-bf48-057c55620033} - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} @@ -79,17 +79,17 @@ - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/packages.config b/src/modules/Workspaces/WorkspacesLib/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/Workspaces/WorkspacesLib/packages.config +++ b/src/modules/Workspaces/WorkspacesLib/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj index 00f0633f11..562ec78471 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/WorkspacesSnapshotTool.vcxproj @@ -2,7 +2,7 @@ - + @@ -13,7 +13,6 @@ false true stdcpplatest - /await %(AdditionalOptions) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -166,15 +165,15 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/packages.config b/src/modules/Workspaces/WorkspacesSnapshotTool/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/packages.config +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj b/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj index 85d2c021ba..b56b3ff86f 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj +++ b/src/modules/Workspaces/WorkspacesWindowArranger/WorkspacesWindowArranger.vcxproj @@ -2,7 +2,7 @@ - + @@ -13,7 +13,6 @@ false true stdcpplatest - /await %(AdditionalOptions) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -165,15 +164,15 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/packages.config b/src/modules/Workspaces/WorkspacesWindowArranger/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/packages.config +++ b/src/modules/Workspaces/WorkspacesWindowArranger/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp index fecdca4356..b3b0ed373f 100644 --- a/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp +++ b/src/modules/ZoomIt/ZoomIt/AudioSampleGenerator.cpp @@ -311,6 +311,14 @@ void AudioSampleGenerator::Stop() // Stop the audio graph - no more quantum callbacks will run m_audioGraph.Stop(); + // Close the microphone input node to release the device so Windows no longer + // reports the microphone as in use by ZoomIt. + if (m_audioInputNode) + { + m_audioInputNode.Close(); + m_audioInputNode = nullptr; + } + // Mark as stopped m_started.store(false); diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc index 6fe01af2bb..d3c5210744 100644 --- a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc @@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN DEFPUSHBUTTON "OK",IDOK,186,306,50,14 PUSHBUTTON "Cancel",IDCANCEL,243,306,50,14 - LTEXT "ZoomIt v10.0",IDC_VERSION,42,7,73,10 + LTEXT "ZoomIt v10.1",IDC_VERSION,42,7,73,10 LTEXT "Copyright \251 2006-2026 Mark Russinovich",IDC_COPYRIGHT,42,17,251,8 CONTROL "Sysinternals - www.sysinternals.com",IDC_LINK, "SysLink",WS_TABSTOP,42,26,150,9 diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj index 6dbacd0016..7055474f1f 100644 --- a/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.vcxproj @@ -1,6 +1,6 @@ - + - + Debug @@ -371,11 +371,11 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - + diff --git a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp index 68731f1a98..b3f736fd43 100644 --- a/src/modules/ZoomIt/ZoomIt/Zoomit.cpp +++ b/src/modules/ZoomIt/ZoomIt/Zoomit.cpp @@ -5140,7 +5140,7 @@ bool IsPenInverted( WPARAM wParam ) // Captures the specified screen using the capture APIs // //---------------------------------------------------------------------------- -std::future> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat) +wil::task> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat) { auto d3dDevice = GetDXGIInterfaceFromObject(device); winrt::com_ptr d3dContext; @@ -5176,9 +5176,7 @@ std::future> CaptureScreenshotAsync(winrt::IDire framePool.Close(); auto texture = GetDXGIInterfaceFromObject(frame.Surface()); - auto result = util::CopyD3DTexture(d3dDevice, texture, true); - - co_return result; + co_return util::CopyD3DTexture(d3dDevice, texture, true); } //---------------------------------------------------------------------------- @@ -5205,10 +5203,7 @@ winrt::com_ptrCaptureScreenshot(winrt::DirectXPixelFormat const auto item = util::CreateCaptureItemForMonitor(hMon); - auto capture = CaptureScreenshotAsync(device, item, pixelFormat); - capture.wait(); - - return capture.get(); + return CaptureScreenshotAsync(device, item, pixelFormat).get(); } diff --git a/src/modules/ZoomIt/ZoomIt/packages.config b/src/modules/ZoomIt/ZoomIt/packages.config index 691158d1b2..51fbe043d6 100644 --- a/src/modules/ZoomIt/ZoomIt/packages.config +++ b/src/modules/ZoomIt/ZoomIt/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomIt/pch.h b/src/modules/ZoomIt/ZoomIt/pch.h index 12b0d326b2..42d134704d 100644 --- a/src/modules/ZoomIt/ZoomIt/pch.h +++ b/src/modules/ZoomIt/ZoomIt/pch.h @@ -69,6 +69,7 @@ // WIL #include #include +#include // DirectX #include diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj index aa53283607..4c30a44134 100644 --- a/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/ZoomItSettingsInterop.vcxproj @@ -1,6 +1,7 @@ - + + true true @@ -16,7 +17,6 @@ Windows Store 10.0 - DynamicLibrary @@ -46,7 +46,7 @@ PowerToys.ZoomItSettingsInterop - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ @@ -105,10 +105,10 @@ - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} @@ -116,17 +116,17 @@ - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config b/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config +++ b/src/modules/ZoomIt/ZoomItSettingsInterop/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj index f3d4b6f75d..8b24f90bf9 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj +++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj @@ -1,10 +1,11 @@ + - + - + @@ -13,7 +14,6 @@ false true stdcpplatest - /await %(AdditionalOptions) _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -64,7 +64,6 @@ Unicode Spectre - true true @@ -85,7 +84,7 @@ PowerToys.$(MSBuildProjectName) - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ @@ -93,7 +92,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories) + ./../;$(RepoRoot)src\common\Telemetry;$(RepoRoot)src\common;$(RepoRoot)src\;./;%(AdditionalIncludeDirectories) Windows @@ -109,7 +108,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories) + ./../;$(RepoRoot)src\common\Telemetry;$(RepoRoot)src\common;$(RepoRoot)src\;./;%(AdditionalIncludeDirectories) Windows @@ -158,19 +157,19 @@ - + {caba8dfb-823b-4bf2-93ac-3f31984150d9} - + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - + {6955446d-23f7-4023-9bb3-8657f904af99} - + {1d5be09d-78c0-4fd7-af00-ae7c1af7c525} - + {8f021b46-362b-485c-bfba-ccf83e820cbd} @@ -185,17 +184,17 @@ - + - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + \ No newline at end of file diff --git a/src/modules/alwaysontop/AlwaysOnTop/packages.config b/src/modules/alwaysontop/AlwaysOnTop/packages.config index ff4b059648..d3882436a5 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/packages.config +++ b/src/modules/alwaysontop/AlwaysOnTop/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj b/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj index 52588938e4..75be8c685d 100644 --- a/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj +++ b/src/modules/awake/Awake.ModuleServices/Awake.ModuleServices.csproj @@ -1,7 +1,7 @@ - - + + enable diff --git a/src/modules/awake/Awake/Awake.csproj b/src/modules/awake/Awake/Awake.csproj index 22b53e481f..46d27c4b70 100644 --- a/src/modules/awake/Awake/Awake.csproj +++ b/src/modules/awake/Awake/Awake.csproj @@ -1,11 +1,11 @@ - + - - + + WinExe - ..\..\..\..\$(Platform)\$(Configuration) + $(RepoRoot)$(Platform)\$(Configuration) enable false false diff --git a/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj b/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj index cc5bdfeb26..8257a285eb 100644 --- a/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj +++ b/src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj @@ -1,7 +1,8 @@ - + + true true @@ -39,7 +40,6 @@ - DynamicLibrary @@ -71,7 +71,7 @@ CmdPalKeyboardService - ..\..\..\..\$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\ @@ -130,16 +130,16 @@ - + - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + diff --git a/src/modules/cmdpal/CmdPalKeyboardService/packages.config b/src/modules/cmdpal/CmdPalKeyboardService/packages.config index 09bfc449e2..f32f48b009 100644 --- a/src/modules/cmdpal/CmdPalKeyboardService/packages.config +++ b/src/modules/cmdpal/CmdPalKeyboardService/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/modules/cmdpal/CommandPalette.slnf b/src/modules/cmdpal/CommandPalette.slnf index c6ccbb7338..decc56060f 100644 --- a/src/modules/cmdpal/CommandPalette.slnf +++ b/src/modules/cmdpal/CommandPalette.slnf @@ -3,6 +3,7 @@ "path": "..\\..\\..\\PowerToys.slnx", "projects": [ "src\\common\\CalculatorEngineCommon\\CalculatorEngineCommon.vcxproj", + "src\\common\\Common.UI.Controls\\Common.UI.Controls.csproj", "src\\common\\ManagedCommon\\ManagedCommon.csproj", "src\\common\\ManagedCsWin32\\ManagedCsWin32.csproj", "src\\common\\ManagedTelemetry\\Telemetry\\ManagedTelemetry.csproj", diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs new file mode 100644 index 0000000000..2d23db30b3 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/AppPackagingFlavor.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common; + +/// +/// Represents the packaging flavor of the application. +/// +public enum AppPackagingFlavor +{ + /// + /// Application is packaged as a Windows MSIX package. + /// + Packaged, + + /// + /// Application is running unpackaged (native executable). + /// + Unpackaged, + + /// + /// Application is running as unpackaged portable (self-contained distribution). + /// + UnpackagedPortable, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/IPrecomputedListItem.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/IPrecomputedListItem.cs new file mode 100644 index 0000000000..2847ee7b12 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/IPrecomputedListItem.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CmdPal.Core.Common.Text; + +namespace Microsoft.CmdPal.Core.Common.Helpers; + +/// +/// Represents an item that can provide precomputed fuzzy matching targets for its title and subtitle. +/// +public interface IPrecomputedListItem +{ + /// + /// Gets the fuzzy matching target for the item's title. + /// + /// The precomputed fuzzy matcher used to build the target. + /// The fuzzy target for the title. + FuzzyTarget GetTitleTarget(IPrecomputedFuzzyMatcher matcher); + + /// + /// Gets the fuzzy matching target for the item's subtitle. + /// + /// The precomputed fuzzy matcher used to build the target. + /// The fuzzy target for the subtitle. + FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/InternalListHelpers.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/InternalListHelpers.cs new file mode 100644 index 0000000000..60d841aaf8 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/InternalListHelpers.cs @@ -0,0 +1,142 @@ +// 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.Buffers; +using System.Diagnostics; +using Microsoft.CmdPal.Core.Common.Text; + +namespace Microsoft.CmdPal.Core.Common.Helpers; + +public static partial class InternalListHelpers +{ + public static RoScored[] FilterListWithScores( + IEnumerable? items, + in FuzzyQuery query, + in ScoringFunction scoreFunction) + { + if (items == null) + { + return []; + } + + // Try to get initial capacity hint + var initialCapacity = items switch + { + ICollection col => col.Count, + IReadOnlyCollection rc => rc.Count, + _ => 64, + }; + + var buffer = ArrayPool>.Shared.Rent(initialCapacity); + var count = 0; + + try + { + foreach (var item in items) + { + var score = scoreFunction(in query, item); + if (score <= 0) + { + continue; + } + + if (count == buffer.Length) + { + GrowBuffer(ref buffer, count); + } + + buffer[count++] = new RoScored(item, score); + } + + Array.Sort(buffer, 0, count, default(RoScoredDescendingComparer)); + var result = GC.AllocateUninitializedArray>(count); + buffer.AsSpan(0, count).CopyTo(result); + return result; + } + finally + { + ArrayPool>.Shared.Return(buffer); + } + } + + private static void GrowBuffer(ref RoScored[] buffer, int count) + { + var newBuffer = ArrayPool>.Shared.Rent(buffer.Length * 2); + buffer.AsSpan(0, count).CopyTo(newBuffer); + ArrayPool>.Shared.Return(buffer); + buffer = newBuffer; + } + + public static T[] FilterList(IEnumerable items, in FuzzyQuery query, ScoringFunction scoreFunction) + { + // Try to get initial capacity hint + var initialCapacity = items switch + { + ICollection col => col.Count, + IReadOnlyCollection rc => rc.Count, + _ => 64, + }; + + var buffer = ArrayPool>.Shared.Rent(initialCapacity); + var count = 0; + + try + { + foreach (var item in items) + { + var score = scoreFunction(in query, item); + if (score <= 0) + { + continue; + } + + if (count == buffer.Length) + { + GrowBuffer(ref buffer, count); + } + + buffer[count++] = new RoScored(item, score); + } + + Array.Sort(buffer, 0, count, default(RoScoredDescendingComparer)); + + var result = GC.AllocateUninitializedArray(count); + for (var i = 0; i < count; i++) + { + result[i] = buffer[i].Item; + } + + return result; + } + finally + { + ArrayPool>.Shared.Return(buffer); + } + } + + private readonly struct RoScoredDescendingComparer : IComparer> + { + public int Compare(RoScored x, RoScored y) => y.Score.CompareTo(x.Score); + } +} + +public delegate int ScoringFunction(in FuzzyQuery query, T item); + +[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] +public readonly struct RoScored +{ + public readonly int Score; + public readonly T Item; + + public RoScored(T item, int score) + { + Score = score; + Item = item; + } + + private string GetDebuggerDisplay() + { + return "Score = " + Score + ", Item = " + Item; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs new file mode 100644 index 0000000000..ad280b6c52 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Helpers/VersionHelper.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using Windows.ApplicationModel; + +namespace Microsoft.CmdPal.Core.Common.Helpers; + +/// +/// Helper class for retrieving application version information safely. +/// +internal static class VersionHelper +{ + /// + /// Gets the application version as a string in the format "Major.Minor.Build.Revision". + /// Falls back to assembly version if packaged version is unavailable, and returns a default value if both fail. + /// + /// The application version string, or a fallback value if retrieval fails. + public static string GetAppVersionSafe() + { + if (TryGetPackagedVersion(out var version)) + { + return version; + } + + if (TryGetAssemblyVersion(out version)) + { + return version; + } + + return "?"; + } + + /// + /// Attempts to retrieve the application version from the package manifest. + /// + /// The version string if successful, or an empty string if unsuccessful. + /// True if the version was retrieved successfully; otherwise, false. + private static bool TryGetPackagedVersion(out string version) + { + version = string.Empty; + try + { + // Package.Current throws InvalidOperationException if the app is not packaged + var v = Package.Current.Id.Version; + version = $"{v.Major}.{v.Minor}.{v.Build}.{v.Revision}"; + return true; + } + catch (InvalidOperationException) + { + return false; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to get version from the package", ex); + return false; + } + } + + /// + /// Attempts to retrieve the application version from the executable file. + /// + /// The version string if successful, or an empty string if unsuccessful. + /// True if the version was retrieved successfully; otherwise, false. + private static bool TryGetAssemblyVersion(out string version) + { + version = string.Empty; + try + { + var processPath = Environment.ProcessPath; + if (string.IsNullOrEmpty(processPath)) + { + return false; + } + + var info = FileVersionInfo.GetVersionInfo(processPath); + version = $"{info.FileMajorPart}.{info.FileMinorPart}.{info.FileBuildPart}.{info.FilePrivatePart}"; + return true; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to get version from the executable", ex); + return false; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs new file mode 100644 index 0000000000..0919c38946 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/ApplicationInfoService.cs @@ -0,0 +1,125 @@ +// 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.Globalization; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.ApplicationModel; + +namespace Microsoft.CmdPal.Core.Common.Services; + +/// +/// Implementation of IApplicationInfoService providing application-wide information. +/// +public sealed class ApplicationInfoService : IApplicationInfoService +{ + private readonly Lazy _configDirectory = new(() => Utilities.BaseSettingsPath("Microsoft.CmdPal")); + private readonly Lazy _isElevated; + private readonly Lazy _logDirectory; + private readonly Lazy _packagingFlavor; + private Func? _getLogDirectory; + + /// + /// Initializes a new instance of the class. + /// The log directory delegate can be set later via . + /// + public ApplicationInfoService() + { + _packagingFlavor = new Lazy(DeterminePackagingFlavor); + _isElevated = new Lazy(DetermineElevationStatus); + _logDirectory = new Lazy(() => _getLogDirectory?.Invoke() ?? "Not available"); + } + + /// + /// Initializes a new instance of the class with an optional log directory provider. + /// + /// Optional delegate to retrieve the log directory path. If not provided, the log directory will be unavailable. + public ApplicationInfoService(Func? getLogDirectory) + : this() + { + _getLogDirectory = getLogDirectory; + } + + /// + /// Sets the log directory delegate to be used for retrieving the log directory path. + /// This allows deferred initialization of the logger path. + /// + /// Delegate to retrieve the log directory path. + public void SetLogDirectory(Func getLogDirectory) + { + ArgumentNullException.ThrowIfNull(getLogDirectory); + _getLogDirectory = getLogDirectory; + } + + public string AppVersion => VersionHelper.GetAppVersionSafe(); + + public AppPackagingFlavor PackagingFlavor => _packagingFlavor.Value; + + public string LogDirectory => _logDirectory.Value; + + public string ConfigDirectory => _configDirectory.Value; + + public bool IsElevated => _isElevated.Value; + + public string GetApplicationInfoSummary() + { + return $""" + Application: + App version: {AppVersion} + Packaging flavor: {PackagingFlavor} + Is elevated: {(IsElevated ? "yes" : "no")} + + Environment: + OS version: {RuntimeInformation.OSDescription} + OS architecture: {RuntimeInformation.OSArchitecture} + Runtime identifier: {RuntimeInformation.RuntimeIdentifier} + Framework: {RuntimeInformation.FrameworkDescription} + Process architecture: {RuntimeInformation.ProcessArchitecture} + Culture: {CultureInfo.CurrentCulture.Name} + UI culture: {CultureInfo.CurrentUICulture.Name} + + Paths: + Log directory: {LogDirectory} + Config directory: {ConfigDirectory} + """; + } + + private static AppPackagingFlavor DeterminePackagingFlavor() + { + // Try to determine if running as packaged + try + { + // If this doesn't throw, we're packaged + _ = Package.Current.Id.Version; + return AppPackagingFlavor.Packaged; + } + catch (InvalidOperationException) + { + // Not packaged, check if portable + // For now, we don't support portable yet, so return Unpackaged + // In the future, check for a marker file or environment variable + return AppPackagingFlavor.Unpackaged; + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to determine packaging flavor", ex); + return AppPackagingFlavor.Unpackaged; + } + } + + private static bool DetermineElevationStatus() + { + try + { + var isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); + return isElevated; + } + catch (Exception) + { + return false; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs new file mode 100644 index 0000000000..f687333d20 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/IApplicationInfoService.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Services; + +/// +/// Provides access to application-wide information such as version, packaging flavor, and directory paths. +/// +public interface IApplicationInfoService +{ + /// + /// Gets the application version as a string in the format "Major.Minor.Build.Revision". + /// + string AppVersion { get; } + + /// + /// Gets the packaging flavor of the application. + /// + AppPackagingFlavor PackagingFlavor { get; } + + /// + /// Gets the directory path where application logs are stored. + /// + string LogDirectory { get; } + + /// + /// Gets the directory path where application configuration files are stored. + /// + string ConfigDirectory { get; } + + /// + /// Gets a value indicating whether the application is running with administrator privileges. + /// + bool IsElevated { get; } + + /// + /// Gets a formatted summary of application information suitable for logging. + /// + /// A formatted string containing application information. + string GetApplicationInfoSummary(); + + /// + /// Sets the log directory delegate to be used for retrieving the log directory path. + /// This allows deferred initialization of the logger path. + /// + /// Delegate to retrieve the log directory path. + void SetLogDirectory(Func getLogDirectory); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs index 0c966f2593..c98740aaf5 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Services/Reports/ErrorReportBuilder.cs @@ -2,20 +2,27 @@ // 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.Globalization; using System.Runtime.InteropServices; -using System.Security.Principal; using Microsoft.CmdPal.Core.Common.Services.Sanitizer; -using Windows.ApplicationModel; namespace Microsoft.CmdPal.Core.Common.Services.Reports; public sealed class ErrorReportBuilder : IErrorReportBuilder { private readonly ErrorReportSanitizer _sanitizer = new(); + private readonly IApplicationInfoService _appInfoService; private static string Preamble => Properties.Resources.ErrorReport_Global_Preamble; + /// + /// Initializes a new instance of the class. + /// + /// Optional application info service. If not provided, a default instance is created. + public ErrorReportBuilder(IApplicationInfoService? appInfoService = null) + { + _appInfoService = appInfoService ?? new ApplicationInfoService(null); + } + public string BuildReport(Exception exception, string context, bool redactPii = true) { ArgumentNullException.ThrowIfNull(exception); @@ -24,6 +31,9 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder var sanitizedMessage = redactPii ? _sanitizer.Sanitize(exceptionMessage) : exceptionMessage; var sanitizedFormattedException = redactPii ? _sanitizer.Sanitize(exception.ToString()) : exception.ToString(); + var applicationInfoSummary = GetAppInfoSafe(); + var applicationInfoSummarySanitized = redactPii ? _sanitizer.Sanitize(applicationInfoSummary) : applicationInfoSummary; + // Note: // - do not localize technical part of the report, we need to ensure it can be read by developers // - keep timestamp format should be consistent with the log (makes it easier to search) @@ -38,18 +48,7 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder HRESULT: 0x{exception.HResult:X8} ({exception.HResult}) Context: {context ?? "N/A"} - Application: - App version: {GetAppVersionSafe()} - Is elevated: {GetElevationStatus()} - - Environment: - OS version: {RuntimeInformation.OSDescription} - OS architecture: {RuntimeInformation.OSArchitecture} - Runtime identifier: {RuntimeInformation.RuntimeIdentifier} - Framework: {RuntimeInformation.FrameworkDescription} - Process architecture: {RuntimeInformation.ProcessArchitecture} - Culture: {CultureInfo.CurrentCulture.Name} - UI culture: {CultureInfo.CurrentUICulture.Name} + {applicationInfoSummarySanitized} Stack Trace: {exception.StackTrace} @@ -66,31 +65,17 @@ public sealed class ErrorReportBuilder : IErrorReportBuilder """; } - private static string GetElevationStatus() + private string? GetAppInfoSafe() { - // Note: do not localize technical part of the report, we need to ensure it can be read by developers try { - var isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); - return isElevated ? "yes" : "no"; + return _appInfoService.GetApplicationInfoSummary(); } - catch (Exception) + catch (Exception ex) { - return "Failed to determine elevation status"; - } - } - - private static string GetAppVersionSafe() - { - // Note: do not localize technical part of the report, we need to ensure it can be read by developers - try - { - var version = Package.Current.Id.Version; - return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"; - } - catch (Exception) - { - return "Failed to retrieve app version"; + // Getting application info should never throw, but if it does, we don't want it to prevent the report from being generated + var message = CoalesceExceptionMessage(ex); + return $"Failed to get application info summary: {message}"; } } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/BloomFilter.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/BloomFilter.cs new file mode 100644 index 0000000000..59255a1bae --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/BloomFilter.cs @@ -0,0 +1,40 @@ +// 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.Runtime.CompilerServices; + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class BloomFilter : IBloomFilter +{ + public ulong Compute(string input) + { + ulong bloom = 0; + + foreach (var ch in input) + { + if (SymbolClassifier.Classify(ch) == SymbolKind.WordSeparator) + { + continue; + } + + var h = (uint)ch * 0x45d9f3b; + bloom |= 1UL << (int)(h & 31); + bloom |= 1UL << (int)(((h >> 16) & 31) + 32); + + if (bloom == ulong.MaxValue) + { + break; + } + } + + return bloom; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MightContain(ulong candidateBloom, ulong queryBloom) + { + return (candidateBloom & queryBloom) == queryBloom; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyMatcherProvider.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyMatcherProvider.cs new file mode 100644 index 0000000000..80c5fa9ace --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyMatcherProvider.cs @@ -0,0 +1,52 @@ +// 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.Globalization; + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class FuzzyMatcherProvider : IFuzzyMatcherProvider +{ + private readonly IBloomFilter _bloomCalculator = new BloomFilter(); + private readonly IStringFolder _normalizer = new StringFolder(); + + private IPrecomputedFuzzyMatcher _current; + + public FuzzyMatcherProvider(PrecomputedFuzzyMatcherOptions core, PinyinFuzzyMatcherOptions? pinyin = null) + { + _current = CreateMatcher(core, pinyin); + } + + public IPrecomputedFuzzyMatcher Current => Volatile.Read(ref _current); + + public void UpdateSettings(PrecomputedFuzzyMatcherOptions core, PinyinFuzzyMatcherOptions? pinyin = null) + { + Volatile.Write(ref _current, CreateMatcher(core, pinyin)); + } + + private IPrecomputedFuzzyMatcher CreateMatcher(PrecomputedFuzzyMatcherOptions core, PinyinFuzzyMatcherOptions? pinyin) + { + return pinyin is null || !IsPinyinEnabled(pinyin) + ? new PrecomputedFuzzyMatcher(core, _normalizer, _bloomCalculator) + : new PrecomputedFuzzyMatcherWithPinyin(core, pinyin, _normalizer, _bloomCalculator); + } + + private static bool IsPinyinEnabled(PinyinFuzzyMatcherOptions o) + { + return o.Mode switch + { + PinyinMode.Off => false, + PinyinMode.On => true, + PinyinMode.AutoSimplifiedChineseUi => IsSimplifiedChineseUi(), + _ => false, + }; + } + + private static bool IsSimplifiedChineseUi() + { + var culture = CultureInfo.CurrentUICulture; + return culture.Name.StartsWith("zh-CN", StringComparison.OrdinalIgnoreCase) + || culture.Name.StartsWith("zh-Hans", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyQuery.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyQuery.cs new file mode 100644 index 0000000000..80de31bd7a --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyQuery.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public readonly struct FuzzyQuery +{ + public readonly string Original; + + public readonly string Folded; + + public readonly ulong Bloom; + + public readonly int EffectiveLength; + + public readonly bool IsAllLowercaseAsciiOrNonLetter; + + public readonly string? SecondaryOriginal; + + public readonly string? SecondaryFolded; + + public readonly ulong SecondaryBloom; + + public readonly int SecondaryEffectiveLength; + + public readonly bool SecondaryIsAllLowercaseAsciiOrNonLetter; + + public int Length => Folded.Length; + + public bool HasSecondary => SecondaryFolded is not null; + + public ReadOnlySpan OriginalSpan => Original.AsSpan(); + + public ReadOnlySpan FoldedSpan => Folded.AsSpan(); + + public ReadOnlySpan SecondaryOriginalSpan => SecondaryOriginal.AsSpan(); + + public ReadOnlySpan SecondaryFoldedSpan => SecondaryFolded.AsSpan(); + + public FuzzyQuery( + string original, + string folded, + ulong bloom, + int effectiveLength, + bool isAllLowercaseAsciiOrNonLetter, + string? secondaryOriginal = null, + string? secondaryFolded = null, + ulong secondaryBloom = 0, + int secondaryEffectiveLength = 0, + bool secondaryIsAllLowercaseAsciiOrNonLetter = true) + { + Original = original; + Folded = folded; + Bloom = bloom; + EffectiveLength = effectiveLength; + IsAllLowercaseAsciiOrNonLetter = isAllLowercaseAsciiOrNonLetter; + + SecondaryOriginal = secondaryOriginal; + SecondaryFolded = secondaryFolded; + SecondaryBloom = secondaryBloom; + SecondaryEffectiveLength = secondaryEffectiveLength; + SecondaryIsAllLowercaseAsciiOrNonLetter = secondaryIsAllLowercaseAsciiOrNonLetter; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTarget.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTarget.cs new file mode 100644 index 0000000000..b0c2927f20 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTarget.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public readonly struct FuzzyTarget +{ + public readonly string Original; + public readonly string Folded; + public readonly ulong Bloom; + + public readonly string? SecondaryOriginal; + public readonly string? SecondaryFolded; + public readonly ulong SecondaryBloom; + + public int Length => Folded.Length; + + public bool HasSecondary => SecondaryFolded is not null; + + public int SecondaryLength => SecondaryFolded?.Length ?? 0; + + public ReadOnlySpan OriginalSpan => Original.AsSpan(); + + public ReadOnlySpan FoldedSpan => Folded.AsSpan(); + + public ReadOnlySpan SecondaryOriginalSpan => SecondaryOriginal.AsSpan(); + + public ReadOnlySpan SecondaryFoldedSpan => SecondaryFolded.AsSpan(); + + public FuzzyTarget( + string original, + string folded, + ulong bloom, + string? secondaryOriginal = null, + string? secondaryFolded = null, + ulong secondaryBloom = 0) + { + Original = original; + Folded = folded; + Bloom = bloom; + SecondaryOriginal = secondaryOriginal; + SecondaryFolded = secondaryFolded; + SecondaryBloom = secondaryBloom; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTargetCache.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTargetCache.cs new file mode 100644 index 0000000000..dc5ec6e011 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/FuzzyTargetCache.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public struct FuzzyTargetCache +{ + private string? _lastRaw; + private uint _schemaId; + private FuzzyTarget _target; + + public FuzzyTarget GetOrUpdate(IPrecomputedFuzzyMatcher matcher, string? raw) + { + raw ??= string.Empty; + + if (_schemaId == matcher.SchemaId && string.Equals(_lastRaw, raw, StringComparison.Ordinal)) + { + return _target; + } + + _target = matcher.PrecomputeTarget(raw); + _schemaId = matcher.SchemaId; + _lastRaw = raw; + return _target; + } + + public void Invalidate() + { + _lastRaw = null; + _target = default; + _schemaId = 0; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IBloomFilter.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IBloomFilter.cs new file mode 100644 index 0000000000..e9234e7adf --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IBloomFilter.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public interface IBloomFilter +{ + ulong Compute(string input); + + bool MightContain(ulong candidateBloom, ulong queryBloom); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IFuzzyMatcherProvider.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IFuzzyMatcherProvider.cs new file mode 100644 index 0000000000..706dd0d8bf --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IFuzzyMatcherProvider.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public interface IFuzzyMatcherProvider +{ + IPrecomputedFuzzyMatcher Current { get; } + + void UpdateSettings(PrecomputedFuzzyMatcherOptions core, PinyinFuzzyMatcherOptions? pinyin = null); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IPrecomputedFuzzyMatcher.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IPrecomputedFuzzyMatcher.cs new file mode 100644 index 0000000000..dfb8af378e --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IPrecomputedFuzzyMatcher.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public interface IPrecomputedFuzzyMatcher +{ + uint SchemaId { get; } + + FuzzyQuery PrecomputeQuery(string? input); + + FuzzyTarget PrecomputeTarget(string? input); + + int Score(scoped in FuzzyQuery query, scoped in FuzzyTarget target); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IStringFolder.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IStringFolder.cs new file mode 100644 index 0000000000..6fcfbfaf61 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/IStringFolder.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public interface IStringFolder +{ + string Fold(string input, bool removeDiacritics); +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinFuzzyMatcherOptions.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinFuzzyMatcherOptions.cs new file mode 100644 index 0000000000..c060c33c92 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinFuzzyMatcherOptions.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class PinyinFuzzyMatcherOptions +{ + public PinyinMode Mode { get; init; } = PinyinMode.AutoSimplifiedChineseUi; + + /// Remove IME syllable separators (') for query secondary variant. + public bool RemoveApostrophesForQuery { get; init; } = true; +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinMode.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinMode.cs new file mode 100644 index 0000000000..0da88e14c0 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PinyinMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public enum PinyinMode +{ + Off = 0, + AutoSimplifiedChineseUi = 1, + On = 2, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcher.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcher.cs new file mode 100644 index 0000000000..0994f1d328 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcher.cs @@ -0,0 +1,575 @@ +// 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.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class PrecomputedFuzzyMatcher : IPrecomputedFuzzyMatcher +{ + private const int NoMatchScore = 0; + private const int StackallocThresholdChars = 512; + private const int FolderSchemaVersion = 1; + private const int BloomSchemaVersion = 1; + + private readonly PrecomputedFuzzyMatcherOptions _options; + private readonly IStringFolder _stringFolder; + private readonly IBloomFilter _bloom; + + public PrecomputedFuzzyMatcher( + PrecomputedFuzzyMatcherOptions? options = null, + IStringFolder? normalization = null, + IBloomFilter? bloomCalculator = null) + { + _options = options ?? PrecomputedFuzzyMatcherOptions.Default; + _bloom = bloomCalculator ?? new BloomFilter(); + _stringFolder = normalization ?? new StringFolder(); + + SchemaId = ComputeSchemaId(_options); + } + + public uint SchemaId { get; } + + public FuzzyQuery PrecomputeQuery(string? input) => PrecomputeQuery(input, null); + + public FuzzyTarget PrecomputeTarget(string? input) => PrecomputeTarget(input, null); + + public int Score(in FuzzyQuery query, in FuzzyTarget target) + { + var qFold = query.FoldedSpan; + var tLen = target.Length; + + if (query.EffectiveLength == 0 || tLen == 0) + { + return NoMatchScore; + } + + var skipWordSeparators = _options.SkipWordSeparators; + var bestScore = 0; + + // 1. Primary → Primary + if (tLen >= query.EffectiveLength && _bloom.MightContain(target.Bloom, query.Bloom)) + { + if (CanMatchSubsequence(qFold, target.FoldedSpan, skipWordSeparators)) + { + bestScore = ScoreNonContiguous( + qRaw: query.OriginalSpan, + qFold: qFold, + qEffectiveLen: query.EffectiveLength, + tRaw: target.OriginalSpan, + tFold: target.FoldedSpan, + ignoreSameCaseBonusForThisQuery: _options.IgnoreSameCaseBonusIfQueryIsAllLowercase && query.IsAllLowercaseAsciiOrNonLetter); + } + } + + // 2. Secondary → Secondary + if (query.HasSecondary && target.HasSecondary) + { + var qSecFold = query.SecondaryFoldedSpan; + + if (target.SecondaryLength >= query.SecondaryEffectiveLength && + _bloom.MightContain(target.SecondaryBloom, query.SecondaryBloom) && + CanMatchSubsequence(qSecFold, target.SecondaryFoldedSpan, skipWordSeparators)) + { + var score = ScoreNonContiguous( + qRaw: query.SecondaryOriginalSpan, + qFold: qSecFold, + qEffectiveLen: query.SecondaryEffectiveLength, + tRaw: target.SecondaryOriginalSpan, + tFold: target.SecondaryFoldedSpan, + ignoreSameCaseBonusForThisQuery: _options.IgnoreSameCaseBonusIfQueryIsAllLowercase && query.SecondaryIsAllLowercaseAsciiOrNonLetter); + + if (score > bestScore) + { + bestScore = score; + } + } + } + + // 3. Primary query → Secondary target + if (target.HasSecondary && + target.SecondaryLength >= query.EffectiveLength && + _bloom.MightContain(target.SecondaryBloom, query.Bloom)) + { + if (CanMatchSubsequence(qFold, target.SecondaryFoldedSpan, skipWordSeparators)) + { + var score = ScoreNonContiguous( + qRaw: query.OriginalSpan, + qFold: qFold, + qEffectiveLen: query.EffectiveLength, + tRaw: target.SecondaryOriginalSpan, + tFold: target.SecondaryFoldedSpan, + ignoreSameCaseBonusForThisQuery: _options.IgnoreSameCaseBonusIfQueryIsAllLowercase && query.IsAllLowercaseAsciiOrNonLetter); + + if (score > bestScore) + { + bestScore = score; + } + } + } + + // 4. Secondary query → Primary target + if (query.HasSecondary && + tLen >= query.SecondaryEffectiveLength && + _bloom.MightContain(target.Bloom, query.SecondaryBloom)) + { + var qSecFold = query.SecondaryFoldedSpan; + + if (CanMatchSubsequence(qSecFold, target.FoldedSpan, skipWordSeparators)) + { + var score = ScoreNonContiguous( + qRaw: query.SecondaryOriginalSpan, + qFold: qSecFold, + qEffectiveLen: query.SecondaryEffectiveLength, + tRaw: target.OriginalSpan, + tFold: target.FoldedSpan, + ignoreSameCaseBonusForThisQuery: _options.IgnoreSameCaseBonusIfQueryIsAllLowercase && query.SecondaryIsAllLowercaseAsciiOrNonLetter); + + if (score > bestScore) + { + bestScore = score; + } + } + } + + return bestScore; + } + + private FuzzyQuery PrecomputeQuery(string? input, string? secondaryInput) + { + input ??= string.Empty; + + var folded = _stringFolder.Fold(input, _options.RemoveDiacritics); + var bloom = _bloom.Compute(folded); + var effectiveLength = _options.SkipWordSeparators + ? folded.Length - CountWordSeparators(folded) + : folded.Length; + + var isAllLowercase = IsAllLowercaseAsciiOrNonLetter(input); + + string? secondaryOriginal = null; + string? secondaryFolded = null; + ulong secondaryBloom = 0; + var secondaryEffectiveLength = 0; + var secondaryIsAllLowercase = true; + + if (!string.IsNullOrEmpty(secondaryInput)) + { + secondaryOriginal = secondaryInput; + secondaryFolded = _stringFolder.Fold(secondaryInput, _options.RemoveDiacritics); + secondaryBloom = _bloom.Compute(secondaryFolded); + secondaryEffectiveLength = _options.SkipWordSeparators + ? secondaryFolded.Length - CountWordSeparators(secondaryFolded) + : secondaryFolded.Length; + + secondaryIsAllLowercase = IsAllLowercaseAsciiOrNonLetter(secondaryInput); + } + + return new FuzzyQuery( + original: input, + folded: folded, + bloom: bloom, + effectiveLength: effectiveLength, + isAllLowercaseAsciiOrNonLetter: isAllLowercase, + secondaryOriginal: secondaryOriginal, + secondaryFolded: secondaryFolded, + secondaryBloom: secondaryBloom, + secondaryEffectiveLength: secondaryEffectiveLength, + secondaryIsAllLowercaseAsciiOrNonLetter: secondaryIsAllLowercase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int CountWordSeparators(string s) + { + var count = 0; + foreach (var c in s) + { + if (SymbolClassifier.Classify(c) == SymbolKind.WordSeparator) + { + count++; + } + } + + return count; + } + } + + internal FuzzyTarget PrecomputeTarget(string? input, string? secondaryInput) + { + input ??= string.Empty; + + var folded = _stringFolder.Fold(input, _options.RemoveDiacritics); + var bloom = _bloom.Compute(folded); + + string? secondaryFolded = null; + ulong secondaryBloom = 0; + + if (!string.IsNullOrEmpty(secondaryInput)) + { + secondaryFolded = _stringFolder.Fold(secondaryInput, _options.RemoveDiacritics); + secondaryBloom = _bloom.Compute(secondaryFolded); + } + + return new FuzzyTarget( + input, + folded, + bloom, + secondaryInput, + secondaryFolded, + secondaryBloom); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsAllLowercaseAsciiOrNonLetter(string s) + { + foreach (var c in s) + { + if ((uint)(c - 'A') <= ('Z' - 'A')) + { + return false; + } + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CanMatchSubsequence( + ReadOnlySpan qFold, + ReadOnlySpan tFold, + bool skipWordSeparators) + { + var qi = 0; + var ti = 0; + + while (qi < qFold.Length && ti < tFold.Length) + { + var qChar = qFold[qi]; + + if (skipWordSeparators && SymbolClassifier.Classify(qChar) == SymbolKind.WordSeparator) + { + qi++; + continue; + } + + if (qChar == tFold[ti]) + { + qi++; + } + + ti++; + } + + // Skip trailing word separators in query + if (skipWordSeparators) + { + while (qi < qFold.Length && SymbolClassifier.Classify(qFold[qi]) == SymbolKind.WordSeparator) + { + qi++; + } + } + + return qi == qFold.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + [SkipLocalsInit] + private int ScoreNonContiguous( + scoped in ReadOnlySpan qRaw, + scoped in ReadOnlySpan qFold, + int qEffectiveLen, + scoped in ReadOnlySpan tRaw, + scoped in ReadOnlySpan tFold, + bool ignoreSameCaseBonusForThisQuery) + { + Debug.Assert(qRaw.Length == qFold.Length, "Original and folded spans are traversed in lockstep: requires qRaw.Length == qFold.Length"); + Debug.Assert(tRaw.Length == tFold.Length, "Original and folded spans are traversed in lockstep: requires tRaw.Length == tFold.Length"); + Debug.Assert(qEffectiveLen <= qFold.Length, "Effective length must be less than or equal to folded length"); + + var qLen = qFold.Length; + var tLen = tFold.Length; + + // Copy options to local variables to avoid repeated field accesses + var charMatchBonus = _options.CharMatchBonus; + var sameCaseBonus = ignoreSameCaseBonusForThisQuery ? 0 : _options.SameCaseBonus; + var consecutiveMultiplier = _options.ConsecutiveMultiplier; + var camelCaseBonus = _options.CamelCaseBonus; + var startOfWordBonus = _options.StartOfWordBonus; + var pathSeparatorBonus = _options.PathSeparatorBonus; + var wordSeparatorBonus = _options.WordSeparatorBonus; + var separatorAlignmentBonus = _options.SeparatorAlignmentBonus; + var exactSeparatorBonus = _options.ExactSeparatorBonus; + var skipWordSeparators = _options.SkipWordSeparators; + + // DP buffer: two rows of length tLen + var bufferSize = tLen * 2; + int[]? rented = null; + + try + { + scoped Span buffer; + if (bufferSize <= StackallocThresholdChars) + { + buffer = stackalloc int[bufferSize]; + } + else + { + rented = ArrayPool.Shared.Rent(bufferSize); + buffer = rented.AsSpan(0, bufferSize); + } + + var scores = buffer[..tLen]; + var seqLens = buffer.Slice(tLen, tLen); + + scores.Clear(); + seqLens.Clear(); + + ref var scores0 = ref MemoryMarshal.GetReference(scores); + ref var seqLens0 = ref MemoryMarshal.GetReference(seqLens); + ref var qRaw0 = ref MemoryMarshal.GetReference(qRaw); + ref var qFold0 = ref MemoryMarshal.GetReference(qFold); + ref var tRaw0 = ref MemoryMarshal.GetReference(tRaw); + ref var tFold0 = ref MemoryMarshal.GetReference(tFold); + + var qiEffective = 0; + + for (var qi = 0; qi < qLen; qi++) + { + var qCharFold = Unsafe.Add(ref qFold0, qi); + var qCharKind = SymbolClassifier.Classify(qCharFold); + + if (skipWordSeparators && qCharKind == SymbolKind.WordSeparator) + { + continue; + } + + // Hoisted values + var qRawIsUpper = char.IsUpper(Unsafe.Add(ref qRaw0, qi)); + + // row computation + var leftScore = 0; + var diagScore = 0; + var diagSeqLen = 0; + + // limit ti to ensure enough remaining characters to match the rest of the query + var tiMax = tLen - qEffectiveLen + qiEffective; + + for (var ti = 0; ti <= tiMax; ti++) + { + var upScore = Unsafe.Add(ref scores0, ti); + var upSeqLen = Unsafe.Add(ref seqLens0, ti); + + var charScore = 0; + if (diagScore != 0 || qiEffective == 0) + { + charScore = ComputeCharScore( + qi, + ti, + qCharFold, + qCharKind, + diagSeqLen, + qRawIsUpper, + ref tRaw0, + ref qFold0, + ref tFold0); + } + + var candidateScore = diagScore + charScore; + if (charScore != 0 && candidateScore >= leftScore) + { + Unsafe.Add(ref scores0, ti) = candidateScore; + Unsafe.Add(ref seqLens0, ti) = diagSeqLen + 1; + leftScore = candidateScore; + } + else + { + Unsafe.Add(ref scores0, ti) = leftScore; + Unsafe.Add(ref seqLens0, ti) = 0; + /* leftScore remains unchanged */ + } + + diagScore = upScore; + diagSeqLen = upSeqLen; + } + + // Early exit: no match possible + if (leftScore == 0) + { + return NoMatchScore; + } + + // Advance effective query index + // Only counts non-separator characters if skipWordSeparators is enabled + qiEffective++; + + if (qiEffective == qEffectiveLen) + { + return leftScore; + } + } + + return scores[tLen - 1]; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + int ComputeCharScore( + int qi, + int ti, + char qCharFold, + SymbolKind qCharKind, + int seqLen, + bool qCharRawCurrIsUpper, + ref char tRaw0, + ref char qFold0, + ref char tFold0) + { + // Match check: + // - exact folded char match always ok + // - otherwise, allow equivalence only for word separators (e.g. '_' matches '-') + var tCharFold = Unsafe.Add(ref tFold0, ti); + if (qCharFold != tCharFold) + { + if (!skipWordSeparators) + { + return 0; + } + + if (qCharKind != SymbolKind.WordSeparator || + SymbolClassifier.Classify(tCharFold) != SymbolKind.WordSeparator) + { + return 0; + } + } + + // 0. Base char match bonus + var score = charMatchBonus; + + // 1. Consecutive match bonus + if (seqLen > 0) + { + score += seqLen * consecutiveMultiplier; + } + + // 2. Same case bonus + // Early outs to appease the branch predictor + if (sameCaseBonus != 0) + { + var tCharRawCurr = Unsafe.Add(ref tRaw0, ti); + var tCharRawCurrIsUpper = char.IsUpper(tCharRawCurr); + if (qCharRawCurrIsUpper == tCharRawCurrIsUpper) + { + score += sameCaseBonus; + } + + if (ti == 0) + { + score += startOfWordBonus; + return score; + } + + var tPrevFold = Unsafe.Add(ref tFold0, ti - 1); + var tPrevKind = SymbolClassifier.Classify(tPrevFold); + if (tPrevKind != SymbolKind.Other) + { + score += tPrevKind == SymbolKind.PathSeparator + ? pathSeparatorBonus + : wordSeparatorBonus; + + if (skipWordSeparators && seqLen == 0 && qi > 0) + { + var qPrevFold = Unsafe.Add(ref qFold0, qi - 1); + var qPrevKind = SymbolClassifier.Classify(qPrevFold); + + if (qPrevKind == SymbolKind.WordSeparator) + { + score += separatorAlignmentBonus; + + if (tPrevKind == SymbolKind.WordSeparator && qPrevFold == tPrevFold) + { + score += exactSeparatorBonus; + } + } + } + + return score; + } + + if (tCharRawCurrIsUpper && seqLen == 0) + { + score += camelCaseBonus; + return score; + } + + return score; + } + else + { + if (ti == 0) + { + score += startOfWordBonus; + return score; + } + + var tPrevFold = Unsafe.Add(ref tFold0, ti - 1); + var tPrevKind = SymbolClassifier.Classify(tPrevFold); + if (tPrevKind != SymbolKind.Other) + { + score += tPrevKind == SymbolKind.PathSeparator + ? pathSeparatorBonus + : wordSeparatorBonus; + + if (skipWordSeparators && seqLen == 0 && qi > 0) + { + var qPrevFold = Unsafe.Add(ref qFold0, qi - 1); + var qPrevKind = SymbolClassifier.Classify(qPrevFold); + + if (qPrevKind == SymbolKind.WordSeparator) + { + score += separatorAlignmentBonus; + + if (tPrevKind == SymbolKind.WordSeparator && qPrevFold == tPrevFold) + { + score += exactSeparatorBonus; + } + } + } + + return score; + } + + if (camelCaseBonus != 0 && seqLen == 0 && char.IsUpper(Unsafe.Add(ref tRaw0, ti))) + { + score += camelCaseBonus; + return score; + } + + return score; + } + } + } + finally + { + if (rented is not null) + { + ArrayPool.Shared.Return(rented); + } + } + } + + // Schema ID is for cache invalidation of precomputed targets. + // Only includes options that affect folding/bloom, not scoring. + private static uint ComputeSchemaId(PrecomputedFuzzyMatcherOptions o) + { + const uint fnvOffset = 2166136261; + const uint fnvPrime = 16777619; + + var h = fnvOffset; + h = unchecked((h ^ FolderSchemaVersion) * fnvPrime); + h = unchecked((h ^ BloomSchemaVersion) * fnvPrime); + h = unchecked((h ^ (uint)(o.RemoveDiacritics ? 1 : 0)) * fnvPrime); + + return h; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherOptions.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherOptions.cs new file mode 100644 index 0000000000..b1b01d60f1 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherOptions.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class PrecomputedFuzzyMatcherOptions +{ + public static PrecomputedFuzzyMatcherOptions Default { get; } = new(); + + /* + * Bonuses + */ + public int CharMatchBonus { get; init; } = 1; + + public int SameCaseBonus { get; init; } = 1; + + public int ConsecutiveMultiplier { get; init; } = 5; + + public int CamelCaseBonus { get; init; } = 2; + + public int StartOfWordBonus { get; init; } = 8; + + public int PathSeparatorBonus { get; init; } = 5; + + public int WordSeparatorBonus { get; init; } = 4; + + public int SeparatorAlignmentBonus { get; init; } = 2; + + public int ExactSeparatorBonus { get; init; } = 1; + + /* + * Settings + */ + public bool RemoveDiacritics { get; init; } = true; + + public bool SkipWordSeparators { get; init; } = true; + + public bool IgnoreSameCaseBonusIfQueryIsAllLowercase { get; init; } = true; +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherWithPinyin.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherWithPinyin.cs new file mode 100644 index 0000000000..026328f2c5 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/PrecomputedFuzzyMatcherWithPinyin.cs @@ -0,0 +1,177 @@ +// 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.Globalization; +using System.Runtime.CompilerServices; +using ToolGood.Words.Pinyin; + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class PrecomputedFuzzyMatcherWithPinyin : IPrecomputedFuzzyMatcher +{ + private readonly IBloomFilter _bloom; + private readonly PrecomputedFuzzyMatcher _core; + + private readonly IStringFolder _stringFolder; + private readonly PinyinFuzzyMatcherOptions _pinyin; + + public PrecomputedFuzzyMatcherWithPinyin( + PrecomputedFuzzyMatcherOptions coreOptions, + PinyinFuzzyMatcherOptions pinyinOptions, + IStringFolder stringFolder, + IBloomFilter bloom) + { + _pinyin = pinyinOptions; + _stringFolder = stringFolder; + _bloom = bloom; + + _core = new PrecomputedFuzzyMatcher(coreOptions, stringFolder, bloom); + + SchemaId = CombineSchema(_core.SchemaId, _pinyin); + } + + public uint SchemaId { get; } + + public FuzzyQuery PrecomputeQuery(string? input) + { + input ??= string.Empty; + + var primary = _core.PrecomputeQuery(input); + + // Fast exit if effectively off (provider should already filter, but keep robust) + if (!IsPinyinEnabled(_pinyin)) + { + return primary; + } + + // Match legacy: remove apostrophes for query secondary + var queryForPinyin = _pinyin.RemoveApostrophesForQuery ? RemoveApostrophesIfAny(input) : input; + + var pinyin = WordsHelper.GetPinyin(queryForPinyin); + if (string.IsNullOrEmpty(pinyin)) + { + return primary; + } + + var secondary = _core.PrecomputeQuery(pinyin); + return new FuzzyQuery( + primary.Original, + primary.Folded, + primary.Bloom, + primary.EffectiveLength, + primary.IsAllLowercaseAsciiOrNonLetter, + secondary.Original, + secondary.Folded, + secondary.Bloom, + secondary.EffectiveLength, + secondary.SecondaryIsAllLowercaseAsciiOrNonLetter); + } + + public FuzzyTarget PrecomputeTarget(string? input) + { + input ??= string.Empty; + + var primary = _core.PrecomputeTarget(input); + + if (!IsPinyinEnabled(_pinyin)) + { + return primary; + } + + // Match legacy: only compute target pinyin when target contains Chinese + if (!ContainsToolGoodChinese(input)) + { + return primary; + } + + var pinyin = WordsHelper.GetPinyin(input); + if (string.IsNullOrEmpty(pinyin)) + { + return primary; + } + + var secondary = _core.PrecomputeTarget(pinyin); + return new FuzzyTarget( + primary.Original, + primary.Folded, + primary.Bloom, + secondary.Original, + secondary.Folded, + secondary.Bloom); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Score(scoped in FuzzyQuery query, scoped in FuzzyTarget target) + => _core.Score(in query, in target); + + private static bool IsPinyinEnabled(PinyinFuzzyMatcherOptions o) => o.Mode switch + { + PinyinMode.Off => false, + PinyinMode.On => true, + PinyinMode.AutoSimplifiedChineseUi => IsSimplifiedChineseUi(), + _ => false, + }; + + private static bool IsSimplifiedChineseUi() + { + var culture = CultureInfo.CurrentUICulture; + return culture.Name.StartsWith("zh-CN", StringComparison.OrdinalIgnoreCase) + || culture.Name.StartsWith("zh-Hans", StringComparison.OrdinalIgnoreCase); + } + + private static bool ContainsToolGoodChinese(string s) + { + return WordsHelper.HasChinese(s); + } + + private static string RemoveApostrophesIfAny(string input) + { + var first = input.IndexOf('\''); + if (first < 0) + { + return input; + } + + var removeCount = 1; + for (var i = first + 1; i < input.Length; i++) + { + if (input[i] == '\'') + { + removeCount++; + } + } + + return string.Create(input.Length - removeCount, input, static (dst, src) => + { + var di = 0; + for (var i = 0; i < src.Length; i++) + { + var c = src[i]; + if (c == '\'') + { + continue; + } + + dst[di++] = c; + } + }); + } + + private static uint CombineSchema(uint coreSchemaId, PinyinFuzzyMatcherOptions p) + { + const uint fnvOffset = 2166136261; + const uint fnvPrime = 16777619; + + var h = fnvOffset; + h = unchecked((h ^ coreSchemaId) * fnvPrime); + h = unchecked((h ^ (uint)p.Mode) * fnvPrime); + h = unchecked((h ^ (p.RemoveApostrophesForQuery ? 1u : 0u)) * fnvPrime); + + // bump if you change formatting/conversion behavior + const uint pinyinAlgoVersion = 1; + h = unchecked((h ^ pinyinAlgoVersion) * fnvPrime); + + return h; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/StringFolder.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/StringFolder.cs new file mode 100644 index 0000000000..2d814be553 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/StringFolder.cs @@ -0,0 +1,163 @@ +// 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.Globalization; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Microsoft.CmdPal.Core.Common.Text; + +public sealed class StringFolder : IStringFolder +{ + // Cache for diacritic-stripped uppercase characters. + // Benign race: worst case is redundant computation writing the same value. + // 0 = uncached, else cachedChar + 1 + private static readonly ushort[] StripCacheUpper = new ushort[char.MaxValue + 1]; + + public string Fold(string input, bool removeDiacritics) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + if (!removeDiacritics || Ascii.IsValid(input)) + { + if (IsAlreadyFoldedAndSlashNormalized(input)) + { + return input; + } + + return string.Create(input.Length, input, static (dst, src) => + { + for (var i = 0; i < src.Length; i++) + { + var c = src[i]; + dst[i] = c == '\\' ? '/' : char.ToUpperInvariant(c); + } + }); + } + + return string.Create(input.Length, input, static (dst, src) => + { + for (var i = 0; i < src.Length; i++) + { + var c = src[i]; + var upper = c == '\\' ? '/' : char.ToUpperInvariant(c); + dst[i] = StripDiacriticsFromUpper(upper); + } + }); + } + + private static bool IsAlreadyFoldedAndSlashNormalized(string input) + { + var sawNonAscii = false; + + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + + if (c == '\\') + { + return false; + } + + if ((uint)(c - 'a') <= 'z' - 'a') + { + return false; + } + + if (c > 0x7F) + { + sawNonAscii = true; + } + } + + if (sawNonAscii) + { + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + if (c <= 0x7F) + { + continue; + } + + var cat = CharUnicodeInfo.GetUnicodeCategory(c); + if (cat is UnicodeCategory.LowercaseLetter or UnicodeCategory.TitlecaseLetter) + { + return false; + } + } + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static char StripDiacriticsFromUpper(char upper) + { + if (upper <= 0x7F) + { + return upper; + } + + // Never attempt normalization on lone UTF-16 surrogates. + if (char.IsSurrogate(upper)) + { + return upper; + } + + var cachedPlus1 = StripCacheUpper[upper]; + if (cachedPlus1 != 0) + { + return (char)(cachedPlus1 - 1); + } + + var mapped = StripDiacriticsSlow(upper); + StripCacheUpper[upper] = (ushort)(mapped + 1); + return mapped; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static char StripDiacriticsSlow(char upper) + { + try + { + var baseChar = FirstNonMark(upper, NormalizationForm.FormD); + if (baseChar == '\0' || baseChar == upper) + { + var kd = FirstNonMark(upper, NormalizationForm.FormKD); + if (kd != '\0') + { + baseChar = kd; + } + } + + return char.ToUpperInvariant(baseChar == '\0' ? upper : baseChar); + } + catch + { + // Absolute safety: if globalization tables ever throw for some reason, + // degrade gracefully rather than failing hard. + return upper; + } + + static char FirstNonMark(char c, NormalizationForm form) + { + var normalized = c.ToString().Normalize(form); + + foreach (var ch in normalized) + { + var cat = CharUnicodeInfo.GetUnicodeCategory(ch); + if (cat is not (UnicodeCategory.NonSpacingMark or UnicodeCategory.SpacingCombiningMark or UnicodeCategory.EnclosingMark)) + { + return ch; + } + } + + return '\0'; + } + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolClassifier.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolClassifier.cs new file mode 100644 index 0000000000..e1be786646 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolClassifier.cs @@ -0,0 +1,29 @@ +// 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.Runtime.CompilerServices; + +namespace Microsoft.CmdPal.Core.Common.Text; + +internal static class SymbolClassifier +{ + // Embedded in .data section - no allocation, no static constructor + private static ReadOnlySpan Lookup => + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 2, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 1, // 32-47: space=2, "=2, '=2, -=2, .=2, /=1 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, // 48-63: :=2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, // 80-95: _=2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96-111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 112-127 + ]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SymbolKind Classify(char c) + { + return c > 0x7F ? SymbolKind.Other : (SymbolKind)Lookup[c]; + } +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolKind.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolKind.cs new file mode 100644 index 0000000000..d2644be420 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Text/SymbolKind.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.Common.Text; + +internal enum SymbolKind : byte +{ + Other = 0, + PathSeparator = 1, + WordSeparator = 2, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs index f8e9478023..af10995cf9 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandItemViewModel.cs @@ -4,6 +4,8 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CmdPal.Core.Common; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CmdPal.Core.Common.Text; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Core.ViewModels.Models; using Microsoft.CommandPalette.Extensions; @@ -13,7 +15,7 @@ using Windows.ApplicationModel.DataTransfer; namespace Microsoft.CmdPal.Core.ViewModels; [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] -public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext +public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBarContext, IPrecomputedListItem { public ExtensionObject Model => _commandItemModel; @@ -22,6 +24,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa private readonly ExtensionObject _commandItemModel = new(null); private CommandContextItemViewModel? _defaultCommandContextItemViewModel; + private FuzzyTargetCache _titleCache; + private FuzzyTargetCache _subtitleCache; + internal InitializedState Initialized { get; private set; } = InitializedState.Uninitialized; protected bool IsFastInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.FastInitialized); @@ -116,6 +121,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa _itemTitle = model.Title; Subtitle = model.Subtitle; + _titleCache.Invalidate(); + _subtitleCache.Invalidate(); Initialized |= InitializedState.FastInitialized; } @@ -249,6 +256,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa Subtitle = "Item failed to load"; MoreCommands = []; _icon = _errorIcon; + _titleCache.Invalidate(); + _subtitleCache.Invalidate(); Initialized |= InitializedState.Error; } @@ -286,6 +295,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa Subtitle = "Item failed to load"; MoreCommands = []; _icon = _errorIcon; + _titleCache.Invalidate(); + _subtitleCache.Invalidate(); Initialized |= InitializedState.Error; } @@ -335,12 +346,14 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa case nameof(Title): _itemTitle = model.Title; + _titleCache.Invalidate(); break; case nameof(Subtitle): var modelSubtitle = model.Subtitle; this.Subtitle = modelSubtitle; _defaultCommandContextItemViewModel?.Subtitle = modelSubtitle; + _subtitleCache.Invalidate(); break; case nameof(Icon): @@ -415,6 +428,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa // Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command // or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK. _itemTitle = model.Title; + _titleCache.Invalidate(); UpdateProperty(nameof(Title), nameof(Name)); _defaultCommandContextItemViewModel?.UpdateTitle(model.Command.Name); @@ -436,6 +450,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa private void UpdateTitle(string? title) { _itemTitle = title ?? string.Empty; + _titleCache.Invalidate(); UpdateProperty(nameof(Title)); } @@ -456,6 +471,12 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa UpdateProperty(nameof(DataPackage)); } + public FuzzyTarget GetTitleTarget(IPrecomputedFuzzyMatcher matcher) + => _titleCache.GetOrUpdate(matcher, Title); + + public FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher) + => _subtitleCache.GetOrUpdate(matcher, Subtitle); + protected override void UnsafeCleanup() { base.UnsafeCleanup(); diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs index b2c03411d1..6ec593bfa4 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/CommandViewModel.cs @@ -11,6 +11,8 @@ public partial class CommandViewModel : ExtensionObjectViewModel { public ExtensionObject Model { get; private set; } = new(null); + public bool IsSet => Model.Unsafe is not null; + protected bool IsInitialized { get; private set; } protected bool IsFastInitialized { get; private set; } diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs index 07c238ab42..83f314a11f 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs @@ -3,9 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Core.Common; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CmdPal.Core.Common.Text; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -16,6 +19,8 @@ namespace Microsoft.CmdPal.Core.ViewModels; public partial class ContextMenuViewModel : ObservableObject, IRecipient { + private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider; + public ICommandBarContext? SelectedItem { get => field; @@ -39,8 +44,9 @@ public partial class ContextMenuViewModel : ObservableObject, private string _lastSearchText = string.Empty; - public ContextMenuViewModel() + public ContextMenuViewModel(IFuzzyMatcherProvider fuzzyMatcherProvider) { + _fuzzyMatcherProvider = fuzzyMatcherProvider; WeakReferenceMessenger.Default.Register(this); } @@ -91,13 +97,14 @@ public partial class ContextMenuViewModel : ObservableObject, .OfType() .Where(c => c.ShouldBeVisible); - var newResults = ListHelpers.FilterList(commands, searchText, ScoreContextCommand); + var query = _fuzzyMatcherProvider.Current.PrecomputeQuery(searchText); + var newResults = InternalListHelpers.FilterList(commands, in query, ScoreFunction); ListHelpers.InPlaceUpdateList(FilteredItems, newResults); } - private static int ScoreContextCommand(string query, CommandContextItemViewModel item) + private int ScoreFunction(in FuzzyQuery query, CommandContextItemViewModel item) { - if (string.IsNullOrEmpty(query) || string.IsNullOrWhiteSpace(query)) + if (string.IsNullOrWhiteSpace(query.Original)) { return 1; } @@ -107,11 +114,21 @@ public partial class ContextMenuViewModel : ObservableObject, return 0; } - var nameMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Title); + var fuzzyMatcher = _fuzzyMatcherProvider.Current; + var title = item.GetTitleTarget(fuzzyMatcher); + var subtitle = item.GetSubtitleTarget(fuzzyMatcher); - var descriptionMatch = FuzzyStringMatcher.ScoreFuzzy(query, item.Subtitle); + var titleScore = fuzzyMatcher.Score(query, title); + var subtitleScore = (fuzzyMatcher.Score(query, subtitle) - 4) / 2; - return new[] { nameMatch, (descriptionMatch - 4) / 2, 0 }.Max(); + return Max3(titleScore, subtitleScore, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Max3(int a, int b, int c) + { + var m = a > b ? a : b; + return m > c ? m : c; } /// diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs new file mode 100644 index 0000000000..71e6970056 --- /dev/null +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.Core.ViewModels; + +public enum ListItemType +{ + Item, + SectionHeader, + Separator, +} diff --git a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs index fb4afc49b8..bd3b505dc1 100644 --- a/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs +++ b/src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/ListItemViewModel.cs @@ -24,7 +24,9 @@ public partial class ListItemViewModel : CommandItemViewModel public string Section { get; private set; } = string.Empty; - public bool IsSectionOrSeparator { get; private set; } + public ListItemType Type { get; private set; } + + public bool IsInteractive => Type == ListItemType.Item; public DetailsViewModel? Details { get; private set; } @@ -85,15 +87,17 @@ public partial class ListItemViewModel : CommandItemViewModel UpdateTags(li.Tags); Section = li.Section ?? string.Empty; - IsSectionOrSeparator = IsSeparator(li); - UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive)); UpdateAccessibleName(); } - private bool IsSeparator(IListItem item) + private ListItemType EvaluateType() { - return item.Command is null; + return Command.IsSet + ? ListItemType.Item + : string.IsNullOrEmpty(Section) ? ListItemType.Separator : ListItemType.SectionHeader; } public override void SlowInitializeProperties() @@ -140,12 +144,12 @@ public partial class ListItemViewModel : CommandItemViewModel break; case nameof(model.Section): Section = model.Section ?? string.Empty; - IsSectionOrSeparator = IsSeparator(model); - UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Section), nameof(Type), nameof(IsInteractive)); break; case nameof(model.Command): - IsSectionOrSeparator = IsSeparator(model); - UpdateProperty(nameof(IsSectionOrSeparator)); + Type = EvaluateType(); + UpdateProperty(nameof(Type), nameof(IsInteractive)); break; case nameof(Details): var extensionDetails = model.Details; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs index 72a295c83f..4c997266c6 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AliasManager.cs @@ -107,7 +107,7 @@ public partial class AliasManager : ObservableObject } // Look for the alias belonging to another command, and remove it - if (newAlias is not null && kv.Value.Alias == newAlias.Alias) + if (newAlias is not null && kv.Value.Alias == newAlias.Alias && kv.Value.CommandId != commandId) { toRemove.Add(kv.Value); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs index 71e150a7d2..4de0215311 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppearanceSettingsViewModel.cs @@ -18,6 +18,8 @@ namespace Microsoft.CmdPal.UI.ViewModels; public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDisposable { + private static readonly Color DefaultTintColor = Color.FromArgb(255, 0, 120, 212); + private static readonly ObservableCollection WindowsColorSwatches = [ // row 0 @@ -128,10 +130,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis OnPropertyChanged(); OnPropertyChanged(nameof(ColorizationModeIndex)); OnPropertyChanged(nameof(IsCustomTintVisible)); - OnPropertyChanged(nameof(IsCustomTintIntensityVisible)); + OnPropertyChanged(nameof(IsColorIntensityVisible)); + OnPropertyChanged(nameof(IsImageTintIntensityVisible)); + OnPropertyChanged(nameof(EffectiveTintIntensity)); OnPropertyChanged(nameof(IsBackgroundControlsVisible)); OnPropertyChanged(nameof(IsNoBackgroundVisible)); OnPropertyChanged(nameof(IsAccentColorControlsVisible)); + OnPropertyChanged(nameof(IsResetButtonVisible)); if (value == ColorizationMode.WindowsAccentColor) { @@ -179,6 +184,19 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis { _settings.CustomThemeColorIntensity = value; OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveTintIntensity)); + Save(); + } + } + + public int BackgroundImageTintIntensity + { + get => _settings.BackgroundImageTintIntensity; + set + { + _settings.BackgroundImageTintIntensity = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveTintIntensity)); Save(); } } @@ -279,12 +297,108 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis }; } + public int BackdropOpacity + { + get => _settings.BackdropOpacity; + set + { + if (_settings.BackdropOpacity != value) + { + _settings.BackdropOpacity = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(EffectiveBackdropStyle)); + OnPropertyChanged(nameof(EffectiveImageOpacity)); + Save(); + } + } + } + + public int BackdropStyleIndex + { + get => (int)_settings.BackdropStyle; + set + { + var newStyle = (BackdropStyle)value; + if (_settings.BackdropStyle != newStyle) + { + _settings.BackdropStyle = newStyle; + + OnPropertyChanged(); + OnPropertyChanged(nameof(IsBackdropOpacityVisible)); + OnPropertyChanged(nameof(IsMicaBackdropDescriptionVisible)); + OnPropertyChanged(nameof(IsBackgroundSettingsEnabled)); + OnPropertyChanged(nameof(IsBackgroundNotAvailableVisible)); + + if (!IsBackgroundSettingsEnabled) + { + IsColorizationDetailsExpanded = false; + } + + Save(); + } + } + } + + /// + /// Gets whether the backdrop opacity slider should be visible. + /// + public bool IsBackdropOpacityVisible => + BackdropStyles.Get(_settings.BackdropStyle).SupportsOpacity; + + /// + /// Gets whether the backdrop description (for styles without options) should be visible. + /// + public bool IsMicaBackdropDescriptionVisible => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsOpacity; + + /// + /// Gets whether background/colorization settings are available. + /// + public bool IsBackgroundSettingsEnabled => + BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization; + + /// + /// Gets whether the "not available" message should be shown (inverse of IsBackgroundSettingsEnabled). + /// + public bool IsBackgroundNotAvailableVisible => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization; + + public BackdropStyle? EffectiveBackdropStyle + { + get + { + // Return style when transparency/blur is visible (not fully opaque Acrylic) + // - Clear/Mica/MicaAlt/AcrylicThin always show their effect + // - Acrylic shows effect only when opacity < 100 + if (_settings.BackdropStyle != BackdropStyle.Acrylic || _settings.BackdropOpacity < 100) + { + return _settings.BackdropStyle; + } + + return null; + } + } + + public double EffectiveImageOpacity => + EffectiveBackdropStyle is not null + ? (BackgroundImageOpacity / 100f) * Math.Sqrt(_settings.BackdropOpacity / 100.0) + : (BackgroundImageOpacity / 100f); + [ObservableProperty] public partial bool IsColorizationDetailsExpanded { get; set; } public bool IsCustomTintVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image; - public bool IsCustomTintIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image; + public bool IsColorIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor; + + public bool IsImageTintIntensityVisible => _settings.ColorizationMode is ColorizationMode.Image; + + /// + /// Gets the effective tint intensity for the preview, based on the current colorization mode. + /// + public int EffectiveTintIntensity => _settings.ColorizationMode is ColorizationMode.Image + ? _settings.BackgroundImageTintIntensity + : _settings.CustomThemeColorIntensity; public bool IsBackgroundControlsVisible => _settings.ColorizationMode is ColorizationMode.Image; @@ -292,16 +406,21 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis public bool IsAccentColorControlsVisible => _settings.ColorizationMode is ColorizationMode.WindowsAccentColor; - public AcrylicBackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f); + public bool IsResetButtonVisible => _settings.ColorizationMode is ColorizationMode.Image; + + public BackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f); public ElementTheme EffectiveTheme => _elementThemeOverride ?? _themeService.Current.Theme; - public Color EffectiveThemeColor => ColorizationMode switch - { - ColorizationMode.WindowsAccentColor => _currentSystemAccentColor, - ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor, - _ => Colors.Transparent, - }; + public Color EffectiveThemeColor => + !BackdropStyles.Get(_settings.BackdropStyle).SupportsColorization + ? Colors.Transparent + : ColorizationMode switch + { + ColorizationMode.WindowsAccentColor => _currentSystemAccentColor, + ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor, + _ => Colors.Transparent, + }; // Since the blur amount is absolute, we need to scale it down for the preview (which is smaller than full screen). public int EffectiveBackgroundImageBlurAmount => (int)Math.Round(BackgroundImageBlurAmount / 4f); @@ -309,11 +428,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis public double EffectiveBackgroundImageBrightness => BackgroundImageBrightness / 100.0; public ImageSource? EffectiveBackgroundImageSource => - ColorizationMode is ColorizationMode.Image - && !string.IsNullOrWhiteSpace(BackgroundImagePath) - && Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri) - ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri) - : null; + !BackdropStyles.Get(_settings.BackdropStyle).SupportsBackgroundImage + ? null + : ColorizationMode is ColorizationMode.Image + && !string.IsNullOrWhiteSpace(BackgroundImagePath) + && Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri) + ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri) + : null; public AppearanceSettingsViewModel(IThemeService themeService, SettingsModel settings) { @@ -327,7 +448,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis Reapply(); - IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None; + IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled; } private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender)); @@ -357,6 +478,8 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis // Theme services recalculates effective color and opacity based on current settings. EffectiveBackdrop = _themeService.Current.BackdropParameters; OnPropertyChanged(nameof(EffectiveBackdrop)); + OnPropertyChanged(nameof(EffectiveBackdropStyle)); + OnPropertyChanged(nameof(EffectiveImageOpacity)); OnPropertyChanged(nameof(EffectiveBackgroundImageBrightness)); OnPropertyChanged(nameof(EffectiveBackgroundImageSource)); OnPropertyChanged(nameof(EffectiveThemeColor)); @@ -379,7 +502,28 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis BackgroundImageBlurAmount = 0; BackgroundImageFit = BackgroundImageFit.UniformToFill; BackgroundImageOpacity = 100; - ColorIntensity = 0; + BackgroundImageTintIntensity = 0; + } + + [RelayCommand] + private void ResetAppearanceSettings() + { + // Reset theme + Theme = UserTheme.Default; + + // Reset backdrop settings + BackdropStyleIndex = (int)BackdropStyle.Acrylic; + BackdropOpacity = 100; + + // Reset background image settings + BackgroundImagePath = string.Empty; + ResetBackgroundImageProperties(); + + // Reset colorization + ColorizationMode = ColorizationMode.None; + ThemeColor = DefaultTintColor; + ColorIntensity = 100; + BackgroundImageTintIntensity = 0; } public void Dispose() diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs new file mode 100644 index 0000000000..9d24d5d435 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropControllerKind.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the type of system backdrop controller to use. +/// +public enum BackdropControllerKind +{ + /// + /// Solid color with alpha transparency (TransparentTintBackdrop). + /// + Solid, + + /// + /// Desktop Acrylic with default blur (DesktopAcrylicKind.Default). + /// + Acrylic, + + /// + /// Desktop Acrylic with thinner blur (DesktopAcrylicKind.Thin). + /// + AcrylicThin, + + /// + /// Mica effect (MicaKind.Base). + /// + Mica, + + /// + /// Mica alternate/darker variant (MicaKind.BaseAlt). + /// + MicaAlt, + + /// + /// Custom backdrop implementation. + /// + Custom, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs new file mode 100644 index 0000000000..72d2835e48 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyle.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the visual backdrop style for the window. +/// +public enum BackdropStyle +{ + /// + /// Standard desktop acrylic with blur effect. + /// + Acrylic, + + /// + /// Solid color with alpha transparency (no blur). + /// + Clear, + + /// + /// Mica effect that samples the desktop wallpaper. + /// + Mica, + + /// + /// Thinner acrylic variant with more transparency. + /// + AcrylicThin, + + /// + /// Mica alternate variant (darker). + /// + MicaAlt, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs new file mode 100644 index 0000000000..c9fa50a23d --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyleConfig.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Configuration parameters for a backdrop style. +/// +public sealed record BackdropStyleConfig +{ + /// + /// Gets the type of system backdrop controller to use. + /// + public required BackdropControllerKind ControllerKind { get; init; } + + /// + /// Gets the base tint opacity before user adjustments. + /// + public required float BaseTintOpacity { get; init; } + + /// + /// Gets the base luminosity opacity before user adjustments. + /// + public required float BaseLuminosityOpacity { get; init; } + + /// + /// Gets the brush type to use for preview approximation. + /// + public required PreviewBrushKind PreviewBrush { get; init; } + + /// + /// Gets the fixed opacity for styles that don't support user adjustment (e.g., Mica). + /// When is false, this value is used as the effective opacity. + /// + public float FixedOpacity { get; init; } + + /// + /// Gets whether this backdrop style supports custom colorization (tint colors). + /// + public bool SupportsColorization { get; init; } = true; + + /// + /// Gets whether this backdrop style supports custom background images. + /// + public bool SupportsBackgroundImage { get; init; } = true; + + /// + /// Gets whether this backdrop style supports opacity adjustment. + /// + public bool SupportsOpacity { get; init; } = true; + + /// + /// Computes the effective tint opacity based on this style's configuration. + /// + /// User's backdrop opacity setting (0-1 normalized). + /// Optional override for base tint opacity (used by colorful theme). + /// The effective opacity to apply. + public float ComputeEffectiveOpacity(float userOpacity, float? baseTintOpacityOverride = null) + { + // For styles that don't support opacity (Mica), use FixedOpacity + if (!SupportsOpacity && FixedOpacity > 0) + { + return FixedOpacity; + } + + // For Solid: only user opacity matters (controls alpha of solid color) + if (ControllerKind == BackdropControllerKind.Solid) + { + return userOpacity; + } + + // For blur effects: multiply base opacity with user opacity + var baseTint = baseTintOpacityOverride ?? BaseTintOpacity; + return baseTint * userOpacity; + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs new file mode 100644 index 0000000000..6ba46c156e --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/BackdropStyles.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Central registry of backdrop style configurations. +/// +public static class BackdropStyles +{ + private static readonly Dictionary Configs = new() + { + [BackdropStyle.Acrylic] = new() + { + ControllerKind = BackdropControllerKind.Acrylic, + BaseTintOpacity = 0.5f, + BaseLuminosityOpacity = 0.9f, + PreviewBrush = PreviewBrushKind.Acrylic, + }, + [BackdropStyle.AcrylicThin] = new() + { + ControllerKind = BackdropControllerKind.AcrylicThin, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 0.85f, + PreviewBrush = PreviewBrushKind.Acrylic, + }, + [BackdropStyle.Mica] = new() + { + ControllerKind = BackdropControllerKind.Mica, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + FixedOpacity = 0.96f, + SupportsOpacity = false, + }, + [BackdropStyle.MicaAlt] = new() + { + ControllerKind = BackdropControllerKind.MicaAlt, + BaseTintOpacity = 0.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + FixedOpacity = 0.98f, + SupportsOpacity = false, + }, + [BackdropStyle.Clear] = new() + { + ControllerKind = BackdropControllerKind.Solid, + BaseTintOpacity = 1.0f, + BaseLuminosityOpacity = 1.0f, + PreviewBrush = PreviewBrushKind.Solid, + }, + }; + + /// + /// Gets the configuration for the specified backdrop style. + /// + public static BackdropStyleConfig Get(BackdropStyle style) => + Configs.TryGetValue(style, out var config) ? config : Configs[BackdropStyle.Acrylic]; + + /// + /// Gets all registered backdrop styles. + /// + public static IEnumerable All => Configs.Keys; +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs index 2ee8f1e357..325f9b5ff8 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using CommunityToolkit.Mvvm.Messaging; using ManagedCommon; using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CmdPal.Core.Common.Text; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Ext.Apps; using Microsoft.CmdPal.Ext.Apps.Programs; @@ -24,7 +25,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.MainPage; /// This class encapsulates the data we load from built-in providers and extensions to use within the same extension-UI system for a . /// TODO: Need to think about how we structure/interop for the page -> section -> item between the main setup, the extensions, and our viewmodels. /// -public partial class MainListPage : DynamicListPage, +public sealed partial class MainListPage : DynamicListPage, IRecipient, IRecipient, IDisposable { @@ -32,13 +33,18 @@ public partial class MainListPage : DynamicListPage, private readonly AliasManager _aliasManager; private readonly SettingsModel _settings; private readonly AppStateModel _appStateModel; - private List>? _filteredItems; - private List>? _filteredApps; + private readonly ScoringFunction _scoringFunction; + private readonly ScoringFunction _fallbackScoringFunction; + private readonly IFuzzyMatcherProvider _fuzzyMatcherProvider; + + private RoScored[]? _filteredItems; + private RoScored[]? _filteredApps; // Keep as IEnumerable for deferred execution. Fallback item titles are updated // asynchronously, so scoring must happen lazily when GetItems is called. - private IEnumerable>? _scoredFallbackItems; - private IEnumerable>? _fallbackItems; + private IEnumerable>? _scoredFallbackItems; + private IEnumerable>? _fallbackItems; + private bool _includeApps; private bool _filteredItemsIncludesApps; private int _appResultLimit = 10; @@ -48,7 +54,12 @@ public partial class MainListPage : DynamicListPage, private CancellationTokenSource? _cancellationTokenSource; - public MainListPage(TopLevelCommandManager topLevelCommandManager, SettingsModel settings, AliasManager aliasManager, AppStateModel appStateModel) + public MainListPage( + TopLevelCommandManager topLevelCommandManager, + SettingsModel settings, + AliasManager aliasManager, + AppStateModel appStateModel, + IFuzzyMatcherProvider fuzzyMatcherProvider) { Title = Resources.builtin_home_name; Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.scale-200.png"); @@ -58,6 +69,10 @@ public partial class MainListPage : DynamicListPage, _aliasManager = aliasManager; _appStateModel = appStateModel; _tlcManager = topLevelCommandManager; + _fuzzyMatcherProvider = fuzzyMatcherProvider; + _scoringFunction = (in query, item) => ScoreTopLevelItem(in query, item, _appStateModel.RecentCommands, _fuzzyMatcherProvider.Current); + _fallbackScoringFunction = (in _, item) => ScoreFallbackItem(item, _settings.FallbackRanks); + _tlcManager.PropertyChanged += TlcManager_PropertyChanged; _tlcManager.TopLevelCommands.CollectionChanged += Commands_CollectionChanged; @@ -190,8 +205,7 @@ public partial class MainListPage : DynamicListPage, public override void UpdateSearchText(string oldSearch, string newSearch) { - var timer = new Stopwatch(); - timer.Start(); + var stopwatch = Stopwatch.StartNew(); _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); @@ -354,15 +368,14 @@ public partial class MainListPage : DynamicListPage, if (_includeApps) { - var allNewApps = AllAppsCommandProvider.Page.GetItems().ToList(); + var allNewApps = AllAppsCommandProvider.Page.GetItems().Cast().ToList(); // We need to remove pinned apps from allNewApps so they don't show twice. var pinnedApps = PinnedAppsManager.Instance.GetPinnedAppIdentifiers(); if (pinnedApps.Length > 0) { - newApps = allNewApps.Where(w => - pinnedApps.IndexOf(((AppListItem)w).AppIdentifier) < 0); + newApps = allNewApps.Where(w => pinnedApps.IndexOf(w.AppIdentifier) < 0); } else { @@ -376,11 +389,10 @@ public partial class MainListPage : DynamicListPage, } } - var history = _appStateModel.RecentCommands!; - Func scoreItem = (a, b) => { return ScoreTopLevelItem(a, b, history); }; + var searchQuery = _fuzzyMatcherProvider.Current.PrecomputeQuery(SearchText); // Produce a list of everything that matches the current filter. - _filteredItems = [.. ListHelpers.FilterListWithScores(newFilteredItems ?? [], SearchText, scoreItem)]; + _filteredItems = InternalListHelpers.FilterListWithScores(newFilteredItems, searchQuery, _scoringFunction); if (token.IsCancellationRequested) { @@ -388,21 +400,14 @@ public partial class MainListPage : DynamicListPage, } IEnumerable newFallbacksForScoring = commands.Where(s => s.IsFallback && globalFallbacks.Contains(s.Id)); + _scoredFallbackItems = InternalListHelpers.FilterListWithScores(newFallbacksForScoring, searchQuery, _scoringFunction); if (token.IsCancellationRequested) { return; } - _scoredFallbackItems = ListHelpers.FilterListWithScores(newFallbacksForScoring ?? [], SearchText, scoreItem); - - if (token.IsCancellationRequested) - { - return; - } - - Func scoreFallbackItem = (a, b) => { return ScoreFallbackItem(a, b, _settings.FallbackRanks); }; - _fallbackItems = [.. ListHelpers.FilterListWithScores(newFallbacks ?? [], SearchText, scoreFallbackItem)]; + _fallbackItems = InternalListHelpers.FilterListWithScores(newFallbacks ?? [], searchQuery, _fallbackScoringFunction); if (token.IsCancellationRequested) { @@ -412,18 +417,7 @@ public partial class MainListPage : DynamicListPage, // Produce a list of filtered apps with the appropriate limit if (newApps.Any()) { - var scoredApps = ListHelpers.FilterListWithScores(newApps, SearchText, scoreItem); - - if (token.IsCancellationRequested) - { - return; - } - - // We'll apply this limit in the GetItems method after merging with commands - // but we need to know the limit now to avoid re-scoring apps - var appLimit = AllAppsCommandProvider.TopLevelResultLimit; - - _filteredApps = [.. scoredApps]; + _filteredApps = InternalListHelpers.FilterListWithScores(newApps, searchQuery, _scoringFunction); if (token.IsCancellationRequested) { @@ -431,10 +425,15 @@ public partial class MainListPage : DynamicListPage, } } + var filterDoneTimestamp = stopwatch.ElapsedMilliseconds; + Logger.LogDebug($"Filter with '{newSearch}' in {filterDoneTimestamp}ms"); + RaiseItemsChanged(); - timer.Stop(); - Logger.LogDebug($"Filter with '{newSearch}' in {timer.ElapsedMilliseconds}ms"); + var listPageUpdatedTimestamp = stopwatch.ElapsedMilliseconds; + Logger.LogDebug($"Render items with '{newSearch}' in {listPageUpdatedTimestamp}ms /d {listPageUpdatedTimestamp - filterDoneTimestamp}ms"); + + stopwatch.Stop(); } } @@ -478,7 +477,11 @@ public partial class MainListPage : DynamicListPage, // Almost verbatim ListHelpers.ScoreListItem, but also accounting for the // fact that we want fallback handlers down-weighted, so that they don't // _always_ show up first. - internal static int ScoreTopLevelItem(string query, IListItem topLevelOrAppItem, IRecentCommandsManager history) + internal static int ScoreTopLevelItem( + in FuzzyQuery query, + IListItem topLevelOrAppItem, + IRecentCommandsManager history, + IPrecomputedFuzzyMatcher precomputedFuzzyMatcher) { var title = topLevelOrAppItem.Title; if (string.IsNullOrWhiteSpace(title)) @@ -486,94 +489,80 @@ public partial class MainListPage : DynamicListPage, return 0; } - var isWhiteSpace = string.IsNullOrWhiteSpace(query); - var isFallback = false; var isAliasSubstringMatch = false; var isAliasMatch = false; var id = IdForTopLevelOrAppItem(topLevelOrAppItem); - var extensionDisplayName = string.Empty; + FuzzyTarget? extensionDisplayNameTarget = null; if (topLevelOrAppItem is TopLevelViewModel topLevel) { isFallback = topLevel.IsFallback; + extensionDisplayNameTarget = topLevel.GetExtensionNameTarget(precomputedFuzzyMatcher); + if (topLevel.HasAlias) { var alias = topLevel.AliasText; - isAliasMatch = alias == query; - isAliasSubstringMatch = isAliasMatch || alias.StartsWith(query, StringComparison.CurrentCultureIgnoreCase); + isAliasMatch = alias == query.Original; + isAliasSubstringMatch = isAliasMatch || alias.StartsWith(query.Original, StringComparison.CurrentCultureIgnoreCase); } - - extensionDisplayName = topLevel.ExtensionHost?.Extension?.PackageDisplayName ?? string.Empty; } - // StringMatcher.FuzzySearch will absolutely BEEF IT if you give it a - // whitespace-only query. - // - // in that scenario, we'll just use a simple string contains for the - // query. Maybe someone is really looking for things with a space in - // them, I don't know. - - // Title: - // * whitespace query: 1 point - // * otherwise full weight match - var nameMatch = isWhiteSpace ? - (title.Contains(query) ? 1 : 0) : - FuzzyStringMatcher.ScoreFuzzy(query, title); - - // Subtitle: - // * whitespace query: 1/2 point - // * otherwise ~half weight match. Minus a bit, because subtitles tend to be longer - var descriptionMatch = isWhiteSpace ? - (topLevelOrAppItem.Subtitle.Contains(query) ? .5 : 0) : - (FuzzyStringMatcher.ScoreFuzzy(query, topLevelOrAppItem.Subtitle) - 4) / 2.0; - - // Extension title: despite not being visible, give the extension name itself some weight - // * whitespace query: 0 points - // * otherwise more weight than a subtitle, but not much - var extensionTitleMatch = isWhiteSpace ? 0 : FuzzyStringMatcher.ScoreFuzzy(query, extensionDisplayName) / 1.5; - - var scores = new[] + // Handle whitespace query separately - FuzzySearch doesn't handle it well + if (string.IsNullOrWhiteSpace(query.Original)) { - nameMatch, - descriptionMatch, - isFallback ? 1 : 0, // Always give fallbacks a chance - }; - var max = scores.Max(); + return ScoreWhitespaceQuery(query.Original, title, topLevelOrAppItem.Subtitle, isFallback); + } - // _Add_ the extension name. This will bubble items that match both - // title and extension name up above ones that just match title. - // e.g. "git" will up-weight "GitHub searches" from the GitHub extension - // above "git" from "whatever" - max = max + extensionTitleMatch; + // Get precomputed targets + var (titleTarget, subtitleTarget) = topLevelOrAppItem is IPrecomputedListItem precomputedItem + ? (precomputedItem.GetTitleTarget(precomputedFuzzyMatcher), precomputedItem.GetSubtitleTarget(precomputedFuzzyMatcher)) + : (precomputedFuzzyMatcher.PrecomputeTarget(title), precomputedFuzzyMatcher.PrecomputeTarget(topLevelOrAppItem.Subtitle)); + + // Score components + var nameScore = precomputedFuzzyMatcher.Score(query, titleTarget); + var descriptionScore = (precomputedFuzzyMatcher.Score(query, subtitleTarget) - 4) / 2.0; + var extensionScore = extensionDisplayNameTarget is { } extTarget ? precomputedFuzzyMatcher.Score(query, extTarget) / 1.5 : 0; + + // Take best match from title/description/fallback, then add extension score + // Extension adds to max so items matching both title AND extension bubble up + var baseScore = Math.Max(Math.Max(nameScore, descriptionScore), isFallback ? 1 : 0); + var matchScore = baseScore + extensionScore; // Apply a penalty to fallback items so they rank below direct matches. // Fallbacks that dynamically match queries (like RDP connections) should // appear after apps and direct command matches. - if (isFallback && max > 1) + if (isFallback && matchScore > 1) { // Reduce fallback scores by 50% to prioritize direct matches - max = max * 0.5; + matchScore = matchScore * 0.5; } - var matchSomething = max - + (isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0)); + // Alias matching: exact match is overwhelming priority, substring match adds a small boost + var aliasBoost = isAliasMatch ? 9001 : (isAliasSubstringMatch ? 1 : 0); + var totalMatch = matchScore + aliasBoost; - // If we matched title, subtitle, or alias (something real), then - // here we add the recent command weight boost - // - // Otherwise something like `x` will still match everything you've run before - var finalScore = matchSomething * 10; - if (matchSomething > 0) + // Apply scaling and history boost only if we matched something real + var finalScore = totalMatch * 10; + if (totalMatch > 0) { - var recentWeightBoost = history.GetCommandHistoryWeight(id); - finalScore += recentWeightBoost; + finalScore += history.GetCommandHistoryWeight(id); } return (int)finalScore; } - internal static int ScoreFallbackItem(string query, IListItem topLevelOrAppItem, string[] fallbackRanks) + private static int ScoreWhitespaceQuery(string query, string title, string subtitle, bool isFallback) + { + // Simple contains check for whitespace queries + var nameMatch = title.Contains(query, StringComparison.Ordinal) ? 1.0 : 0; + var descriptionMatch = subtitle.Contains(query, StringComparison.Ordinal) ? 0.5 : 0; + var baseScore = Math.Max(Math.Max(nameMatch, descriptionMatch), isFallback ? 1 : 0); + + return (int)(baseScore * 10); + } + + private static int ScoreFallbackItem(IListItem topLevelOrAppItem, string[] fallbackRanks) { // Default to 1 so it always shows in list. var finalScore = 1; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs index d63c0e4f90..0c0d876179 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs @@ -4,6 +4,7 @@ #pragma warning disable IDE0007 // Use implicit type +using Microsoft.CmdPal.Core.Common.Helpers; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -16,10 +17,10 @@ internal static class MainListPageResultFactory /// applying an application result limit and filtering fallback items as needed. /// public static IListItem[] Create( - IList>? filteredItems, - IList>? scoredFallbackItems, - IList>? filteredApps, - IList>? fallbackItems, + IList>? filteredItems, + IList>? scoredFallbackItems, + IList>? filteredApps, + IList>? fallbackItems, int appResultLimit) { if (appResultLimit < 0) @@ -147,7 +148,7 @@ internal static class MainListPageResultFactory return result; } - private static int GetNonEmptyFallbackItemsCount(IList>? fallbackItems) + private static int GetNonEmptyFallbackItemsCount(IList>? fallbackItems) { int fallbackItemsCount = 0; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs index 140811c784..4d775083f0 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/MainWindowViewModel.cs @@ -22,6 +22,7 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable public partial Stretch BackgroundImageStretch { get; private set; } = Stretch.Fill; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] public partial double BackgroundImageOpacity { get; private set; } [ObservableProperty] @@ -39,6 +40,30 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable [ObservableProperty] public partial bool ShowBackgroundImage { get; private set; } + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveBackdropStyle))] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] + public partial BackdropStyle BackdropStyle { get; private set; } + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(EffectiveBackdropStyle))] + [NotifyPropertyChangedFor(nameof(EffectiveImageOpacity))] + public partial float BackdropOpacity { get; private set; } = 1.0f; + + // Returns null when no transparency needed (BlurImageControl uses this to decide source type) + public BackdropStyle? EffectiveBackdropStyle => + BackdropStyle == BackdropStyle.Clear || + BackdropStyle == BackdropStyle.Mica || + BackdropOpacity < 1.0f + ? BackdropStyle + : null; + + // When transparency is enabled, use square root curve so image stays visible longer as backdrop fades + public double EffectiveImageOpacity => + EffectiveBackdropStyle is not null + ? BackgroundImageOpacity * Math.Sqrt(BackdropOpacity) + : BackgroundImageOpacity; + public MainWindowViewModel(IThemeService themeService) { _themeService = themeService; @@ -58,6 +83,9 @@ public partial class MainWindowViewModel : ObservableObject, IDisposable BackgroundImageTintIntensity = _themeService.Current.TintIntensity; BackgroundImageBlurAmount = _themeService.Current.BlurAmount; + BackdropStyle = _themeService.Current.BackdropParameters.Style; + BackdropOpacity = _themeService.Current.BackdropOpacity; + ShowBackgroundImage = BackgroundImageSource != null; }); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj index 1c85aa939b..187c835e37 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj @@ -1,6 +1,6 @@ - - - + + + enable diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs new file mode 100644 index 0000000000..2e8a644a28 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PreviewBrushKind.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels; + +/// +/// Specifies the brush type to use for backdrop preview approximation. +/// +public enum PreviewBrushKind +{ + /// + /// SolidColorBrush with computed alpha. + /// + Solid, + + /// + /// AcrylicBrush with blur effect. + /// + Acrylic, +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs index 8d9ff6d6bb..31dc0f9b9c 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs @@ -285,6 +285,42 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } } + /// + /// Looks up a localized string similar to Built-in. + /// + public static string builtin_extension_name { + get { + return ResourceManager.GetString("builtin_extension_name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}, {1} commands. + /// + public static string builtin_extension_subtext { + get { + return ResourceManager.GetString("builtin_extension_subtext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}, {1}. + /// + public static string builtin_extension_subtext_disabled { + get { + return ResourceManager.GetString("builtin_extension_subtext_disabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}, {1} commands, {2} fallback commands. + /// + public static string builtin_extension_subtext_with_fallback { + get { + return ResourceManager.GetString("builtin_extension_subtext_with_fallback", resourceCulture); + } + } + /// /// Looks up a localized string similar to Home. /// diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx index 9f6b68c1bd..92a5350d53 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx @@ -126,6 +126,21 @@ Built-in commands + + Built-in + + + {0}, {1} commands + {0}=extension name, {1}=number of commands + + + {0}, {1} commands, {2} fallback commands + {0}=extension name, {1}=number of commands, {2} number of fallback commands + + + {0}, {1} + {0}=extension name, {1}=message + View log messages diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs index d6208b712a..1a2a82fa7f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Core.Common.Services; @@ -15,6 +17,9 @@ namespace Microsoft.CmdPal.UI.ViewModels; public partial class ProviderSettingsViewModel : ObservableObject { private static readonly IconInfoViewModel EmptyIcon = new(null); + private static readonly CompositeFormat ExtensionSubtextFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext); + private static readonly CompositeFormat ExtensionSubtextWithFallbackFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_with_fallback); + private static readonly CompositeFormat ExtensionSubtextDisabledFormat = CompositeFormat.Parse(Resources.builtin_extension_subtext_disabled); private readonly CommandProviderWrapper _provider; private readonly ProviderSettings _providerSettings; @@ -39,13 +44,13 @@ public partial class ProviderSettingsViewModel : ObservableObject public string DisplayName => _provider.DisplayName; - public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? "Built-in"; + public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? Resources.builtin_extension_name; public string ExtensionSubtext => IsEnabled ? HasFallbackCommands ? - $"{ExtensionName}, {TopLevelCommands.Count} commands, {_provider.FallbackItems?.Length} fallback commands" : - $"{ExtensionName}, {TopLevelCommands.Count} commands" : - $"{ExtensionName}, {Resources.builtin_disabled_extension}"; + string.Format(CultureInfo.CurrentCulture, ExtensionSubtextWithFallbackFormat, ExtensionName, TopLevelCommands.Count, _provider.FallbackItems?.Length ?? 0) : + string.Format(CultureInfo.CurrentCulture, ExtensionSubtextFormat, ExtensionName, TopLevelCommands.Count) : + string.Format(CultureInfo.CurrentCulture, ExtensionSubtextDisabledFormat, ExtensionName, Resources.builtin_disabled_extension); [MemberNotNullWhen(true, nameof(Extension))] public bool IsFromExtension => _provider.Extension is not null; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs deleted file mode 100644 index efb7ca1fa1..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/AcrylicBackdropParameters.cs +++ /dev/null @@ -1,9 +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 Windows.UI; - -namespace Microsoft.CmdPal.UI.ViewModels.Services; - -public sealed record AcrylicBackdropParameters(Color TintColor, Color FallbackColor, float TintOpacity, float LuminosityOpacity); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs new file mode 100644 index 0000000000..dde4df0e0e --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/BackdropParameters.cs @@ -0,0 +1,29 @@ +// 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 Windows.UI; + +namespace Microsoft.CmdPal.UI.ViewModels.Services; + +/// +/// Parameters for configuring the window backdrop appearance. +/// +/// The tint color applied to the backdrop. +/// The fallback color when backdrop effects are unavailable. +/// +/// The effective opacity for the backdrop, pre-computed by the theme provider. +/// For Acrylic style: TintOpacity * BackdropOpacity. +/// For Clear style: BackdropOpacity (controls the solid color alpha). +/// +/// +/// The effective luminosity opacity for Acrylic backdrop, pre-computed by the theme provider. +/// Computed as LuminosityOpacity * BackdropOpacity. +/// +/// The backdrop style (Acrylic or Clear). +public sealed record BackdropParameters( + Color TintColor, + Color FallbackColor, + float EffectiveOpacity, + float EffectiveLuminosityOpacity, + BackdropStyle Style = BackdropStyle.Acrylic); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs index 244fd41fba..a82484d52f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ThemeSnapshot.cs @@ -51,12 +51,23 @@ public sealed class ThemeSnapshot public required double BackgroundImageOpacity { get; init; } /// - /// Gets the effective acrylic backdrop parameters based on current settings and theme. + /// Gets the effective backdrop parameters based on current settings and theme. /// - /// The resolved AcrylicBackdropParameters to apply. - public required AcrylicBackdropParameters BackdropParameters { get; init; } + /// The resolved BackdropParameters to apply. + public required BackdropParameters BackdropParameters { get; init; } + + /// + /// Gets the raw backdrop opacity setting (0-1 range). + /// Used for determining if transparency is enabled and for image opacity calculations. + /// + public required float BackdropOpacity { get; init; } public required int BlurAmount { get; init; } public required float BackgroundBrightness { get; init; } + + /// + /// Gets whether colorization is active (accent color, custom color, or image mode). + /// + public required bool HasColorization { get; init; } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs index 483fe3fdc3..c023c3daae 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs @@ -74,6 +74,8 @@ public partial class SettingsModel : ObservableObject public int CustomThemeColorIntensity { get; set; } = 100; + public int BackgroundImageTintIntensity { get; set; } + public int BackgroundImageOpacity { get; set; } = 20; public int BackgroundImageBlurAmount { get; set; } @@ -84,6 +86,10 @@ public partial class SettingsModel : ObservableObject public string? BackgroundImagePath { get; set; } + public BackdropStyle BackdropStyle { get; set; } + + public int BackdropOpacity { get; set; } = 100; + // END SETTINGS /////////////////////////////////////////////////////////////////////////// diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs index cc863fe362..13b9423119 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelViewModel.cs @@ -3,8 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; +using System.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using ManagedCommon; +using Microsoft.CmdPal.Core.Common.Helpers; +using Microsoft.CmdPal.Core.Common.Text; using Microsoft.CmdPal.Core.ViewModels; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Settings; @@ -16,7 +19,8 @@ using WyHash; namespace Microsoft.CmdPal.UI.ViewModels; -public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IExtendedAttributesProvider +[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] +public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IExtendedAttributesProvider, IPrecomputedListItem { private readonly SettingsModel _settings; private readonly ProviderSettings _providerSettings; @@ -34,6 +38,10 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx private HotkeySettings? _hotkey; private IIconInfo? _initialIcon; + private FuzzyTargetCache _titleCache; + private FuzzyTargetCache _subtitleCache; + private FuzzyTargetCache _extensionNameCache; + private CommandAlias? Alias { get; set; } public bool IsFallback { get; private set; } @@ -176,6 +184,8 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx } } + public string ExtensionName => ExtensionHost.GetExtensionDisplayName() ?? string.Empty; + public TopLevelViewModel( CommandItemViewModel item, bool isFallback, @@ -230,6 +240,15 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx { PropChanged?.Invoke(this, new PropChangedEventArgs(e.PropertyName)); + if (e.PropertyName is nameof(CommandItemViewModel.Title) or nameof(CommandItemViewModel.Name)) + { + _titleCache.Invalidate(); + } + else if (e.PropertyName is nameof(CommandItemViewModel.Subtitle)) + { + _subtitleCache.Invalidate(); + } + if (e.PropertyName is "IsInitialized" or nameof(CommandItemViewModel.Command)) { GenerateId(); @@ -420,4 +439,18 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem, IEx [WellKnownExtensionAttributes.DataPackage] = _commandItemViewModel?.DataPackage, }; } + + public FuzzyTarget GetTitleTarget(IPrecomputedFuzzyMatcher matcher) + => _titleCache.GetOrUpdate(matcher, Title); + + public FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher) + => _subtitleCache.GetOrUpdate(matcher, Subtitle); + + public FuzzyTarget GetExtensionNameTarget(IPrecomputedFuzzyMatcher matcher) + => _extensionNameCache.GetOrUpdate(matcher, ExtensionName); + + private string GetDebuggerDisplay() + { + return ToString(); + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml index d8d4655291..efddeddb84 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml @@ -5,6 +5,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Microsoft.CmdPal.UI.Controls" xmlns:local="using:Microsoft.CmdPal.UI" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:services="using:Microsoft.CmdPal.UI.Services"> @@ -16,8 +17,10 @@ - - + + + + @@ -25,7 +28,7 @@ 240 - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml index f470c54791..be70b0ca08 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml @@ -5,49 +5,90 @@ xmlns:controls="using:Microsoft.CmdPal.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" x:Name="LayoutRoot" d:DesignHeight="300" d:DesignWidth="400" mc:Ignorable="d"> - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs index e7fa721277..b8aad6c323 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutControl.xaml.cs @@ -11,6 +11,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; +using Microsoft.Windows.ApplicationModel.Resources; using Windows.System; namespace Microsoft.CmdPal.UI.Controls; @@ -36,6 +37,8 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie public static readonly DependencyProperty AllowDisableProperty = DependencyProperty.Register("AllowDisable", typeof(bool), typeof(ShortcutControl), new PropertyMetadata(false, OnAllowDisableChanged)); + private static ResourceLoader resourceLoader = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance.ResourceLoader; + private static void OnAllowDisableChanged(DependencyObject d, DependencyPropertyChangedEventArgs? e) { var me = d as ShortcutControl; @@ -96,8 +99,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie { hotkeySettings = value; SetValue(HotkeySettingsProperty, value); - PreviewKeysControl.ItemsSource = HotkeySettings?.GetKeysList() ?? new List(); - AutomationProperties.SetHelpText(EditButton, HotkeySettings?.ToString() ?? string.Empty); + SetKeys(); c.Keys = HotkeySettings?.GetKeysList() ?? new List(); } } @@ -108,8 +110,6 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie InitializeComponent(); internalSettings = new HotkeySettings(); - var resourceLoader = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance.ResourceLoader; - // We create the Dialog in C# because doing it in XAML is giving WinUI/XAML Island bugs when using dark theme. shortcutDialog = new ContentDialog { @@ -421,11 +421,9 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie hotkeySettings = null; SetValue(HotkeySettingsProperty, hotkeySettings); - PreviewKeysControl.ItemsSource = HotkeySettings?.GetKeysList() ?? new List(); + SetKeys(); lastValidSettings = hotkeySettings; - - AutomationProperties.SetHelpText(EditButton, HotkeySettings?.ToString() ?? string.Empty); shortcutDialog.Hide(); } @@ -436,8 +434,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie HotkeySettings = lastValidSettings with { }; } - PreviewKeysControl.ItemsSource = hotkeySettings?.GetKeysList() ?? new List(); - AutomationProperties.SetHelpText(EditButton, HotkeySettings?.ToString() ?? string.Empty); + SetKeys(); shortcutDialog.Hide(); } @@ -450,9 +447,7 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie var empty = new HotkeySettings(); HotkeySettings = empty; - - PreviewKeysControl.ItemsSource = HotkeySettings.GetKeysList(); - AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString()); + SetKeys(); shortcutDialog.Hide(); } @@ -508,4 +503,23 @@ public sealed partial class ShortcutControl : UserControl, IDisposable, IRecipie Dispose(disposing: true); GC.SuppressFinalize(this); } + + private void SetKeys() + { + var keys = HotkeySettings?.GetKeysList(); + + if (keys != null && keys.Count > 0) + { + VisualStateManager.GoToState(this, "Configured", true); + PreviewKeysControl.ItemsSource = keys; +#pragma warning disable CS8602 // Dereference of a possibly null reference. + AutomationProperties.SetHelpText(EditButton, HotkeySettings.ToString()); +#pragma warning restore CS8602 // Dereference of a possibly null reference. + } + else + { + VisualStateManager.GoToState(this, "Normal", true); + AutomationProperties.SetHelpText(EditButton, resourceLoader.GetString("ConfigureShortcut")); + } + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml index 0000685ac3..d2a5b7203f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutDialogContentControl.xaml @@ -2,12 +2,16 @@ x:Class="Microsoft.CmdPal.UI.Controls.ShortcutDialogContentControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:Microsoft.CmdPal.UI.Controls" + xmlns:converters="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" x:Name="ShortcutContentControl" mc:Ignorable="d"> + + + @@ -33,13 +37,16 @@ - + State="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay, Converter={StaticResource BoolToKeyVisualStateConverter}, ConverterParameter=Error}" + Style="{StaticResource AccentKeyVisualStyle}" /> diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml deleted file mode 100644 index c6f2a9fe2a..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs deleted file mode 100644 index 5605d5b195..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ShortcutControl/ShortcutWithTextLabelControl.xaml.cs +++ /dev/null @@ -1,35 +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.Collections.Generic; - -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; - -namespace Microsoft.CmdPal.UI.Controls -{ - public sealed partial class ShortcutWithTextLabelControl : UserControl - { - public string Text - { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } - - public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string))); - - public List Keys - { - get { return (List)GetValue(KeysProperty); } - set { SetValue(KeysProperty, value); } - } - - public static readonly DependencyProperty KeysProperty = DependencyProperty.Register("Keys", typeof(List), typeof(ShortcutWithTextLabelControl), new PropertyMetadata(default(string))); - - public ShortcutWithTextLabelControl() - { - this.InitializeComponent(); - } - } -} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs index ae585e7f11..dc8d0d0d77 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemContainerStyleSelector.cs @@ -24,11 +24,19 @@ internal sealed partial class GridItemContainerStyleSelector : StyleSelector protected override Style? SelectStyleCore(object item, DependencyObject container) { - if (item is ListItemViewModel { IsSectionOrSeparator: true } listItem) + if (item is not ListItemViewModel element) { - return string.IsNullOrWhiteSpace(listItem.Title) - ? Separator! - : Section; + return Medium; + } + + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + default: + break; } return GridProperties switch diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs index f638f3f09e..1e568981c7 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/GridItemTemplateSelector.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CmdPal.Core.ViewModels; +using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -24,15 +25,19 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject) { - if (item is ListItemViewModel element && element.IsSectionOrSeparator) + if (item is not ListItemViewModel element) { - if (dependencyObject is UIElement li) - { - li.IsTabStop = false; - li.IsHitTestVisible = false; - } + return Medium; + } - return string.IsNullOrWhiteSpace(element.Section) ? Separator : Section; + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + default: + break; } return GridProperties switch diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs index 45a785c2b4..dd2d515417 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemContainerStyleSelector.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CmdPal.Core.ViewModels; +using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -18,11 +19,20 @@ internal sealed partial class ListItemContainerStyleSelector : StyleSelector protected override Style? SelectStyleCore(object item, DependencyObject container) { - return item switch + if (item is not ListItemViewModel element) { - ListItemViewModel { IsSectionOrSeparator: true } listItemViewModel when string.IsNullOrWhiteSpace(listItemViewModel.Title) => Separator!, - ListItemViewModel { IsSectionOrSeparator: true } => Section, - _ => Default, - }; + return Default; + } + + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + case ListItemType.Item: + default: + return Default; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs index 7fb810f5dc..47e12c148a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Converters/ListItemTemplateSelector.cs @@ -18,30 +18,20 @@ public sealed partial class ListItemTemplateSelector : DataTemplateSelector protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container) { - DataTemplate? dataTemplate = ListItem; - - if (container is ListViewItem listItem) + if (item is not ListItemViewModel element) { - if (item is ListItemViewModel element) - { - if (container is ListViewItem li && element.IsSectionOrSeparator) - { - li.IsEnabled = false; - li.AllowFocusWhenDisabled = false; - li.AllowFocusOnInteraction = false; - li.IsHitTestVisible = false; - dataTemplate = string.IsNullOrWhiteSpace(element.Section) ? Separator : Section; - } - else - { - listItem.IsEnabled = true; - listItem.AllowFocusWhenDisabled = true; - listItem.AllowFocusOnInteraction = true; - listItem.IsHitTestVisible = true; - } - } + return ListItem; } - return dataTemplate; + switch (element.Type) + { + case ListItemType.Separator: + return Separator; + case ListItemType.SectionHeader: + return Section; + case ListItemType.Item: + default: + return ListItem; + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml index 2f670d62b4..022b214c79 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/ExtViews/ListPage.xaml @@ -206,23 +206,30 @@ x:Key="ListSectionContainerStyle" BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem"> + + + + - - - - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml index adc802eba9..0177295546 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml @@ -39,13 +39,9 @@ Grid.Row="1" Padding="4" IsClosable="False" - IsOpen="True" - Message="Foundry Local is still in Public Preview"> + IsOpen="True"> - + - - - - - - { - DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () => + DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () => App.OpenSettingsWindow(type)); }); @@ -106,30 +107,8 @@ namespace Microsoft.PowerToys.Settings.UI return needToUpdate; }); - // open oobe - ShellPage.SetOpenOobeCallback(() => - { - if (App.GetOobeWindow() == null) - { - App.SetOobeWindow(new OobeWindow(OOBE.Enums.PowerToysModules.Overview)); - } - - App.GetOobeWindow().Activate(); - }); - - // open whats new window - ShellPage.SetOpenWhatIsNewCallback(() => - { - if (App.GetScoobeWindow() == null) - { - App.SetScoobeWindow(new ScoobeWindow()); - } - - App.GetScoobeWindow().Activate(); - }); - this.InitializeComponent(); - SetAppTitleBar(); + SetTitleBar(); // receive IPC Message App.IPCMessageReceivedCallback = (string msg) => @@ -156,21 +135,22 @@ namespace Microsoft.PowerToys.Settings.UI PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds }); } - private void SetAppTitleBar() + private void SetTitleBar() { // We need to assign the window here so it can configure the custom title bar area correctly. shellPage.TitleBar.Window = this; + this.ExtendsContentIntoTitleBar = true; WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); } - public void NavigateToSection(System.Type type) + public void NavigateToSection(Type type) { ShellPage.Navigate(type); } public void CloseHiddenWindow() { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); if (!NativeMethods.IsWindowVisible(hWnd)) { Close(); @@ -179,10 +159,10 @@ namespace Microsoft.PowerToys.Settings.UI private void Window_Closed(object sender, WindowEventArgs args) { - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); WindowHelper.SerializePlacement(hWnd); - if (App.GetOobeWindow() == null && App.GetScoobeWindow() == null) + if (!App.IsSecondaryWindowOpen()) { App.ClearSettingsWindow(); } @@ -198,10 +178,7 @@ namespace Microsoft.PowerToys.Settings.UI private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) { // Set window icon - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); - WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd); - AppWindow appWindow = AppWindow.GetFromWindowId(windowId); - appWindow.SetIcon("Assets\\Settings\\icon.ico"); + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Activated(object sender, WindowActivatedEventArgs args) @@ -209,7 +186,7 @@ namespace Microsoft.PowerToys.Settings.UI if (args.WindowActivationState != WindowActivationState.Deactivated) { this.Activated -= Window_Activated; - var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + var hWnd = WindowNative.GetWindowHandle(this); var placement = WindowHelper.DeserializePlacementOrDefault(hWnd); NativeMethods.SetWindowPlacement(hWnd, ref placement); } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml index c39c986abd..209fd33745 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml @@ -5,6 +5,7 @@ xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" mc:Ignorable="d"> @@ -12,13 +13,13 @@ - - - + + - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs index 73e25ed856..c0d4a9926c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAdvancedPaste.xaml.cs @@ -18,15 +18,16 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAdvancedPaste() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.AdvancedPaste]); + + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.AdvancedPaste); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AdvancedPastePage)); + OobeWindow.OpenMainWindowCallback(typeof(AdvancedPastePage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml index 191fae9d1d..cbf110a93a 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml @@ -5,6 +5,7 @@ xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" mc:Ignorable="d"> @@ -13,7 +14,7 @@ - + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs index 08fb0f8a80..8a353a2638 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAlwaysOnTop.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAlwaysOnTop() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.AlwaysOnTop]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.AlwaysOnTop); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AlwaysOnTopPage)); + OobeWindow.OpenMainWindowCallback(typeof(AlwaysOnTopPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs index 624e473523..153b7e5472 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeAwake.xaml.cs @@ -17,15 +17,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeAwake() { InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Awake]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.Awake); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(AwakePage)); + OobeWindow.OpenMainWindowCallback(typeof(AwakePage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs index 1dea42fa74..65b30533bf 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdNotFound.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeCmdNotFound() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.CmdNotFound]); + ViewModel = ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.CmdNotFound); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(CmdNotFoundPage)); + OobeWindow.OpenMainWindowCallback(typeof(CmdNotFoundPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml index b8a8218be8..67a751bfcc 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml @@ -5,6 +5,7 @@ xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" mc:Ignorable="d"> @@ -13,7 +14,7 @@ - + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs index ab68213a32..14a87af7b7 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCmdPal.xaml.cs @@ -18,15 +18,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeCmdPal() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.CmdPal]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.CmdPal); DataContext = ViewModel; } private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(CmdPalPage)); + OobeWindow.OpenMainWindowCallback(typeof(CmdPalPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml index 1700a3b05f..917cba3285 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml @@ -5,6 +5,7 @@ xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" mc:Ignorable="d"> @@ -13,7 +14,7 @@ - + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs index ce69157269..0a4ff41970 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeColorPicker.xaml.cs @@ -21,15 +21,15 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views public OobeColorPicker() { this.InitializeComponent(); - ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.ColorPicker]); + ViewModel = App.OobeShellViewModel.GetModule(PowerToysModules.ColorPicker); DataContext = ViewModel; } private void Start_ColorPicker_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.ColorPickerSharedEventCallback != null) + if (OobeWindow.ColorPickerSharedEventCallback != null) { - using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, OobeShellPage.ColorPickerSharedEventCallback())) + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, OobeWindow.ColorPickerSharedEventCallback())) { eventHandle.Set(); } @@ -40,9 +40,9 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.OpenMainWindowCallback != null) + if (OobeWindow.OpenMainWindowCallback != null) { - OobeShellPage.OpenMainWindowCallback(typeof(ColorPickerPage)); + OobeWindow.OpenMainWindowCallback(typeof(ColorPickerPage)); } ViewModel.LogOpeningSettingsEvent(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml index cb32790fb3..c891353243 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml @@ -5,6 +5,7 @@ xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" mc:Ignorable="d"> @@ -12,11 +13,11 @@ - + - + - + - - - - - - - - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs deleted file mode 100644 index a18ea16a22..0000000000 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/ScoobeShellPage.xaml.cs +++ /dev/null @@ -1,194 +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 System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; -using ManagedCommon; -using Microsoft.PowerToys.Settings.UI.Helpers; -using Microsoft.PowerToys.Settings.UI.SerializationContext; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; - -namespace Microsoft.PowerToys.Settings.UI.OOBE.Views -{ - public sealed partial class ScoobeShellPage : Page - { - public static Action OpenMainWindowCallback { get; set; } - - public static void SetOpenMainWindowCallback(Action implementation) - { - OpenMainWindowCallback = implementation; - } - - /// - /// Gets or sets a shell handler to be used to update contents of the shell dynamically from page within the frame. - /// - public static ScoobeShellPage ScoobeShellHandler { get; set; } - - /// - /// Gets the list of release groups loaded from GitHub (grouped by major.minor version). - /// - public IList> ReleaseGroups { get; private set; } - - private bool _isLoading; - - public ScoobeShellPage() - { - InitializeComponent(); - ScoobeShellHandler = this; - } - - private async void ShellPage_Loaded(object sender, RoutedEventArgs e) - { - SetTitleBar(); - await LoadReleasesAsync(); - } - - private async Task LoadReleasesAsync() - { - if (_isLoading) - { - return; - } - - _isLoading = true; - LoadingProgressRing.Visibility = Visibility.Visible; - ErrorInfoBar.IsOpen = false; - navigationView.MenuItems.Clear(); - - try - { - var releases = await FetchReleasesFromGitHubAsync(); - ReleaseGroups = GroupReleasesByMajorMinor(releases); - PopulateNavigationItems(); - } - catch (Exception ex) - { - Logger.LogError("Failed to load releases", ex); - ErrorInfoBar.IsOpen = true; - } - finally - { - LoadingProgressRing.Visibility = Visibility.Collapsed; - _isLoading = false; - } - } - - private static async Task> FetchReleasesFromGitHubAsync() - { - using var proxyClientHandler = new HttpClientHandler - { - DefaultProxyCredentials = CredentialCache.DefaultCredentials, - Proxy = WebRequest.GetSystemWebProxy(), - PreAuthenticate = true, - }; - - using var httpClient = new HttpClient(proxyClientHandler); - httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys"); - - string json = await httpClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases?per_page=20"); - var allReleases = JsonSerializer.Deserialize>(json, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo); - - return allReleases - .OrderByDescending(r => r.PublishedDate) - .ToList(); - } - - private static IList> GroupReleasesByMajorMinor(IList releases) - { - return releases - .GroupBy(r => GetMajorMinorVersion(r)) - .Select(g => g.OrderByDescending(r => r.PublishedDate).ToList() as IList) - .ToList(); - } - - private static string GetMajorMinorVersion(PowerToysReleaseInfo release) - { - string version = GetVersionFromRelease(release); - var parts = version.Split('.'); - if (parts.Length >= 2) - { - return $"{parts[0]}.{parts[1]}"; - } - - return version; - } - - private static string GetVersionFromRelease(PowerToysReleaseInfo release) - { - // TagName is typically like "v0.96.0", Name might be "Release v0.96.0" - string version = release.TagName ?? release.Name ?? "Unknown"; - if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) - { - version = version.Substring(1); - } - - return version; - } - - private void PopulateNavigationItems() - { - if (ReleaseGroups == null || ReleaseGroups.Count == 0) - { - return; - } - - foreach (var releaseGroup in ReleaseGroups) - { - var viewModel = new ScoobeReleaseGroupViewModel(releaseGroup); - navigationView.MenuItems.Add(viewModel); - } - - // Select the first item to trigger navigation - navigationView.SelectedItem = navigationView.MenuItems[0]; - } - - private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) - { - if (args.SelectedItem is ScoobeReleaseGroupViewModel viewModel) - { - NavigationFrame.Navigate(typeof(ScoobeReleaseNotesPage), viewModel.Releases); - } - } - - private async void RetryButton_Click(object sender, RoutedEventArgs e) - { - await LoadReleasesAsync(); - } - - private void SetTitleBar() - { - var window = App.GetScoobeWindow(); - if (window != null) - { - window.ExtendsContentIntoTitleBar = true; - window.SetTitleBar(AppTitleBar); - } - } - - private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) - { - if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal) - { - TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment - AppTitleBar.IsPaneToggleButtonVisible = true; - } - else - { - TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment - AppTitleBar.IsPaneToggleButtonVisible = false; - } - } - - private void TitleBar_PaneButtonClick(TitleBar sender, object args) - { - navigationView.IsPaneOpen = !navigationView.IsPaneOpen; - } - } -} diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml index f277350fbc..93e197f66e 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml @@ -5,13 +5,183 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ui="using:CommunityToolkit.WinUI" xmlns:winuiex="using:WinUIEx" + Width="1100" + Height="700" MinWidth="480" MinHeight="480" + Activated="Window_Activated" Closed="Window_Closed" mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs index 6a63054773..cc98149397 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OobeWindow.xaml.cs @@ -3,125 +3,157 @@ // See the LICENSE file in the project root for more information. using System; - +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; -using Microsoft.PowerToys.Settings.UI.OOBE.Enums; +using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel; using Microsoft.PowerToys.Settings.UI.OOBE.Views; -using Microsoft.UI; -using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; using PowerToys.Interop; -using Windows.Graphics; +using WinRT.Interop; using WinUIEx; -using WinUIEx.Messaging; namespace Microsoft.PowerToys.Settings.UI { - /// - /// An empty window that can be used on its own or navigated to within a Frame. - /// - public sealed partial class OobeWindow : WindowEx, IDisposable + public sealed partial class OobeWindow : WindowEx { - private PowerToysModules initialModule; + public OobeShellViewModel ViewModel => App.OobeShellViewModel; - private const int ExpectedWidth = 1100; - private const int ExpectedHeight = 700; - private const int DefaultDPI = 96; - private int _currentDPI; - private WindowId _windowId; - private IntPtr _hWnd; - private AppWindow _appWindow; - private bool disposedValue; + public static Func RunSharedEventCallback { get; set; } - public OobeWindow(PowerToysModules initialModule) + public static void SetRunSharedEventCallback(Func implementation) + { + RunSharedEventCallback = implementation; + } + + public static Func ColorPickerSharedEventCallback { get; set; } + + public static void SetColorPickerSharedEventCallback(Func implementation) + { + ColorPickerSharedEventCallback = implementation; + } + + public static Action OpenMainWindowCallback { get; set; } + + public static void SetOpenMainWindowCallback(Action implementation) + { + OpenMainWindowCallback = implementation; + } + + public OobeWindow() { App.ThemeService.ThemeChanged += OnThemeChanged; App.ThemeService.ApplyTheme(); this.InitializeComponent(); - _hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); - _windowId = Win32Interop.GetWindowIdFromWindow(_hWnd); - _appWindow = AppWindow.GetFromWindowId(_windowId); - this.Activated += Window_Activated_SetIcon; - this.ExtendsContentIntoTitleBar = true; + SetTitleBar(); - var dpi = NativeMethods.GetDpiForWindow(_hWnd); - _currentDPI = dpi; - float scalingFactor = (float)dpi / DefaultDPI; - int width = (int)(ExpectedWidth * scalingFactor); - int height = (int)(ExpectedHeight * scalingFactor); + RootGrid.DataContext = ViewModel; - SizeInt32 size; - size.Width = width; - size.Height = height; - _appWindow.Resize(size); - - this.initialModule = initialModule; - - this.SizeChanged += OobeWindow_SizeChanged; - - var loader = ResourceLoaderInstance.ResourceLoader; - Title = loader.GetString("OobeWindow_Title"); - - if (shellPage != null) - { - shellPage.NavigateToModule(this.initialModule); - } - - OobeShellPage.SetRunSharedEventCallback(() => + SetRunSharedEventCallback(() => { return Constants.PowerLauncherSharedEvent(); }); - OobeShellPage.SetColorPickerSharedEventCallback(() => + SetColorPickerSharedEventCallback(() => { return Constants.ShowColorPickerSharedEvent(); }); - OobeShellPage.SetOpenMainWindowCallback((Type type) => + SetOpenMainWindowCallback((Type type) => { App.OpenSettingsWindow(type); }); } - public void SetAppWindow(PowerToysModules module) + private void SetTitleBar() { - if (shellPage != null) + WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(AppTitleBar); + Title = ResourceLoaderInstance.ResourceLoader.GetString("OobeWindow_Title"); + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (navigationView.SelectedItem is NavigationViewItem selectedItem) { - shellPage.NavigateToModule(module); + switch (selectedItem.Tag) + { + case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break; + case "AdvancedPaste": NavigationFrame.Navigate(typeof(OobeAdvancedPaste)); break; + case "AlwaysOnTop": NavigationFrame.Navigate(typeof(OobeAlwaysOnTop)); break; + case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break; + case "CmdNotFound": NavigationFrame.Navigate(typeof(OobeCmdNotFound)); break; + case "CmdPal": NavigationFrame.Navigate(typeof(OobeCmdPal)); break; + case "ColorPicker": NavigationFrame.Navigate(typeof(OobeColorPicker)); break; + case "CropAndLock": NavigationFrame.Navigate(typeof(OobeCropAndLock)); break; + case "EnvironmentVariables": NavigationFrame.Navigate(typeof(OobeEnvironmentVariables)); break; + case "FancyZones": NavigationFrame.Navigate(typeof(OobeFancyZones)); break; + case "FileLocksmith": NavigationFrame.Navigate(typeof(OobeFileLocksmith)); break; + case "Run": NavigationFrame.Navigate(typeof(OobeRun)); break; + case "ImageResizer": NavigationFrame.Navigate(typeof(OobeImageResizer)); break; + case "KBM": NavigationFrame.Navigate(typeof(OobeKBM)); break; + case "LightSwitch": NavigationFrame.Navigate(typeof(OobeLightSwitch)); break; + case "PowerRename": NavigationFrame.Navigate(typeof(OobePowerRename)); break; + case "PowerDisplay": NavigationFrame.Navigate(typeof(OobePowerDisplay)); break; + case "QuickAccent": NavigationFrame.Navigate(typeof(OobePowerAccent)); break; + case "FileExplorer": NavigationFrame.Navigate(typeof(OobeFileExplorer)); break; + case "ShortcutGuide": NavigationFrame.Navigate(typeof(OobeShortcutGuide)); break; + case "TextExtractor": NavigationFrame.Navigate(typeof(OobePowerOCR)); break; + case "MouseUtils": NavigationFrame.Navigate(typeof(OobeMouseUtils)); break; + case "MouseWithoutBorders": NavigationFrame.Navigate(typeof(OobeMouseWithoutBorders)); break; + case "MeasureTool": NavigationFrame.Navigate(typeof(OobeMeasureTool)); break; + case "Hosts": NavigationFrame.Navigate(typeof(OobeHosts)); break; + case "RegistryPreview": NavigationFrame.Navigate(typeof(OobeRegistryPreview)); break; + case "Peek": NavigationFrame.Navigate(typeof(OobePeek)); break; + case "NewPlus": NavigationFrame.Navigate(typeof(OobeNewPlus)); break; + case "Workspaces": NavigationFrame.Navigate(typeof(OobeWorkspaces)); break; + case "ZoomIt": NavigationFrame.Navigate(typeof(OobeZoomIt)); break; + } } } - private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) + private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) + { + if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal) + { + TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment + AppTitleBar.IsPaneToggleButtonVisible = true; + } + else + { + TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment + AppTitleBar.IsPaneToggleButtonVisible = false; + } + } + + private void TitleBar_PaneButtonClick(Microsoft.UI.Xaml.Controls.TitleBar sender, object args) + { + navigationView.IsPaneOpen = !navigationView.IsPaneOpen; + } + + private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) + { + ((App)App.Current)!.OpenScoobe(); + } + + private void Window_Activated(object sender, WindowActivatedEventArgs args) { // Set window icon - _appWindow.SetIcon("Assets\\Settings\\icon.ico"); - } - - private void OobeWindow_SizeChanged(object sender, WindowSizeChangedEventArgs args) - { - var dpi = NativeMethods.GetDpiForWindow(_hWnd); - if (_currentDPI != dpi) - { - // Reacting to a DPI change. Should not cause a resize -> sizeChanged loop. - _currentDPI = dpi; - float scalingFactor = (float)dpi / DefaultDPI; - int width = (int)(ExpectedWidth * scalingFactor); - int height = (int)(ExpectedHeight * scalingFactor); - SizeInt32 size; - size.Width = width; - size.Height = height; - _appWindow.Resize(size); - } + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Closed(object sender, WindowEventArgs args) { - App.ClearOobeWindow(); + if (navigationView.SelectedItem is NavigationViewItem selectedItem && selectedItem.Tag is string tag) + { + App.OobeShellViewModel.GetModuleFromTag(tag).LogClosingModuleEvent(); + } - var mainWindow = App.GetSettingsWindow(); - if (mainWindow != null) + if (App.GetSettingsWindow() is MainWindow mainWindow) { mainWindow.CloseHiddenWindow(); } @@ -134,19 +166,13 @@ namespace Microsoft.PowerToys.Settings.UI WindowHelper.SetTheme(this, theme); } - private void Dispose(bool disposing) + private void NavigationView_Loaded(object sender, RoutedEventArgs e) { - if (!disposedValue) + // Select the first module by default + if (navigationView.MenuItems.Count > 0) { - disposedValue = true; + navigationView.SelectedItem = navigationView.MenuItems[0]; } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml index 934229cea1..3e6bb53da5 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml @@ -6,12 +6,95 @@ xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:winuiex="using:WinUIEx" + Width="1100" + Height="700" MinWidth="480" MinHeight="480" + Activated="Window_Activated" Closed="Window_Closed" mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs index 446d0e2d0d..378df70063 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/ScoobeWindow.xaml.cs @@ -3,29 +3,38 @@ // See the LICENSE file in the project root for more information. using System; - +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.OOBE.Views; -using Microsoft.UI; -using Microsoft.UI.Windowing; +using Microsoft.PowerToys.Settings.UI.SerializationContext; using Microsoft.UI.Xaml; -using PowerToys.Interop; -using Windows.Graphics; +using Microsoft.UI.Xaml.Controls; +using WinRT.Interop; using WinUIEx; -using WinUIEx.Messaging; namespace Microsoft.PowerToys.Settings.UI { - public sealed partial class ScoobeWindow : WindowEx, IDisposable + public sealed partial class ScoobeWindow : WindowEx { - private const int ExpectedWidth = 1100; - private const int ExpectedHeight = 700; - private const int DefaultDPI = 96; - private int _currentDPI; - private WindowId _windowId; - private IntPtr _hWnd; - private AppWindow _appWindow; - private bool disposedValue; + public static Action OpenMainWindowCallback { get; set; } + + public static void SetOpenMainWindowCallback(Action implementation) + { + OpenMainWindowCallback = implementation; + } + + /// + /// Gets the list of release groups loaded from GitHub (grouped by major.minor version). + /// + public IList> ReleaseGroups { get; private set; } + + private bool _isLoading; public ScoobeWindow() { @@ -34,63 +43,31 @@ namespace Microsoft.PowerToys.Settings.UI this.InitializeComponent(); - _hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); - _windowId = Win32Interop.GetWindowIdFromWindow(_hWnd); - _appWindow = AppWindow.GetFromWindowId(_windowId); - this.Activated += Window_Activated_SetIcon; - this.ExtendsContentIntoTitleBar = true; + SetTitleBar(); - var dpi = NativeMethods.GetDpiForWindow(_hWnd); - _currentDPI = dpi; - float scalingFactor = (float)dpi / DefaultDPI; - int width = (int)(ExpectedWidth * scalingFactor); - int height = (int)(ExpectedHeight * scalingFactor); - - SizeInt32 size; - size.Width = width; - size.Height = height; - _appWindow.Resize(size); - - this.SizeChanged += ScoobeWindow_SizeChanged; - - var loader = Helpers.ResourceLoaderInstance.ResourceLoader; - Title = loader.GetString("ScoobeWindow_Title"); - - ScoobeShellPage.SetOpenMainWindowCallback((Type type) => + SetOpenMainWindowCallback((Type type) => { App.OpenSettingsWindow(type); }); } - private void Window_Activated_SetIcon(object sender, WindowActivatedEventArgs args) + private void SetTitleBar() { - // Set window icon - _appWindow.SetIcon("Assets\\Settings\\icon.ico"); + WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(this)); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(AppTitleBar); + Title = ResourceLoaderInstance.ResourceLoader.GetString("ScoobeWindow_Title"); } - private void ScoobeWindow_SizeChanged(object sender, WindowSizeChangedEventArgs args) + private void Window_Activated(object sender, WindowActivatedEventArgs args) { - var dpi = NativeMethods.GetDpiForWindow(_hWnd); - if (_currentDPI != dpi) - { - // Reacting to a DPI change. Should not cause a resize -> sizeChanged loop. - _currentDPI = dpi; - float scalingFactor = (float)dpi / DefaultDPI; - int width = (int)(ExpectedWidth * scalingFactor); - int height = (int)(ExpectedHeight * scalingFactor); - SizeInt32 size; - size.Width = width; - size.Height = height; - _appWindow.Resize(size); - } + // Set window icon + this.SetIcon("Assets\\Settings\\icon.ico"); } private void Window_Closed(object sender, WindowEventArgs args) { - App.ClearScoobeWindow(); - - var mainWindow = App.GetSettingsWindow(); - if (mainWindow != null) + if (App.GetSettingsWindow() is MainWindow mainWindow) { mainWindow.CloseHiddenWindow(); } @@ -103,19 +80,133 @@ namespace Microsoft.PowerToys.Settings.UI WindowHelper.SetTheme(this, theme); } - private void Dispose(bool disposing) + private async void NavigationView_Loaded(object sender, RoutedEventArgs e) { - if (!disposedValue) + await LoadReleasesAsync(); + } + + private async Task LoadReleasesAsync() + { + if (_isLoading) { - disposedValue = true; + return; + } + + _isLoading = true; + LoadingProgressRing.Visibility = Visibility.Visible; + ErrorInfoBar.IsOpen = false; + navigationView.MenuItems.Clear(); + + try + { + var releases = await FetchReleasesFromGitHubAsync(); + ReleaseGroups = GroupReleasesByMajorMinor(releases); + PopulateNavigationItems(); + } + catch (Exception ex) + { + Logger.LogError("Failed to load releases", ex); + ErrorInfoBar.IsOpen = true; + } + finally + { + LoadingProgressRing.Visibility = Visibility.Collapsed; + _isLoading = false; } } - public void Dispose() + private static async Task> FetchReleasesFromGitHubAsync() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + using var proxyClientHandler = new HttpClientHandler + { + DefaultProxyCredentials = CredentialCache.DefaultCredentials, + Proxy = WebRequest.GetSystemWebProxy(), + PreAuthenticate = true, + }; + + using var httpClient = new HttpClient(proxyClientHandler); + httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys"); + + string json = await httpClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases?per_page=20"); + var allReleases = JsonSerializer.Deserialize>(json, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo); + + if (allReleases is null || allReleases.Count == 0) + { + return []; + } + + return allReleases + .OrderByDescending(r => r.PublishedDate) + .ToList(); + } + + private static IList> GroupReleasesByMajorMinor(IList releases) + { + return releases + .GroupBy(GetMajorMinorVersion) + .Select(g => g.OrderByDescending(r => r.PublishedDate).ToList() as IList) + .ToList(); + } + + private static string GetMajorMinorVersion(PowerToysReleaseInfo release) + { + string version = ScoobeReleaseGroupViewModel.GetVersionFromRelease(release); + var parts = version.Split('.'); + if (parts.Length >= 2) + { + return $"{parts[0]}.{parts[1]}"; + } + + return version; + } + + private void PopulateNavigationItems() + { + if (ReleaseGroups == null || ReleaseGroups.Count == 0) + { + return; + } + + foreach (var releaseGroup in ReleaseGroups) + { + var viewModel = new ScoobeReleaseGroupViewModel(releaseGroup); + navigationView.MenuItems.Add(viewModel); + } + + // Select the first item to trigger navigation + navigationView.SelectedItem = navigationView.MenuItems[0]; + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is ScoobeReleaseGroupViewModel viewModel) + { + NavigationFrame.Navigate(typeof(ScoobeReleaseNotesPage), viewModel.Releases); + } + } + + private async void RetryButton_Click(object sender, RoutedEventArgs e) + { + await LoadReleasesAsync(); + } + + private void NavigationView_DisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) + { + if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal) + { + TitleBarIcon.Margin = new Thickness(0, 0, 8, 0); // Workaround, see XAML comment + AppTitleBar.IsPaneToggleButtonVisible = true; + } + else + { + TitleBarIcon.Margin = new Thickness(16, 0, 0, 0); // Workaround, see XAML comment + AppTitleBar.IsPaneToggleButtonVisible = false; + } + } + + private void TitleBar_PaneButtonClick(Microsoft.UI.Xaml.Controls.TitleBar sender, object args) + { + navigationView.IsPaneOpen = !navigationView.IsPaneOpen; } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml index 3f6a4c3074..693280e13f 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Themes/Generic.xaml @@ -3,6 +3,5 @@ - \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml index ae4bbeb438..3345528652 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml @@ -8,6 +8,7 @@ xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="using:Microsoft.PowerToys.Settings.UI.Library" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI" xmlns:viewmodels="using:Microsoft.PowerToys.Settings.UI.ViewModels" @@ -21,15 +22,12 @@ - ms-appx:///Assets/Settings/Modules/APDialog.dark.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.dark.svg - ms-appx:///Assets/Settings/Modules/APDialog.light.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.light.svg - ms-appx:///Assets/Settings/Modules/APDialog.light.png ms-appx:///Assets/Settings/Icons/Models/OpenAI.light.svg @@ -104,7 +102,7 @@ - - - - - + + + + + + + - - - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + - + - - - - - - - - + + + - + - - - - - + + + + + + + + + - - - + + + + + + + + - - - + + + - - - + + + - - - - - - - - - - - - + + + + + + + + + + - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml index 08d736a0fc..899295ec66 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml @@ -63,11 +63,51 @@ + + + + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml.cs index 641311daad..d82746faf6 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerDisplayPage.xaml.cs @@ -133,6 +133,65 @@ namespace Microsoft.PowerToys.Settings.UI.Views return ProfileHelper.GenerateUniqueProfileName(existingNames, baseName); } + // Custom VCP Mapping event handlers + private async void AddCustomMapping_Click(object sender, RoutedEventArgs e) + { + var dialog = new CustomVcpMappingEditorDialog(ViewModel.Monitors); + dialog.XamlRoot = this.XamlRoot; + + var result = await dialog.ShowAsync(); + + if (result == ContentDialogResult.Primary && dialog.ResultMapping != null) + { + ViewModel.AddCustomVcpMapping(dialog.ResultMapping); + } + } + + private async void EditCustomMapping_Click(object sender, RoutedEventArgs e) + { + if (sender is not Button button || button.Tag is not CustomVcpValueMapping mapping) + { + return; + } + + var dialog = new CustomVcpMappingEditorDialog(ViewModel.Monitors); + dialog.XamlRoot = this.XamlRoot; + dialog.PreFillMapping(mapping); + + var result = await dialog.ShowAsync(); + + if (result == ContentDialogResult.Primary && dialog.ResultMapping != null) + { + ViewModel.UpdateCustomVcpMapping(mapping, dialog.ResultMapping); + } + } + + private async void DeleteCustomMapping_Click(object sender, RoutedEventArgs e) + { + if (sender is not Button button || button.Tag is not CustomVcpValueMapping mapping) + { + return; + } + + var resourceLoader = ResourceLoaderInstance.ResourceLoader; + var dialog = new ContentDialog + { + XamlRoot = this.XamlRoot, + Title = resourceLoader.GetString("PowerDisplay_CustomMapping_Delete_Title"), + Content = resourceLoader.GetString("PowerDisplay_CustomMapping_Delete_Message"), + PrimaryButtonText = resourceLoader.GetString("Yes"), + CloseButtonText = resourceLoader.GetString("No"), + DefaultButton = ContentDialogButton.Close, + }; + + var result = await dialog.ShowAsync(); + + if (result == ContentDialogResult.Primary) + { + ViewModel.DeleteCustomVcpMapping(mapping); + } + } + // Flag to prevent reentrant handling during programmatic checkbox changes private bool _isRestoringColorTempCheckbox; diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml index 5bcdf7195c..23da2b2449 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml @@ -9,6 +9,7 @@ xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI" xmlns:viewModels="using:Microsoft.PowerToys.Settings.UI.ViewModels" @@ -45,7 +46,7 @@ BorderThickness="0,0,0,0" ContentAlignment="Left" CornerRadius="0"> - - - - - - + @@ -459,7 +460,7 @@ Value="{x:Bind ViewModel.SearchClickedItemWeight, Mode=TwoWay}" /> - + @@ -548,7 +549,7 @@ x:Uid="PowerLauncher_TitleFontSize" HeaderIcon="{ui:FontIcon Glyph=}"> - - - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml index a6b44dfdf7..895dbd0782 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerPreviewPage.xaml @@ -6,6 +6,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Microsoft.PowerToys.Settings.UI.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ptcontrols="using:Microsoft.PowerToys.Common.UI.Controls" xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" xmlns:ui="using:CommunityToolkit.WinUI" AutomationProperties.LandmarkType="Main" @@ -89,7 +90,7 @@ - diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml index 6a64fdea83..56bc838161 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml @@ -76,7 +76,6 @@ x:Uid="Shell_Search_ShowAll" HorizontalAlignment="Stretch" VerticalAlignment="Center" - Text="Show all results" TextAlignment="Center" /> @@ -259,7 +258,7 @@ diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs index c8f3284432..7515cad863 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml.cs @@ -48,16 +48,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// public delegate bool UpdatingGeneralSettingsCallback(ModuleType moduleType, bool isEnabled); - /// - /// Declaration for opening oobe window callback function. - /// - public delegate void OobeOpeningCallback(); - - /// - /// Declaration for opening whats new window callback function. - /// - public delegate void WhatIsNewOpeningCallback(); - /// /// Gets or sets a shell handler to be used to update contents of the shell dynamically from page within the frame. /// @@ -88,16 +78,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views /// public static UpdatingGeneralSettingsCallback UpdateGeneralSettingsCallback { get; set; } - /// - /// Gets or sets callback function for opening oobe window - /// - public static OobeOpeningCallback OpenOobeWindowCallback { get; set; } - - /// - /// Gets or sets callback function for opening oobe window - /// - public static WhatIsNewOpeningCallback OpenWhatIsNewWindowCallback { get; set; } - /// /// Gets view model. /// @@ -223,24 +203,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateGeneralSettingsCallback = implementation; } - /// - /// Set oobe opening callback function - /// - /// delegate function implementation. - public static void SetOpenOobeCallback(OobeOpeningCallback implementation) - { - OpenOobeWindowCallback = implementation; - } - - /// - /// Set whats new opening callback function - /// - /// delegate function implementation. - public static void SetOpenWhatIsNewCallback(WhatIsNewOpeningCallback implementation) - { - OpenWhatIsNewWindowCallback = implementation; - } - public static void SetElevationStatus(bool isElevated) { IsElevated = isElevated; @@ -325,7 +287,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views private void OOBEItem_Tapped(object sender, TappedRoutedEventArgs e) { - OpenOobeWindowCallback(); + ((App)App.Current)!.OpenOobe(); + } + + private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) + { + ((App)App.Current)!.OpenScoobe(); } private async void FeedbackItem_Tapped(object sender, TappedRoutedEventArgs e) @@ -333,15 +300,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views await Launcher.LaunchUriAsync(new Uri("https://aka.ms/powerToysGiveFeedback")); } - private void WhatIsNewItem_Tapped(object sender, TappedRoutedEventArgs e) - { - OpenWhatIsNewWindowCallback(); - } - private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { - NavigationViewItem selectedItem = args.SelectedItem as NavigationViewItem; - if (selectedItem != null) + if (args.SelectedItem is NavigationViewItem selectedItem) { Type pageType = selectedItem.GetValue(NavHelper.NavigateToProperty) as Type; @@ -409,7 +370,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views navigationView.IsPaneOpen = !navigationView.IsPaneOpen; } - private async void Close_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) + private async void Close_Tapped(object sender, TappedRoutedEventArgs e) { await CloseDialog.ShowAsync(); } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml index 0177d1158f..88496afab6 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ZoomItPage.xaml @@ -16,15 +16,6 @@ - - - + @@ -94,7 +85,7 @@ - + @@ -110,7 +101,7 @@ - + @@ -124,25 +115,24 @@ IsExpanded="True">