Compare commits

..

20 Commits

Author SHA1 Message Date
Michael Jolley
c7f8ac0b06 Merging main (broken) 2026-02-13 17:15:25 -06:00
Gordon Lam
09bdbfac38 build(updating): Add WinRT coroutine support and refactor async methods (#45522)
## Summary of the Pull Request  
There are many build warnings now with like "cl : command line warning
D9047: option 'await' has been deprecated and will be re moved in a
future release." after we update to VS2026.

Introduce WinRT coroutine support by replacing `std::future` with
`IAsyncOperation` for asynchronous methods. Adjust output directories
and remove the `/await` option from project files to streamline the
build process. Update methods to utilize `std::expected` and `co_await`,
enhancing the async handling of version checks and downloads.

## PR Checklist  
- [ ] Closes: #xxx  
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass  
- [ ] **Localization:** All end-user-facing strings can be localized  
- [ ] **Dev docs:** Added/updated  
- [ ] **New binaries:** Added on the required places  
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments  
Refactor async methods to improve performance and compatibility with
WinRT. The changes include modifying the return types of several
functions in the `updating` namespace, specifically
`uninstall_previous_msix_version_async`,
`get_github_version_info_async`, and `download_new_version_async`.

## Validation Steps Performed  
Manual testing was conducted to ensure that the new async methods
function correctly and that the application behaves as expected during
version checks and downloads. Automated tests were updated to cover the
new coroutine implementations.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-11 06:31:46 -08:00
Michael Jolley
12bfa13b72 Fixing spelling 2026-02-10 20:15:29 -06:00
Michael Jolley
54d1ab3b70 WinRTExtensionService 2026-02-10 20:13:56 -06:00
Michael Jolley
82671661a8 WinRTExtensionService 2026-02-09 13:21:41 -06:00
Michael Jolley
99a6c8c74e Working on DI 2026-02-05 16:33:20 -06:00
Michael Jolley
285183899f I don't know what I've done. Send help... or Jiri 2026-02-04 16:32:58 -06:00
Michael Jolley
d3d39f91dc DI 2026-02-03 20:42:22 -06:00
Michael Jolley
10f953f684 Working on DI 2026-02-03 16:48:40 -06:00
Michael Jolley
54c5d7b55d Working on DI 2026-02-03 16:48:30 -06:00
Michael Jolley
94a699d00b Working on DI 2026-01-30 22:38:03 -06:00
Michael Jolley
fa4bc0a397 Working on DI 2026-01-29 21:42:53 -06:00
Michael Jolley
303be86d86 Working on DI 2026-01-28 17:00:27 -06:00
Michael Jolley
ec423177da Refactor CmdPal to inject SettingsService instead of SettingsModel
- Update all ViewModels to receive SettingsService via DI
- Implement IDisposable pattern for proper event cleanup
- Add App.Services property for DI access where needed
- Restore GetProviderSettings/GetGlobalFallbacks to SettingsModel
- Fix CommandProviderWrapper and TopLevelCommandManager logging
- Update all Settings pages to use SettingsService
- Add DefaultAppExtensionHost to replace CommandPaletteHost.Instance
- Add parameterless constructors for XAML-instantiated types
- Fix various ILogger injection issues throughout UI layer
2026-01-27 17:33:58 -06:00
Michael Jolley
f78ec0c8e5 Migrate CoreLogger to ILogger with LoggerMessage pattern (partial)
UI.ViewModels:
- CommandSettingsViewModel: Add ILogger injection
- CommandItemViewModel: Add ILogger with LoggerMessage methods
- ContextMenuViewModel: Add ILogger injection
- ExtensionObjectViewModel: Add Logger property with NullLogger default
- ShellViewModel: Replace CoreLogger calls with LoggerMessage methods
- UpdateCommandBarMessage: Remove logging from interface default method

UI helpers/services:
- WallpaperHelper: Add ILogger injection with LoggerMessage methods
- LocalKeyboardListener: Add ILogger injection with LoggerMessage methods
- ImageProvider: Add ILogger injection with LoggerMessage methods
- ThemeService: Add ILogger injection with LoggerMessage methods

Note: UI project migration is partial - XAML code-behind files still use static Logger
2026-01-27 14:05:21 -06:00
Michael Jolley
a1e8b6aca9 Add AppStateService and SettingsService with PersistenceService
- Extract persistence logic into PersistenceService
- Add AppStateService to manage app state
- Add SettingsService to manage settings
- Add JsonSerializationContext for AOT-compatible JSON
- Add EscapeKeyBehavior and MonitorBehavior enums
2026-01-26 18:12:58 -06:00
Michael Jolley
81aeb74fda Use ILogger in AppExtensionHost instead of CoreLogger 2026-01-26 17:00:06 -06:00
Michael Jolley
b5ae2efc0d Rename CoreLogger to CmdPalLogger 2026-01-26 16:56:43 -06:00
Michael Jolley
ba9585a663 Migrate Microsoft.CmdPal.Core.ViewModels into Microsoft.CmdPal.UI.ViewModels
- Move all source files from Core.ViewModels to UI.ViewModels
- Update namespace from Microsoft.CmdPal.Core.ViewModels to Microsoft.CmdPal.UI.ViewModels
- Remove Core.ViewModels project from solution files
- Update all using statements in dependent projects
- Delete the Core folder which is now empty
2026-01-26 16:34:38 -06:00
Michael Jolley
5f273c7be6 Rename Microsoft.CmdPal.Core.Common to Microsoft.CmdPal.Common
- Move project from Core/ subfolder to cmdpal/ root
- Update namespace from Microsoft.CmdPal.Core.Common to Microsoft.CmdPal.Common
- Update all project references and using statements
- Update solution files (PowerToys.slnx, CommandPalette.slnf)
2026-01-26 15:52:52 -06:00
315 changed files with 3957 additions and 2660 deletions

View File

@@ -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#getting-started
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2026 Enterprise (Any edition will work)
description: Install Visual Studio 2022 Enterprise (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.Enterprise
id: Microsoft.VisualStudio.2022.Enterprise
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Enterprise
channelId: VisualStudio.18.Release
channelId: VisualStudio.17.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -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#getting-started
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2026 Professional (Any edition will work)
description: Install Visual Studio 2022 Professional (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.Professional
id: Microsoft.VisualStudio.2022.Professional
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Professional
channelId: VisualStudio.18.Release
channelId: VisualStudio.17.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -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#getting-started
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
@@ -13,11 +13,11 @@ properties:
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2026 Community (Any edition will work)
description: Install Visual Studio 2022 Community (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
settings:
id: Microsoft.VisualStudio.Community
id: Microsoft.VisualStudio.2022.Community
source: winget
- resource: Microsoft.VisualStudio.DSC/VSComponents
dependsOn:
@@ -29,7 +29,7 @@ properties:
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Community
channelId: VisualStudio.18.Release
channelId: VisualStudio.17.Release
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
configurationVersion: 0.2.0

View File

@@ -64,7 +64,6 @@
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<BuildStlModules>false</BuildStlModules>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<!-- TODO: _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING for compatibility with VS 17.8. Check if we can remove. -->
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- CLR + CFG are not compatible >:{ -->

View File

@@ -196,6 +196,10 @@
<Folder Name="/modules/CommandPalette/">
<Project Path="src/modules/cmdpal/CmdPalKeyboardService/CmdPalKeyboardService.vcxproj" Id="5f63c743-f6ce-4dba-a200-2b3f8a14e8c2" />
<Project Path="src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj" Id="0adeb797-c8c7-4ffa-acd5-2af6cad7ecd8" />
<Project Path="src/modules/cmdpal/Microsoft.CmdPal.Common/Microsoft.CmdPal.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Built-in Extensions/">
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/Microsoft.CmdPal.Ext.Apps.csproj">
@@ -275,16 +279,6 @@
<Deploy />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Core/">
<Project Path="src/modules/cmdpal/Core/Microsoft.CmdPal.Core.Common/Microsoft.CmdPal.Core.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Core/Microsoft.CmdPal.Core.ViewModels/Microsoft.CmdPal.Core.ViewModels.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/CommandPalette/Extension SDK/">
<Project Path="src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />

View File

@@ -88,7 +88,7 @@
### Building PowerToys Locally
#### One stop script for building installer
1. Open `Developer PowerShell for VS`.
1. Open `Developer Powershell for VS 2022` or `Developer PowerShell for VS` for VS 2026.
2. Run tools\build\build-installer.ps1
> 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`
1. From the start menu, open a `Developer Command Prompt for VS 2022` or `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` 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 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`.
```
git clean -xfd -e *exe -- .\installer\

View File

@@ -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`
1. Open `Developer Command Prompt for VS 2022` or `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`):
Or from the command line (Developer Command Prompt for VS 2022 or Developer Command Prompt for VS):
```pwsh
msbuild PowerToys.slnx /t:Clean /p:Platform=x64 /p:Configuration=Debug
```

View File

@@ -15,27 +15,9 @@ VS Code extensions Needed:
---
## Building in VS Code
### 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",
"}"
]
},
```
### 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):
**For Visual Studio 2022:**
```json
@@ -55,6 +37,24 @@ 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.

View File

@@ -12,11 +12,22 @@ A PowerToy module is a self-contained utility integrated into the PowerToys ecos
### Requirements
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`.
- [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`.
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
```

View File

@@ -152,7 +152,7 @@ FancyZones is divided into several projects:
## Development Environment Setup
### Prerequisites
- Visual Studio 2026 (or 2022 17.4+): Required for building and debugging
- Visual Studio 2022 or 2026: 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, set FancyZonesEditor as the startup project
1. In Visual Studio 2022 or 2026, set FancyZonesEditor as the startup project
2. Set breakpoints in the code where needed
3. Click Run to start debugging

View File

@@ -2,31 +2,91 @@
Welcome to the PowerToys developer documentation. This documentation provides information for developers who want to contribute to PowerToys or understand how it works.
## Getting Started
## Core Architecture
### Prerequisites
- [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
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)
## Common Components
> **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`).
- [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
### Fork, Clone, and Set Up
## 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:
1. Fork the repo on GitHub if you haven't already
1. Clone your fork locally
1. Run the automated setup script (**recommended**):
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.
- **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.
## 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:
```powershell
.\tools\build\setup-dev-environment.ps1
@@ -38,10 +98,15 @@ 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:
<details>
<summary><strong>Manual setup (if you prefer not to use the script)</strong></summary>
```powershell
.\tools\build\setup-dev-environment.ps1 -Help
```
### Manual Setup
If you prefer to set up manually, follow these steps:
#### Install Visual Studio dependencies
@@ -50,17 +115,15 @@ Run with `-Help` to see all available options.
Alternatively, import the `.vsconfig` file from the repository root using Visual Studio Installer to install all required workloads.
#### Initialize submodules
#### Get Submodules to compile
This is a one-time step required before you can compile most parts of PowerToys.
We have submodules that need to be initialized before you can compile most parts of PowerToys. This should be a one-time step.
1. Open a terminal
1. Navigate to the folder you cloned PowerToys to.
1. Run `git submodule update --init --recursive`
</details>
### Building
### Compiling Source Code
#### Using Visual Studio
@@ -88,77 +151,7 @@ You can also build from the command line using the provided scripts in `tools\bu
.\tools\build\build-installer.ps1
```
### 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
## Compile 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).
@@ -172,3 +165,8 @@ 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). <br />
Specifications for the [PowerToys settings API](core/settings/readme.md).

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

BIN
doc/images/runner/tray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -88,7 +88,7 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>inc;..\..\src\;..\..\src\common\Telemetry;telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/await /Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions>/Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
<WarningLevel>Level4</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>

View File

@@ -57,7 +57,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
auto state = UpdateState::read();
const auto new_version_info = get_github_version_info_async().get();
const auto new_version_info = std::move(get_github_version_info_async()).get();
if (std::holds_alternative<version_up_to_date>(*new_version_info))
{
isUpToDate = true;
@@ -76,7 +76,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
if (!downloaded_installer)
{
Logger::error("Couldn't download new installer");

View File

@@ -18,7 +18,7 @@ namespace // Strings in this namespace should not be localized
namespace updating
{
std::future<bool> uninstall_previous_msix_version_async()
winrt::Windows::Foundation::IAsyncOperation<bool> uninstall_previous_msix_version_async()
{
winrt::Windows::Management::Deployment::PackageManager package_manager;

View File

@@ -2,11 +2,11 @@
#include <string>
#include <optional>
#include <future>
#include <winrt/Windows.Foundation.h>
#include <common/version/helper.h>
namespace updating
{
std::future<bool> uninstall_previous_msix_version_async();
}
winrt::Windows::Foundation::IAsyncOperation<bool> uninstall_previous_msix_version_async();
}

View File

@@ -33,6 +33,7 @@
#include <winrt/Windows.System.h>
#include <wil/resource.h>
#include <wil/coroutine.h>
#endif //PCH_H

View File

@@ -82,11 +82,7 @@ namespace updating
// prevent the warning that may show up depend on the value of the constants (#defines)
#pragma warning(push)
#pragma warning(disable : 4702)
#if USE_STD_EXPECTED
std::future<std::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease)
#else
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease)
#endif
wil::task<github_version_result> get_github_version_info_async(const bool prerelease)
{
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
@@ -170,7 +166,7 @@ namespace updating
return !ec ? std::optional{ installer_download_path } : std::nullopt;
}
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version)
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version)
{
auto installer_download_path = create_download_path();
if (!installer_download_path)

View File

@@ -2,7 +2,6 @@
#include <optional>
#include <string>
#include <future>
#include <filesystem>
#include <variant>
#include <winrt/Windows.Foundation.h>
@@ -16,6 +15,7 @@
#endif
#include <common/version/helper.h>
#include <wil/coroutine.h>
namespace updating
{
@@ -32,13 +32,15 @@ namespace updating
};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version);
std::filesystem::path get_pending_updates_path();
#if USE_STD_EXPECTED
std::future<std::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease = false);
using github_version_result = std::expected<github_version_info, std::wstring>;
#else
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const bool prerelease = false);
using github_version_result = nonstd::expected<github_version_info, std::wstring>;
#endif
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);
std::filesystem::path get_pending_updates_path();
void cleanup_updates();
// non-localized

View File

@@ -1,6 +1,7 @@
#pragma once
#include <future>
#include <functional>
#include <string>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Web.Http.h>
@@ -21,15 +22,15 @@ namespace http
headers.UserAgent().TryParseAdd(USER_AGENT);
}
std::future<std::wstring> request(const winrt::Windows::Foundation::Uri& url)
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> 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<void> 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<void> download(const winrt::Windows::Foundation::Uri& url, const std::wstring& dstFilePath, const std::function<void(float)>& progressUpdateCallback)
winrt::Windows::Foundation::IAsyncAction download(winrt::Windows::Foundation::Uri url, std::wstring dstFilePath, std::function<void(float)> progressUpdateCallback)
{
auto response = co_await m_client.GetAsync(url, HttpCompletionOption::ResponseHeadersRead);
response.EnsureSuccessStatusCode();

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -13,7 +13,6 @@
<ConformanceMode>false</ConformanceMode>
<TreatWarningAsError>true</TreatWarningAsError>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>

View File

@@ -5140,7 +5140,7 @@ bool IsPenInverted( WPARAM wParam )
// Captures the specified screen using the capture APIs
//
//----------------------------------------------------------------------------
std::future<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
wil::task<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
{
auto d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(device);
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
@@ -5176,9 +5176,7 @@ std::future<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDire
framePool.Close();
auto texture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(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_ptr<ID3D11Texture2D>CaptureScreenshot(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();
}

View File

@@ -69,6 +69,7 @@
// WIL
#include <wil/com.h>
#include <wil/resource.h>
#include <wil/coroutine.h>
// DirectX
#include <d3d11_4.h>

View File

@@ -10,8 +10,7 @@
"src\\common\\version\\version.vcxproj",
"src\\modules\\cmdpal\\CmdPalKeyboardService\\CmdPalKeyboardService.vcxproj",
"src\\modules\\cmdpal\\CmdPalModuleInterface\\CmdPalModuleInterface.vcxproj",
"src\\modules\\cmdpal\\Core\\Microsoft.CmdPal.Core.Common\\Microsoft.CmdPal.Core.Common.csproj",
"src\\modules\\cmdpal\\Core\\Microsoft.CmdPal.Core.ViewModels\\Microsoft.CmdPal.Core.ViewModels.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.Common\\Microsoft.CmdPal.Common.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.UI.ViewModels\\Microsoft.CmdPal.UI.ViewModels.csproj",
"src\\modules\\cmdpal\\Microsoft.CmdPal.UI\\Microsoft.CmdPal.UI.csproj",
"src\\modules\\cmdpal\\Microsoft.Terminal.UI\\Microsoft.Terminal.UI.vcxproj",

View File

@@ -1,62 +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;
namespace Microsoft.CmdPal.Core.Common;
public static class CoreLogger
{
public static void InitializeLogger(ILogger implementation)
{
_logger = implementation;
}
private static ILogger? _logger;
public static void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, ex, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogError(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogWarning(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogInfo(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogDebug(message, memberName, sourceFilePath, sourceLineNumber);
}
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{
_logger?.LogTrace(memberName, sourceFilePath, sourceLineNumber);
}
}
public interface ILogger
{
void LogError(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogError(string message, Exception ex, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogWarning(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogInfo(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0);
}

View File

@@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Core.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -1,37 +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 System.Threading.Tasks;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.Common.Services;
public interface IExtensionService
{
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false);
// Task<IEnumerable<string>> GetInstalledHomeWidgetPackageFamilyNamesAsync(bool includeDisabledExtensions = false);
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(Microsoft.CommandPalette.Extensions.ProviderType providerType, bool includeDisabledExtensions = false);
IExtensionWrapper? GetInstalledExtension(string extensionUniqueId);
Task SignalStopExtensionsAsync();
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;
event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionRemoved;
void EnableExtension(string extensionUniqueId);
void DisableExtension(string extensionUniqueId);
///// <summary>
///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature
///// being absent from the machine or in an unknown state.
///// </summary>
///// <param name="extension">The out of proc extension object</param>
///// <returns>True only if the extension was disabled. False otherwise.</returns>
// public Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension);
}

View File

@@ -1,12 +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.
namespace Microsoft.CmdPal.Core.ViewModels;
/// <summary>
/// Encapsulates a navigation request within Command Palette view models.
/// </summary>
/// <param name="TargetViewModel">A view model that should be navigated to.</param>
/// <param name="NavigationToken"> A <see cref="CancellationToken"/> that can be used to cancel the pending navigation.</param>
public record AsyncNavigationRequest(object? TargetViewModel, CancellationToken NavigationToken);

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\CoreCommonProps.props" />
<PropertyGroup>
<EnableCoreMrtTooling>false</EnableCoreMrtTooling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" />
<PackageReference Include="CommunityToolkit.Mvvm" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CmdPal.Core.Common\Microsoft.CmdPal.Core.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -1,72 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Core.ViewModels.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Core.ViewModels.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Show details.
/// </summary>
public static string ShowDetailsCommand {
get {
return ResourceManager.GetString("ShowDetailsCommand", resourceCulture);
}
}
}
}

View File

@@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ShowDetailsCommand" xml:space="preserve">
<value>Show details</value>
<comment>Name for the command that shows details of an item</comment>
</data>
</root>

View File

@@ -0,0 +1,167 @@
// 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 ManagedCommon;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Common;
// Adapter implementing Microsoft.Extensions.Logging.ILogger,
// delegating to ManagedCommon.Logger.
public sealed partial class CmdPalLogger : ILogger
{
private static readonly AsyncLocal<Stack<object>> _scopeStack = new();
private readonly LogLevel _minLevel;
public string CurrentVersionLogDirectoryPath => Logger.CurrentVersionLogDirectoryPath;
public CmdPalLogger(LogLevel minLevel = LogLevel.Information)
{
_minLevel = minLevel;
// Ensure underlying logger initialized (idempotent if already done elsewhere).
Logger.InitializeLogger("\\CmdPal\\Logs\\");
}
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && logLevel >= _minLevel;
public IDisposable? BeginScope<TState>(TState state)
where TState : notnull
{
var stack = _scopeStack.Value;
if (stack is null)
{
stack = new Stack<object>();
_scopeStack.Value = stack;
}
stack.Push(state);
return new Scope(stack);
}
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
ArgumentNullException.ThrowIfNull(formatter);
var message = formatter(state, exception);
if (string.IsNullOrEmpty(message) && exception is null)
{
return;
}
var scopeSuffix = BuildScopeSuffix();
var eventPrefix = eventId.Id != 0 ? $"[{eventId.Id}/{eventId.Name}] " : string.Empty;
var finalMessage = $"{eventPrefix}{message}{scopeSuffix}";
switch (logLevel)
{
case LogLevel.Trace:
// Existing stack: Trace logs an empty line; append message via Debug.
Logger.LogTrace();
if (!string.IsNullOrEmpty(message))
{
Logger.LogDebug(finalMessage);
}
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Debug:
Logger.LogDebug(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Information:
Logger.LogInfo(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Warning:
Logger.LogWarning(finalMessage);
if (exception is not null)
{
Logger.LogError(exception.Message, exception);
}
break;
case LogLevel.Error:
case LogLevel.Critical:
if (exception is not null)
{
Logger.LogError(finalMessage, exception);
}
else
{
Logger.LogError(finalMessage);
}
break;
case LogLevel.None:
default:
break;
}
}
private static string BuildScopeSuffix()
{
var stack = _scopeStack.Value;
if (stack is null || stack.Count == 0)
{
return string.Empty;
}
// Show most-recent first.
return $" [Scopes: {string.Join(" => ", stack.ToArray())}]";
}
private sealed partial class Scope : IDisposable
{
private readonly Stack<object> _stack;
private bool _disposed;
public Scope(Stack<object> stack) => _stack = stack;
public void Dispose()
{
if (_disposed)
{
return;
}
if (_stack.Count > 0)
{
_stack.Pop();
}
_disposed = true;
}
}
}

View File

@@ -5,7 +5,7 @@
using System;
using System.Runtime.InteropServices;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Provides utility methods for building diagnostic and error messages.

View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.Common;
namespace Microsoft.CmdPal.Common;
public partial class ExtensionHostInstance
{

View File

@@ -4,7 +4,7 @@
using System.Threading;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Thread-safe boolean implementation using atomic operations

View File

@@ -7,7 +7,7 @@ using System.Threading;
using Microsoft.UI.Dispatching;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
public static partial class NativeEventWaiter
{

View File

@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
using Windows.Win32;
using Windows.Win32.Storage.FileSystem;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
public static class PathHelper
{

View File

@@ -6,7 +6,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one operation runs at a time.

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// An async gate that ensures only one value computation runs at a time.

View File

@@ -3,9 +3,10 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Helper class for retrieving application version information safely.
@@ -37,7 +38,7 @@ internal static class VersionHelper
/// </summary>
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
private static bool TryGetPackagedVersion(out string version)
private static bool TryGetPackagedVersion(out string version, ILogger logger)
{
version = string.Empty;
try
@@ -53,7 +54,7 @@ internal static class VersionHelper
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to get version from the package", ex);
Log_FailedToGetVersion(logger, ex);
return false;
}
}
@@ -63,7 +64,7 @@ internal static class VersionHelper
/// </summary>
/// <param name="version">The version string if successful, or an empty string if unsuccessful.</param>
/// <returns>True if the version was retrieved successfully; otherwise, false.</returns>
private static bool TryGetAssemblyVersion(out string version)
private static bool TryGetAssemblyVersion(out string version, ILogger logger)
{
version = string.Empty;
try
@@ -80,8 +81,14 @@ internal static class VersionHelper
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to get version from the executable", ex);
Log_FailedToGetVersionFromExe(logger, ex);
return false;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to get version from the package")]
static partial void Log_FailedToGetVersion(ILogger logger, Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to get version from the executable")]
static partial void Log_FailedToGetVersionFromExe(ILogger logger, Exception ex);
}

View File

@@ -7,7 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace Microsoft.CmdPal.Core.Common.Helpers;
namespace Microsoft.CmdPal.Common.Helpers;
/// <summary>
/// Well-known key chords used in the Command Palette and extensions.

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\CoreCommonProps.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Common</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,164 @@
// 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.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization.Metadata;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
namespace Microsoft.CmdPal.Common;
public partial class PersistenceService
{
private static bool TryParseJsonObject(string json, ILogger logger, [NotNullWhen(true)] out JsonObject? obj)
{
obj = null;
try
{
obj = JsonNode.Parse(json) as JsonObject;
return obj is not null;
}
catch (Exception ex)
{
Log_PersistenceParseFailure(logger, ex);
return false;
}
}
private static bool TryReadSavedObject(string filePath, ILogger logger, [NotNullWhen(true)] out JsonObject? saved)
{
saved = null;
string oldContent;
try
{
if (!File.Exists(filePath))
{
saved = new JsonObject();
return true;
}
oldContent = File.ReadAllText(filePath);
}
catch (Exception ex)
{
Log_PersistenceReadFileFailure(logger, filePath, ex);
return false;
}
if (string.IsNullOrWhiteSpace(oldContent))
{
Log_FileEmpty(logger, filePath);
return false;
}
return TryParseJsonObject(oldContent, logger, out saved);
}
public static T LoadObject<T>(string filePath, JsonTypeInfo<T> typeInfo, ILogger logger)
where T : new()
{
if (string.IsNullOrEmpty(filePath))
{
throw new InvalidOperationException($"You must set a valid file path before loading {typeof(T).Name}");
}
if (!File.Exists(filePath))
{
Log_FileDoesntExist(logger, typeof(T).Name, filePath);
return new T();
}
try
{
var jsonContent = File.ReadAllText(filePath);
var loaded = JsonSerializer.Deserialize(jsonContent, typeInfo);
return loaded ?? new T();
}
catch (Exception ex)
{
Log_PersistenceReadFailure(logger, typeof(T).Name, filePath, ex);
return new T();
}
}
public static void SaveObject<T>(
T model,
string filePath,
JsonTypeInfo<T> typeInfo,
JsonSerializerOptions optionsForWrite,
Action<JsonObject>? beforeWriteMutation,
Action<T>? afterWriteCallback,
ILogger logger)
{
if (string.IsNullOrEmpty(filePath))
{
throw new InvalidOperationException($"You must set a valid file path before saving {typeof(T).Name}");
}
try
{
var json = JsonSerializer.Serialize(model, typeInfo);
if (!TryParseJsonObject(json, logger, out var newObj))
{
Log_SerializationError(logger, typeof(T).Name);
return;
}
if (!TryReadSavedObject(filePath, logger, out var savedObj))
{
savedObj = new JsonObject();
}
foreach (var kvp in newObj)
{
savedObj[kvp.Key] = kvp.Value?.DeepClone();
}
beforeWriteMutation?.Invoke(savedObj);
var serialized = savedObj.ToJsonString(optionsForWrite);
File.WriteAllText(filePath, serialized);
afterWriteCallback?.Invoke(model);
}
catch (Exception ex)
{
Log_PersistenceSaveFailure(logger, typeof(T).Name, filePath, ex);
}
}
public static string SettingsJsonPath(string fileName)
{
var directory = Utilities.BaseSettingsPath("Microsoft.CommandPalette");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe
return Path.Combine(directory, fileName);
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to save {typeName} to '{filePath}'.")]
static partial void Log_PersistenceSaveFailure(ILogger logger, string typeName, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to read {typeName} from '{filePath}'.")]
static partial void Log_PersistenceReadFailure(ILogger logger, string typeName, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Debug, Message = "Failed to serialize {typeName} to JsonObject.")]
static partial void Log_SerializationError(ILogger logger, string typeName);
[LoggerMessage(Level = LogLevel.Debug, Message = "The provided {typeName} file does not exist ({filePath})")]
static partial void Log_FileDoesntExist(ILogger logger, string typeName, string filePath);
[LoggerMessage(Level = LogLevel.Debug, Message = "The file at '{filePath}' is empty.")]
static partial void Log_FileEmpty(ILogger logger, string filePath);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to read file at '{filePath}'.")]
static partial void Log_PersistenceReadFileFailure(ILogger logger, string filePath, Exception exception);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to parse persisted JSON.")]
static partial void Log_PersistenceParseFailure(ILogger logger, Exception exception);
}

View File

@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Core.Common.Properties {
namespace Microsoft.CmdPal.Common.Properties {
using System;
@@ -22,7 +22,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
@@ -36,10 +36,10 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Core.Common.Properties.Resources", typeof(Resources).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Common.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -51,7 +51,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
@@ -67,7 +67,7 @@ namespace Microsoft.CmdPal.Core.Common.Properties {
///
///(While youre at it, give the details below a quick skim — just to make sure theres nothing personal youd prefer not to share. Its rare, but sometimes little surprises sneak in.).
/// </summary>
internal static string ErrorReport_Global_Preamble {
public static string ErrorReport_Global_Preamble {
get {
return ResourceManager.GetString("ErrorReport_Global_Preamble", resourceCulture);
}

View File

@@ -5,8 +5,8 @@
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
namespace Microsoft.CmdPal.Core.Common.Services;
@@ -14,20 +14,22 @@ namespace Microsoft.CmdPal.Core.Common.Services;
/// <summary>
/// Implementation of IApplicationInfoService providing application-wide information.
/// </summary>
public sealed class ApplicationInfoService : IApplicationInfoService
public sealed partial class ApplicationInfoService : IApplicationInfoService
{
private readonly Lazy<string> _configDirectory = new(() => Utilities.BaseSettingsPath("Microsoft.CmdPal"));
private readonly Lazy<bool> _isElevated;
private readonly Lazy<string> _logDirectory;
private readonly Lazy<AppPackagingFlavor> _packagingFlavor;
private readonly ILogger _logger;
private Func<string>? _getLogDirectory;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class.
/// The log directory delegate can be set later via <see cref="SetLogDirectory(Func{string})"/>.
/// </summary>
public ApplicationInfoService()
public ApplicationInfoService(ILogger logger)
{
_logger = logger;
_packagingFlavor = new Lazy<AppPackagingFlavor>(DeterminePackagingFlavor);
_isElevated = new Lazy<bool>(DetermineElevationStatus);
_logDirectory = new Lazy<string>(() => _getLogDirectory?.Invoke() ?? "Not available");
@@ -37,10 +39,13 @@ public sealed class ApplicationInfoService : IApplicationInfoService
/// Initializes a new instance of the <see cref="ApplicationInfoService"/> class with an optional log directory provider.
/// </summary>
/// <param name="getLogDirectory">Optional delegate to retrieve the log directory path. If not provided, the log directory will be unavailable.</param>
public ApplicationInfoService(Func<string>? getLogDirectory)
: this()
public ApplicationInfoService(
Func<string>? getLogDirectory,
ILogger logger)
: this(logger)
{
_getLogDirectory = getLogDirectory;
_logger = logger;
}
/// <summary>
@@ -87,7 +92,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
""";
}
private static AppPackagingFlavor DeterminePackagingFlavor()
private AppPackagingFlavor DeterminePackagingFlavor()
{
// Try to determine if running as packaged
try
@@ -105,7 +110,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
}
catch (Exception ex)
{
CoreLogger.LogError("Failed to determine packaging flavor", ex);
Log_FailedToDeterminePackagingFlavor(ex);
return AppPackagingFlavor.Unpackaged;
}
}
@@ -122,4 +127,7 @@ public sealed class ApplicationInfoService : IApplicationInfoService
return false;
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to determine packaging flavor")]
partial void Log_FailedToDeterminePackagingFlavor(Exception ex);
}

View File

@@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.Core.Common.Services;
namespace Microsoft.CmdPal.Common.Services;
public interface IRunHistoryService
{

View File

@@ -3,45 +3,44 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AliasManager : ObservableObject
{
private readonly TopLevelCommandManager _topLevelCommandManager;
// private readonly TopLevelCommandManager _topLevelCommandManager;
private readonly SettingsService _settingsService;
// REMEMBER, CommandAlias.SearchPrefix is what we use as keys
private readonly Dictionary<string, CommandAlias> _aliases;
private Dictionary<string, CommandAlias> Aliases => _settingsService.CurrentSettings.Aliases;
public AliasManager(TopLevelCommandManager tlcManager, SettingsModel settings)
// TopLevelCommandManager tlcManager,
public AliasManager(SettingsService settingsService)
{
_topLevelCommandManager = tlcManager;
_aliases = settings.Aliases;
// _topLevelCommandManager = tlcManager;
_settingsService = settingsService;
if (_aliases.Count == 0)
if (Aliases.Count == 0)
{
PopulateDefaultAliases();
}
}
private void AddAlias(CommandAlias a) => _aliases.Add(a.SearchPrefix, a);
private void AddAlias(CommandAlias a) => Aliases.Add(a.SearchPrefix, a);
public bool CheckAlias(string searchText)
{
if (_aliases.TryGetValue(searchText, out var alias))
if (Aliases.TryGetValue(searchText, out var alias))
{
try
{
var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
if (topLevelCommand is not null)
{
WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(topLevelCommand.GetPerformCommandMessage());
return true;
}
// var topLevelCommand = _topLevelCommandManager.LookupCommand(alias.CommandId);
// if (topLevelCommand is not null)
// {
// WeakReferenceMessenger.Default.Send<ClearSearchMessage>();
// WeakReferenceMessenger.Default.Send<PerformCommandMessage>(topLevelCommand.GetPerformCommandMessage());
// return true;
// }
}
catch
{
@@ -65,7 +64,7 @@ public partial class AliasManager : ObservableObject
public string? KeysFromId(string commandId)
{
return _aliases
return Aliases
.Where(kv => kv.Value.CommandId == commandId)
.Select(kv => kv.Value.Alias)
.FirstOrDefault();
@@ -73,7 +72,7 @@ public partial class AliasManager : ObservableObject
public CommandAlias? AliasFromId(string commandId)
{
return _aliases
return Aliases
.Where(kv => kv.Value.CommandId == commandId)
.Select(kv => kv.Value)
.FirstOrDefault();
@@ -89,7 +88,7 @@ public partial class AliasManager : ObservableObject
// If we already have _this exact alias_, do nothing
if (newAlias is not null &&
_aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
Aliases.TryGetValue(newAlias.SearchPrefix, out var existingAlias))
{
if (existingAlias.CommandId == commandId)
{
@@ -98,7 +97,7 @@ public partial class AliasManager : ObservableObject
}
List<CommandAlias> toRemove = [];
foreach (var kv in _aliases)
foreach (var kv in Aliases)
{
// Look for the old aliases for the command, and remove it
if (kv.Value.CommandId == commandId)
@@ -112,18 +111,18 @@ public partial class AliasManager : ObservableObject
toRemove.Add(kv.Value);
// Remove alias from other TopLevelViewModels it may be assigned to
var topLevelCommand = _topLevelCommandManager.LookupCommand(kv.Value.CommandId);
if (topLevelCommand is not null)
{
topLevelCommand.AliasText = string.Empty;
}
// var topLevelCommand = _topLevelCommandManager.LookupCommand(kv.Value.CommandId);
// if (topLevelCommand is not null)
// {
// topLevelCommand.AliasText = string.Empty;
// }
}
}
foreach (var alias in toRemove)
{
// REMEMBER, SearchPrefix is what we use as keys
_aliases.Remove(alias.SearchPrefix);
Aliases.Remove(alias.SearchPrefix);
}
if (newAlias is not null)

View File

@@ -1,18 +1,20 @@
// 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.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.CmdPal.Core.Common;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public abstract partial class AppExtensionHost : IExtensionHost
{
private readonly ILogger _logger;
private static readonly GlobalLogPageContext _globalLogPageContext = new();
private static ulong _hostingHwnd;
@@ -27,6 +29,11 @@ public abstract partial class AppExtensionHost : IExtensionHost
public static void SetHostHwnd(ulong hostHwnd) => _hostingHwnd = hostHwnd;
public AppExtensionHost(ILogger logger)
{
_logger = logger;
}
public void DebugLog(string message)
{
#if DEBUG
@@ -60,7 +67,7 @@ public abstract partial class AppExtensionHost : IExtensionHost
return Task.CompletedTask.AsAsyncAction();
}
CoreLogger.LogDebug(message.Message);
Log_Message(message.Message);
_ = Task.Run(() =>
{
@@ -158,6 +165,9 @@ public abstract partial class AppExtensionHost : IExtensionHost
}
public abstract string? GetExtensionDisplayName();
[LoggerMessage(Level = LogLevel.Debug, Message = "{message}")]
partial void Log_Message(string message);
}
public interface IAppHostService

View File

@@ -2,25 +2,12 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AppStateModel : ObservableObject
{
[JsonIgnore]
public static readonly string FilePath;
public event TypedEventHandler<AppStateModel, object?>? StateChanged;
///////////////////////////////////////////////////////////////////////////
// STATE HERE
// Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
@@ -28,144 +15,4 @@ public partial class AppStateModel : ObservableObject
public RecentCommandsManager RecentCommands { get; set; } = new();
public List<string> RunHistory { get; set; } = [];
// END SETTINGS
///////////////////////////////////////////////////////////////////////////
static AppStateModel()
{
FilePath = StateJsonPath();
}
public static AppStateModel LoadState()
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(LoadState)}");
}
if (!File.Exists(FilePath))
{
Debug.WriteLine("The provided settings file does not exist");
return new();
}
try
{
// Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath);
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
Debug.WriteLine(loaded is not null ? "Loaded settings file" : "Failed to parse");
return loaded ?? new();
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return new();
}
public static void SaveState(AppStateModel model)
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(SaveState)}");
}
try
{
// Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel!);
// validate JSON
if (JsonNode.Parse(settingsJson) is not JsonObject newSettings)
{
Logger.LogError("Failed to parse app state as a JsonObject.");
return;
}
// read previous settings
if (!TryReadSavedState(out var savedSettings))
{
savedSettings = new JsonObject();
}
// merge new settings into old ones
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value?.DeepClone();
}
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel!.Options);
File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should
// have a file change watcher on the settings file, and
// reload the settings then
model.StateChanged?.Invoke(model, null);
}
catch (Exception ex)
{
Logger.LogError($"Failed to save application state to {FilePath}:", ex);
}
}
private static bool TryReadSavedState([NotNullWhen(true)] out JsonObject? savedSettings)
{
savedSettings = null;
// read existing content from the file
string oldContent;
try
{
if (File.Exists(FilePath))
{
oldContent = File.ReadAllText(FilePath);
}
else
{
// file doesn't exist (might not have been created yet), so consider this a success
// and return empty settings
savedSettings = new JsonObject();
return true;
}
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to read app state file {FilePath}:\n{ex}");
return false;
}
// detect empty file, just for sake of logging
if (string.IsNullOrWhiteSpace(oldContent))
{
Logger.LogInfo($"App state file is empty: {FilePath}");
return false;
}
// is it valid JSON?
try
{
savedSettings = JsonNode.Parse(oldContent) as JsonObject;
return savedSettings != null;
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to parse app state from {FilePath}:\n{ex}");
return false;
}
}
internal static string StateJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
// now, the settings is just next to the exe
return Path.Combine(directory, "state.json");
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Common;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AppStateService
{
private readonly ILogger _logger;
private readonly string _filePath;
private AppStateModel _appStateModel;
public event TypedEventHandler<AppStateModel, object?>? StateChanged;
public AppStateModel CurrentSettings => _appStateModel;
public AppStateService(ILogger logger)
{
_logger = logger;
_filePath = PersistenceService.SettingsJsonPath("state.json");
_appStateModel = LoadState();
}
private AppStateModel LoadState()
{
return PersistenceService.LoadObject<AppStateModel>(_filePath, JsonSerializationContext.Default.AppStateModel!, _logger);
}
public void SaveSettings(AppStateModel model)
{
PersistenceService.SaveObject(
model,
_filePath,
JsonSerializationContext.Default.AppStateModel,
JsonSerializationContext.Default.Options,
null,
afterWriteCallback: m => FinalizeStateSave(m),
_logger);
}
private void FinalizeStateSave(AppStateModel model)
{
_appStateModel = model;
StateChanged?.Invoke(model, null);
}
}

View File

@@ -87,7 +87,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
Color.FromArgb(255, 126, 115, 95), // #7e735f
];
private readonly SettingsModel _settings;
private readonly SettingsService _settingsService;
private readonly UISettings _uiSettings;
private readonly IThemeService _themeService;
private readonly DispatcherQueueTimer _saveTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
@@ -96,22 +96,24 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
private ElementTheme? _elementThemeOverride;
private Color _currentSystemAccentColor;
private SettingsModel Settings => _settingsService.CurrentSettings;
public ObservableCollection<Color> Swatches => WindowsColorSwatches;
public int ThemeIndex
{
get => (int)_settings.Theme;
get => (int)Settings.Theme;
set => Theme = (UserTheme)value;
}
public UserTheme Theme
{
get => _settings.Theme;
get => Settings.Theme;
set
{
if (_settings.Theme != value)
if (Settings.Theme != value)
{
_settings.Theme = value;
Settings.Theme = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ThemeIndex));
Save();
@@ -121,12 +123,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public ColorizationMode ColorizationMode
{
get => _settings.ColorizationMode;
get => Settings.ColorizationMode;
set
{
if (_settings.ColorizationMode != value)
if (Settings.ColorizationMode != value)
{
_settings.ColorizationMode = value;
Settings.ColorizationMode = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ColorizationModeIndex));
OnPropertyChanged(nameof(IsCustomTintVisible));
@@ -152,18 +154,18 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int ColorizationModeIndex
{
get => (int)_settings.ColorizationMode;
get => (int)Settings.ColorizationMode;
set => ColorizationMode = (ColorizationMode)value;
}
public Color ThemeColor
{
get => _settings.CustomThemeColor;
get => Settings.CustomThemeColor;
set
{
if (_settings.CustomThemeColor != value)
if (Settings.CustomThemeColor != value)
{
_settings.CustomThemeColor = value;
Settings.CustomThemeColor = value;
OnPropertyChanged();
@@ -179,10 +181,10 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int ColorIntensity
{
get => _settings.CustomThemeColorIntensity;
get => Settings.CustomThemeColorIntensity;
set
{
_settings.CustomThemeColorIntensity = value;
Settings.CustomThemeColorIntensity = value;
OnPropertyChanged();
OnPropertyChanged(nameof(EffectiveTintIntensity));
Save();
@@ -203,12 +205,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public string BackgroundImagePath
{
get => _settings.BackgroundImagePath ?? string.Empty;
get => Settings.BackgroundImagePath ?? string.Empty;
set
{
if (_settings.BackgroundImagePath != value)
if (Settings.BackgroundImagePath != value)
{
_settings.BackgroundImagePath = value;
Settings.BackgroundImagePath = value;
OnPropertyChanged();
if (BackgroundImageOpacity == 0)
@@ -223,12 +225,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageOpacity
{
get => _settings.BackgroundImageOpacity;
get => Settings.BackgroundImageOpacity;
set
{
if (_settings.BackgroundImageOpacity != value)
if (Settings.BackgroundImageOpacity != value)
{
_settings.BackgroundImageOpacity = value;
Settings.BackgroundImageOpacity = value;
OnPropertyChanged();
Save();
}
@@ -237,12 +239,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageBrightness
{
get => _settings.BackgroundImageBrightness;
get => Settings.BackgroundImageBrightness;
set
{
if (_settings.BackgroundImageBrightness != value)
if (Settings.BackgroundImageBrightness != value)
{
_settings.BackgroundImageBrightness = value;
Settings.BackgroundImageBrightness = value;
OnPropertyChanged();
Save();
}
@@ -251,12 +253,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int BackgroundImageBlurAmount
{
get => _settings.BackgroundImageBlurAmount;
get => Settings.BackgroundImageBlurAmount;
set
{
if (_settings.BackgroundImageBlurAmount != value)
if (Settings.BackgroundImageBlurAmount != value)
{
_settings.BackgroundImageBlurAmount = value;
Settings.BackgroundImageBlurAmount = value;
OnPropertyChanged();
Save();
}
@@ -265,12 +267,12 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public BackgroundImageFit BackgroundImageFit
{
get => _settings.BackgroundImageFit;
get => Settings.BackgroundImageFit;
set
{
if (_settings.BackgroundImageFit != value)
if (Settings.BackgroundImageFit != value)
{
_settings.BackgroundImageFit = value;
Settings.BackgroundImageFit = value;
OnPropertyChanged();
OnPropertyChanged(nameof(BackgroundImageFitIndex));
Save();
@@ -387,7 +389,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
[ObservableProperty]
public partial bool IsColorizationDetailsExpanded { get; set; }
public bool IsCustomTintVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;
public bool IsCustomTintVisible => Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;
public bool IsColorIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor;
@@ -399,12 +401,13 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
public int EffectiveTintIntensity => _settings.ColorizationMode is ColorizationMode.Image
? _settings.BackgroundImageTintIntensity
: _settings.CustomThemeColorIntensity;
public bool IsCustomTintIntensityVisible => Settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image;
public bool IsBackgroundControlsVisible => _settings.ColorizationMode is ColorizationMode.Image;
public bool IsBackgroundControlsVisible => Settings.ColorizationMode is ColorizationMode.Image;
public bool IsNoBackgroundVisible => _settings.ColorizationMode is ColorizationMode.None;
public bool IsNoBackgroundVisible => Settings.ColorizationMode is ColorizationMode.None;
public bool IsAccentColorControlsVisible => _settings.ColorizationMode is ColorizationMode.WindowsAccentColor;
public bool IsAccentColorControlsVisible => Settings.ColorizationMode is ColorizationMode.WindowsAccentColor;
public bool IsResetButtonVisible => _settings.ColorizationMode is ColorizationMode.Image;
@@ -436,11 +439,11 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri)
: null;
public AppearanceSettingsViewModel(IThemeService themeService, SettingsModel settings)
public AppearanceSettingsViewModel(IThemeService themeService, SettingsService settingsService)
{
_themeService = themeService;
_themeService.ThemeChanged += ThemeServiceOnThemeChanged;
_settings = settings;
_settingsService = settingsService;
_uiSettings = new UISettings();
_uiSettings.ColorValuesChanged += UiSettingsOnColorValuesChanged;
@@ -448,7 +451,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
Reapply();
IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled;
SettingsIsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None && IsBackgroundSettingsEnabled;
}
private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender));
@@ -469,7 +472,7 @@ public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDis
private void Save()
{
SettingsModel.SaveSettings(_settings);
_settingsService.SaveSettings(Settings);
_saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
}

View File

@@ -0,0 +1,24 @@
// 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.Extensions.Logging;
namespace Microsoft.CmdPal.UI.ViewModels;
/// <summary>
/// Encapsulates a navigation request within Command Palette view models.
/// </summary>
/// <param name="TargetViewModel">A view model that should be navigated to.</param>
/// <param name="NavigationToken"> A <see cref="CancellationToken"/> that can be used to cancel the pending navigation.</param>
public record AsyncNavigationRequest(object? TargetViewModel, CancellationToken NavigationToken);
#pragma warning disable SA1402 // File may only contain a single type
public record AsyncListPageNavigationRequest(object? TargetViewModel, SettingsService SettingsService, ILogger Logger, CancellationToken NavigationToken)
: AsyncNavigationRequest(TargetViewModel, NavigationToken);
public record AsyncContentPageNavigationRequest(object? TargetViewModel, object? ImageProvider, CancellationToken NavigationToken)
: AsyncNavigationRequest(TargetViewModel, NavigationToken);
#pragma warning restore SA1402 // File may only contain a single type

View File

@@ -4,15 +4,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
using Windows.System;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandBarViewModel : ObservableObject,
IRecipient<UpdateCommandBarMessage>
{
private readonly ILogger _logger;
public ICommandBarContext? SelectedItem
{
get => field;
@@ -48,8 +51,9 @@ public partial class CommandBarViewModel : ObservableObject,
[ObservableProperty]
public partial PageViewModel? CurrentPage { get; set; }
public CommandBarViewModel()
public CommandBarViewModel(ILogger logger)
{
_logger = logger;
WeakReferenceMessenger.Default.Register<UpdateCommandBarMessage>(this);
}

View File

@@ -3,11 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels.Models;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
namespace Microsoft.CmdPal.UI.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel

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