mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-04 09:30:04 +02:00
Compare commits
13 Commits
copilot/im
...
dev/vanzue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d4a65bf32 | ||
|
|
032b6163cc | ||
|
|
1ee721c306 | ||
|
|
ee702f9edf | ||
|
|
916182e47d | ||
|
|
0e4d1c1496 | ||
|
|
d6bebf8423 | ||
|
|
ebf36a324a | ||
|
|
eba7760ee1 | ||
|
|
0998bed0d4 | ||
|
|
ad958759fa | ||
|
|
dbf16cf62a | ||
|
|
38d460cc2b |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
9
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -40,6 +40,7 @@ body:
|
||||
- Other (please specify in "Steps to Reproduce")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Area(s) with issue?
|
||||
@@ -105,13 +106,7 @@ body:
|
||||
placeholder: What happened instead?
|
||||
validations:
|
||||
required: false
|
||||
- type: upload
|
||||
id: bugreportfile
|
||||
attributes:
|
||||
label: Upload Bug Report ZIP-file
|
||||
description: Right-clicking the PowerToys tray icon in the taskbar and selecting “Report bug” generates a ZIP file containing diagnostic information about your setup and PowerToys logs, helping us better understand and troubleshoot the issue.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- id: additionalInfo
|
||||
type: textarea
|
||||
attributes:
|
||||
|
||||
24
.github/actions/spell-check/allow/code.txt
vendored
24
.github/actions/spell-check/allow/code.txt
vendored
@@ -1,7 +1,6 @@
|
||||
# COLORS
|
||||
|
||||
argb
|
||||
Bgr
|
||||
bgra
|
||||
BLACKONWHITE
|
||||
BLUEGRAY
|
||||
@@ -29,7 +28,6 @@ RUS
|
||||
|
||||
AYUV
|
||||
bak
|
||||
HDP
|
||||
Bcl
|
||||
bgcode
|
||||
Deflatealgorithm
|
||||
@@ -40,7 +38,6 @@ Gbps
|
||||
gcode
|
||||
Heatshrink
|
||||
Mbits
|
||||
Kbits
|
||||
MBs
|
||||
mkv
|
||||
msix
|
||||
@@ -48,7 +45,6 @@ nupkg
|
||||
petabyte
|
||||
resw
|
||||
resx
|
||||
runtimeconfig
|
||||
srt
|
||||
Stereolithography
|
||||
terabyte
|
||||
@@ -101,7 +97,6 @@ Yubico
|
||||
Perplexity
|
||||
Groq
|
||||
svgl
|
||||
devhome
|
||||
|
||||
# KEYS
|
||||
|
||||
@@ -300,8 +295,6 @@ pwa
|
||||
|
||||
AOT
|
||||
Aot
|
||||
ify
|
||||
TFM
|
||||
|
||||
# YML
|
||||
onefuzz
|
||||
@@ -320,7 +313,6 @@ xef
|
||||
xes
|
||||
PACKAGEVERSIONNUMBER
|
||||
APPXMANIFESTVERSION
|
||||
PROGMAN
|
||||
|
||||
# MRU lists
|
||||
CACHEWRITE
|
||||
@@ -330,15 +322,6 @@ REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
MEMORYSTATUSEX
|
||||
ABE
|
||||
HTCAPTION
|
||||
POSCHANGED
|
||||
QUERYPOS
|
||||
SETAUTOHIDEBAR
|
||||
WINDOWPOS
|
||||
WINEVENTPROC
|
||||
WORKERW
|
||||
|
||||
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
||||
DDDD
|
||||
@@ -359,10 +342,3 @@ reportbug
|
||||
#ffmpeg
|
||||
crf
|
||||
nostdin
|
||||
|
||||
# Performance counter keys
|
||||
engtype
|
||||
Nonpaged
|
||||
|
||||
# XAML
|
||||
Untargeted
|
||||
|
||||
4
.github/actions/spell-check/allow/names.txt
vendored
4
.github/actions/spell-check/allow/names.txt
vendored
@@ -192,7 +192,6 @@ ycv
|
||||
yeelam
|
||||
Yuniardi
|
||||
yuyoyuppe
|
||||
zadjii
|
||||
Zeol
|
||||
Zhao
|
||||
Zhaopeng
|
||||
@@ -207,7 +206,6 @@ Bilibili
|
||||
BVID
|
||||
capturevideosample
|
||||
cmdow
|
||||
Contoso
|
||||
Controlz
|
||||
cortana
|
||||
devhints
|
||||
@@ -230,7 +228,6 @@ regedit
|
||||
roslyn
|
||||
Skia
|
||||
Spotify
|
||||
taskmgr
|
||||
tldr
|
||||
Vanara
|
||||
wangyi
|
||||
@@ -246,3 +243,4 @@ xamlstyler
|
||||
Xavalon
|
||||
Xbox
|
||||
Youdao
|
||||
zadjii
|
||||
|
||||
5
.github/actions/spell-check/excludes.txt
vendored
5
.github/actions/spell-check/excludes.txt
vendored
@@ -110,8 +110,7 @@
|
||||
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
|
||||
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
||||
@@ -143,5 +142,3 @@ ignore$
|
||||
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
|
||||
^src/common/CalculatorEngineCommon/exprtk\.hpp$
|
||||
src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage.cs
|
||||
^src/modules/powerrename/unittests/testdata/avif_test\.avif$
|
||||
^src/modules/powerrename/unittests/testdata/heif_test\.heic$
|
||||
|
||||
906
.github/actions/spell-check/expect.txt
vendored
906
.github/actions/spell-check/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
3
.github/actions/spell-check/patterns.txt
vendored
3
.github/actions/spell-check/patterns.txt
vendored
@@ -289,6 +289,3 @@ St&yle
|
||||
|
||||
# Microsoft Store URLs and product IDs
|
||||
ms-windows-store://\S+
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
2
.github/copilot-instructions.md
vendored
2
.github/copilot-instructions.md
vendored
@@ -33,4 +33,4 @@ These are auto-applied based on file location:
|
||||
## Detailed Documentation
|
||||
|
||||
- [Architecture](../doc/devdocs/core/architecture.md)
|
||||
- [Coding Style](../doc/devdocs/development/style.md)
|
||||
- [Coding Style](../doc/devdocs/development/style.md)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for shared libraries including logging, IPC, settings, DPI, telemetry, and utilities consumed by multiple modules'
|
||||
applyTo: 'src/common/**'
|
||||
---
|
||||
|
||||
# Common Libraries – Shared Code Guidance
|
||||
|
||||
Guidelines for modifying shared code in `src/common/`. Changes here can have wide-reaching impact across the entire PowerToys codebase.
|
||||
|
||||
## Scope
|
||||
|
||||
- Logging infrastructure (`src/common/logger/`)
|
||||
- IPC primitives and named pipe utilities
|
||||
- Settings serialization and management
|
||||
- DPI awareness and scaling utilities
|
||||
- Telemetry helpers
|
||||
- General utilities (JSON parsing, string helpers, etc.)
|
||||
|
||||
## Guidelines
|
||||
|
||||
### API Stability
|
||||
|
||||
- Avoid breaking public headers/APIs; if changed, search & update all callers
|
||||
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility
|
||||
- When modifying public interfaces, grep the entire codebase for usages
|
||||
|
||||
### Performance
|
||||
|
||||
- Watch perf in hot paths (hooks, timers, serialization)
|
||||
- Avoid avoidable allocations in frequently called code
|
||||
- Profile changes that touch performance-sensitive areas
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Ask before adding third-party deps or changing serialization formats
|
||||
- New dependencies must be MIT-licensed or approved by PM team
|
||||
- Add any new external packages to `NOTICE.md`
|
||||
|
||||
### Logging
|
||||
|
||||
- C++ logging uses spdlog (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`)
|
||||
- Initialize with `init_logger()` early in startup
|
||||
- Keep hot paths quiet – no logging in tight loops or hooks
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- No unintended ABI breaks
|
||||
- No noisy logs in hot paths
|
||||
- New non-obvious symbols briefly commented
|
||||
- All callers updated when interfaces change
|
||||
|
||||
## Code Style
|
||||
|
||||
- **C++**: Follow `.clang-format` in `src/`; use Modern C++ patterns per C++ Core Guidelines
|
||||
- **C#**: Follow `src/.editorconfig`; enforce StyleCop.Analyzers
|
||||
|
||||
## Validation
|
||||
|
||||
- Build: `tools\build\build.cmd` from `src/common/` folder
|
||||
- Verify no ABI breaks: grep for changed function/struct names across codebase
|
||||
- Check logs: ensure no new logging in performance-critical paths
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for Runner and Settings UI components that communicate via named pipes and manage module lifecycle'
|
||||
applyTo: 'src/runner/**,src/settings-ui/**'
|
||||
---
|
||||
|
||||
# Runner & Settings UI – Core Components Guidance
|
||||
|
||||
Guidelines for modifying the Runner (tray/module loader) and Settings UI (configuration app). These components communicate via Windows Named Pipes using JSON messages.
|
||||
|
||||
## Runner (`src/runner/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- Module bootstrap, hotkey management, settings bridge, update/elevation handling
|
||||
|
||||
### Guidelines
|
||||
|
||||
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`
|
||||
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules
|
||||
- Keep startup lean: avoid blocking/network calls in early init path
|
||||
- Preserve GPO & elevation behaviors; confirm no regression in policy handling
|
||||
- Ask before modifying update workflow or elevation logic
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Stable startup, consistent contracts, no unnecessary logging noise
|
||||
|
||||
## Settings UI (`src/settings-ui/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- WinUI/WPF UI, communicates with Runner over named pipes; manages persisted settings schema
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Don't break settings schema silently; add migration when shape changes
|
||||
- If IPC/JSON contracts change, align with `src/runner/**` implementation
|
||||
- Keep UI responsive: marshal to UI thread for UI-bound operations
|
||||
- Reuse existing styles/resources; avoid duplicate theme keys
|
||||
- Add/adjust migration or serialization tests when changing persisted settings
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Schema integrity preserved, responsive UI, consistent contracts, no style duplication
|
||||
|
||||
## Shared Concerns
|
||||
|
||||
### IPC Contract Changes
|
||||
|
||||
When modifying the JSON message format between Runner and Settings UI:
|
||||
|
||||
1. Update both `src/runner/` and `src/settings-ui/` in the same PR
|
||||
2. Preserve backward compatibility where possible
|
||||
3. Add migration logic for settings schema changes
|
||||
4. Test both directions of communication
|
||||
|
||||
### Code Style
|
||||
|
||||
- **C++ (Runner)**: Follow `.clang-format` in `src/`
|
||||
- **C# (Settings UI)**: Follow `src/.editorconfig`, use StyleCop.Analyzers
|
||||
- **XAML**: Use XamlStyler or run `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
|
||||
## Validation
|
||||
|
||||
- Build Runner: `tools\build\build.cmd` from `src/runner/`
|
||||
- Build Settings UI: `tools\build\build.cmd` from `src/settings-ui/`
|
||||
- Test IPC: Launch both Runner and Settings UI, verify communication works
|
||||
- Schema changes: Run serialization tests if settings shape changed
|
||||
@@ -33,7 +33,7 @@ Generated Files/ReleaseNotes/
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **GitHub CLI (`gh`) installed and authenticated** — The collection script uses `gh pr view` and `gh api graphql` to fetch PR metadata and co-author information. Run `gh auth status` to verify; if not logged in, run `gh auth login` first. See [Step 1.0.0](./references/step1-collection.md) for details.
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- MCP Server: github-mcp-server installed
|
||||
- GitHub Copilot code review enabled for the org/repo
|
||||
|
||||
@@ -49,10 +49,6 @@ Generated Files/ReleaseNotes/
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 1.0 Verify gh auth + MemberList │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 1.1 Collect PRs (stable range) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
@@ -89,7 +85,6 @@ Generated Files/ReleaseNotes/
|
||||
|
||||
| Step | Action | Details |
|
||||
|------|--------|---------|
|
||||
| 1.0 | Verify prerequisites | `gh auth status` must pass; generate MemberList.md |
|
||||
| 1.1 | Collect PRs | From previous release tag on `stable` branch → `sorted_prs.csv` |
|
||||
| 1.2 | Assign Milestones | Ensure all PRs have correct milestone |
|
||||
| 2.1–2.4 | Label PRs | Auto-suggest + human label low-confidence |
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
- Added mouse button actions so you can choose what left, right, or middle click does in [#1234](https://github.com/microsoft/PowerToys/pull/1234) by [@PesBandi](https://github.com/PesBandi)
|
||||
- Added mouse button actions so you can choose what left, right, or middle click does. Thanks [@PesBandi](https://github.com/PesBandi)!
|
||||
|
||||
- Aligned window styling with current Windows theme for a cleaner look in [#1235](https://github.com/microsoft/PowerToys/pull/1235) by [@sadirano](https://github.com/sadirano)
|
||||
- Aligned window styling with current Windows theme for a cleaner look. Thanks [@sadirano](https://github.com/sadirano)!
|
||||
|
||||
- Ensured screen readers are notified when the selected item in the list changes for better accessibility in [#1236](https://github.com/microsoft/PowerToys/pull/1236)
|
||||
- Ensured screen readers are notified when the selected item in the list changes for better accessibility.
|
||||
|
||||
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours in [#1237](https://github.com/microsoft/PowerToys/pull/1237)
|
||||
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours.
|
||||
|
||||
- Fixed Alt+Left Arrow navigation not working when search box contains text in [#1238](https://github.com/microsoft/PowerToys/pull/1238) by [@jiripolasek](https://github.com/jiripolasek)
|
||||
- Fixed Alt+Left Arrow navigation not working when search box contains text. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
@@ -1,7 +1,6 @@
|
||||
# Step 1: Collection and Milestones
|
||||
|
||||
## 1.0 To-do
|
||||
- 1.0.0 Verify GitHub CLI authentication (REQUIRED)
|
||||
- 1.0.1 Generate MemberList.md (REQUIRED)
|
||||
- 1.1 Collect PRs
|
||||
- 1.2 Assign Milestones (REQUIRED)
|
||||
@@ -21,34 +20,6 @@
|
||||
|
||||
---
|
||||
|
||||
## 1.0.0 Verify GitHub CLI Authentication (REQUIRED)
|
||||
|
||||
⚠️ **BLOCKING:** The collection script requires an authenticated `gh` CLI to fetch PR metadata and co-author information via GitHub's GraphQL API. Without authentication, PR data and `NeedThanks` attribution will be incomplete.
|
||||
|
||||
### Check authentication status
|
||||
|
||||
```powershell
|
||||
gh auth status
|
||||
```
|
||||
|
||||
**If authenticated:** You'll see `Logged in to github.com account <username>`. Proceed to 1.0.1.
|
||||
|
||||
**If NOT authenticated:** Run the login flow before continuing:
|
||||
|
||||
```powershell
|
||||
# Interactive login (opens browser for OAuth)
|
||||
gh auth login --hostname github.com --web
|
||||
|
||||
# Or use a personal access token
|
||||
gh auth login --with-token <<< "YOUR_GITHUB_TOKEN"
|
||||
```
|
||||
|
||||
**Required scopes:** `repo` (for reading PR data and assigning milestones)
|
||||
|
||||
After login, verify again with `gh auth status` and confirm exit code 0.
|
||||
|
||||
---
|
||||
|
||||
## 1.0.1 Generate MemberList.md (REQUIRED)
|
||||
|
||||
Create `Generated Files/ReleaseNotes/MemberList.md` from the **PowerToys core team** section in [COMMUNITY.md](../../../COMMUNITY.md).
|
||||
@@ -109,8 +80,6 @@ The script detects both merge commits (`Merge pull request #12345`) and squash c
|
||||
**Output** (in `Generated Files/ReleaseNotes/`):
|
||||
- `milestone_prs.json` - raw PR data
|
||||
- `sorted_prs.csv` - sorted PR list with columns: Id, Title, Labels, Author, Url, Body, CopilotSummary, NeedThanks
|
||||
- `Author`: Comma-separated list of all contributors (PR opener + co-authors from commit trailers)
|
||||
- `NeedThanks`: Comma-separated list of external contributors to thank (excludes core team members from MemberList.md). Empty string means no thanks needed.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown f
|
||||
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “That’s exactly what I need” or “Yes, that’s an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
|
||||
- If nothing special on impact or unclear impact, mark as needing human summary
|
||||
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
|
||||
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be thanked (empty = no thanks needed, all authors are core team). For each non-empty `NeedThanks` value, append thanks for **every** listed contributor: `Thanks [@user1](https://github.com/user1)!` for a single contributor, or `Thanks [@user1](https://github.com/user1) and [@user2](https://github.com/user2)!` for two, or `Thanks [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)!` for three or more.
|
||||
- If the column `NeedThanks` in CSV is `True`, append: `Thanks [@Author](https://github.com/Author)!`
|
||||
- Do NOT include PR numbers in bullet lines
|
||||
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
|
||||
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`
|
||||
@@ -72,13 +72,13 @@ Some items in the Development section may overlap and should be moved to the Mod
|
||||
|
||||
## Advanced Paste
|
||||
|
||||
- Wrapped paste option lists in a single ScrollViewer in [#5678](https://github.com/microsoft/PowerToys/pull/5678)
|
||||
- Added image input handling for AI-powered transformations in [#5679](https://github.com/microsoft/PowerToys/pull/5679)
|
||||
- Wrapped paste option lists in a single ScrollViewer
|
||||
- Added image input handling for AI-powered transformations
|
||||
...
|
||||
|
||||
## Awake
|
||||
|
||||
- Fixed timed mode expiration in [#5680](https://github.com/microsoft/PowerToys/pull/5680) by [@daverayment](https://github.com/daverayment)
|
||||
- Fixed timed mode expiration. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
@@ -42,7 +42,30 @@ param(
|
||||
[string]$OutputJson = "milestone_prs.json"
|
||||
)
|
||||
|
||||
# (See top-level synopsis above for full documentation)
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Dump merged PR information whose merge commits are reachable from EndCommit but not from StartCommit.
|
||||
.DESCRIPTION
|
||||
Uses git rev-list to compute commits in the (StartCommit, EndCommit] range, extracts PR numbers from merge commit messages,
|
||||
queries GitHub (gh CLI) for details, then outputs a CSV.
|
||||
|
||||
PR merge commit messages in PowerToys generally contain patterns like:
|
||||
Merge pull request #12345 from ...
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -Branch stable
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -EndCommit 89ef7654 -OutputCsv changes.csv
|
||||
|
||||
.NOTES
|
||||
Requires: gh CLI authenticated; git available in working directory (must be inside PowerToys repo clone).
|
||||
CopilotSummary behavior:
|
||||
- Attempts to locate the latest GitHub Copilot authored review (preferred).
|
||||
- If no review is found, lazily fetches PR comments to look for a Copilot-authored comment.
|
||||
- Normalizes whitespace and strips newlines. Empty when no Copilot activity detected.
|
||||
- Run with -Verbose to see whether the summary came from a 'review' or 'comment' source.
|
||||
#>
|
||||
|
||||
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
|
||||
function Write-Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
|
||||
@@ -128,11 +151,11 @@ catch {
|
||||
}
|
||||
|
||||
Write-Info "Collecting commits between $startSha..$endSha (excluding start, including end)."
|
||||
# Get list of commits reachable from end but not from start.
|
||||
# IMPORTANT: In PowerShell, the .. operator creates a numeric/char range. If $startSha and $endSha look like hex strings,
|
||||
# `$startSha..$endSha` must be passed as a single string argument.
|
||||
$rangeArg = "$startSha..$endSha"
|
||||
$commitList = git rev-list $rangeArg
|
||||
# Get list of commits reachable from end but not from start.
|
||||
# IMPORTANT: In PowerShell, the .. operator creates a numeric/char range. If $startSha and $endSha look like hex strings,
|
||||
# `$startSha..$endSha` must be passed as a single string argument.
|
||||
$rangeArg = "$startSha..$endSha"
|
||||
$commitList = git rev-list $rangeArg
|
||||
|
||||
# Normalize list (filter out empty strings)
|
||||
$normalizedCommits = $commitList | Where-Object { $_ -and $_.Trim() -ne '' }
|
||||
@@ -187,63 +210,6 @@ $prNumbers = $mergeCommits | Select-Object -ExpandProperty Pr -Unique | Sort-Obj
|
||||
Write-Info ("Found {0} unique PRs: {1}" -f $prNumbers.Count, ($prNumbers -join ', '))
|
||||
Write-DebugMsg ("Total merge commits examined: {0}" -f $mergeCommits.Count)
|
||||
|
||||
# Build a map of PR number → list of commit SHAs (for co-author extraction)
|
||||
$prToCommits = @{}
|
||||
foreach ($mc in $mergeCommits) {
|
||||
if (-not $prToCommits.ContainsKey($mc.Pr)) {
|
||||
$prToCommits[$mc.Pr] = @()
|
||||
}
|
||||
$prToCommits[$mc.Pr] += $mc.Sha
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get all authors (including co-authors) for a set of commits via GitHub GraphQL API.
|
||||
.DESCRIPTION
|
||||
Uses the Commit.authors field in GitHub's GraphQL API which natively includes
|
||||
co-authors (from Co-authored-by trailers). Returns GitHub usernames (login)
|
||||
without any email parsing — GitHub resolves the association for us.
|
||||
|
||||
NOTE: For squash merges this captures all co-authors correctly because GitHub
|
||||
preserves Co-authored-by trailers in the squash commit. For traditional merge
|
||||
commits, only the merger's author is returned — co-authors on individual PR
|
||||
commits are not traversed. This is acceptable because PowerToys primarily uses
|
||||
squash merging.
|
||||
#>
|
||||
function Get-CommitAuthors {
|
||||
param(
|
||||
[string[]]$CommitShas,
|
||||
[string]$RepoFullName = "microsoft/PowerToys"
|
||||
)
|
||||
$parts = $RepoFullName -split '/'
|
||||
$owner = $parts[0]
|
||||
$repoName = $parts[1]
|
||||
$allAuthors = @()
|
||||
|
||||
foreach ($sha in $CommitShas) {
|
||||
try {
|
||||
$query = "{ repository(owner: `"$owner`", name: `"$repoName`") { object(expression: `"$sha`") { ... on Commit { authors(first: 20) { nodes { user { login } name } } } } } }"
|
||||
$result = gh api graphql -f query="$query" 2>$null | ConvertFrom-Json
|
||||
$nodes = $result.data.repository.object.authors.nodes
|
||||
if ($nodes) {
|
||||
foreach ($node in $nodes) {
|
||||
if ($node.user -and $node.user.login) {
|
||||
$allAuthors += $node.user.login
|
||||
} else {
|
||||
# User without a GitHub account (rare) — use display name as fallback
|
||||
Write-DebugMsg "Commit $sha has an author without GitHub account: $($node.name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-DebugMsg "GraphQL authors query failed for commit ${sha}: $_"
|
||||
}
|
||||
}
|
||||
|
||||
return $allAuthors | Select-Object -Unique
|
||||
}
|
||||
|
||||
# Query GitHub for each PR
|
||||
$prDetails = @()
|
||||
function Get-CopilotSummaryFromPrJson {
|
||||
@@ -341,45 +307,22 @@ foreach ($pr in $prNumbers) {
|
||||
$bodyValue = if ($json.body) { ($json.body -replace "`r", '') -replace "`n", ' ' } else { '' }
|
||||
$bodyValue = $bodyValue -replace '\s+', ' '
|
||||
|
||||
# Collect all contributors: PR author + co-authors from commit messages
|
||||
# Determine if author needs thanks (not in member list)
|
||||
$authorLogin = $json.author.login
|
||||
$allContributors = @($authorLogin)
|
||||
|
||||
# Extract all authors (including co-authors) from associated commits via GitHub GraphQL API
|
||||
if ($prToCommits.ContainsKey([int]$pr)) {
|
||||
$commitAuthors = Get-CommitAuthors -CommitShas $prToCommits[[int]$pr] -RepoFullName $Repo
|
||||
if ($commitAuthors) {
|
||||
$allContributors += $commitAuthors
|
||||
}
|
||||
$needThanks = $true
|
||||
if ($memberList.Count -gt 0 -and $authorLogin) {
|
||||
$needThanks = -not ($memberList -contains $authorLogin)
|
||||
}
|
||||
|
||||
# Deduplicate contributors (case-insensitive)
|
||||
$allContributors = $allContributors | Where-Object { $_ } | Sort-Object -Unique
|
||||
|
||||
# Filter to only external contributors (not in member list) for thanks
|
||||
$externalContributors = @()
|
||||
if ($memberList.Count -gt 0) {
|
||||
$externalContributors = $allContributors | Where-Object { -not ($memberList -contains $_) }
|
||||
} else {
|
||||
$externalContributors = $allContributors
|
||||
}
|
||||
|
||||
# Author column: all contributors (comma-separated)
|
||||
$authorField = ($allContributors -join ', ')
|
||||
|
||||
# NeedThanks column: comma-separated list of external contributors who
|
||||
# deserve thanks attribution. Empty string means no thanks needed.
|
||||
$needThanksField = ($externalContributors -join ', ')
|
||||
|
||||
$prDetails += [PSCustomObject]@{
|
||||
Id = $json.number
|
||||
Title = $json.title
|
||||
Labels = $labelNames
|
||||
Author = $authorField
|
||||
Author = $authorLogin
|
||||
Url = $json.url
|
||||
Body = $bodyValue
|
||||
CopilotSummary = $copilot.Summary
|
||||
NeedThanks = $needThanksField
|
||||
NeedThanks = $needThanks
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
21
.github/skills/winmd-api-search/LICENSE.txt
vendored
21
.github/skills/winmd-api-search/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
192
.github/skills/winmd-api-search/SKILL.md
vendored
192
.github/skills/winmd-api-search/SKILL.md
vendored
@@ -1,192 +0,0 @@
|
||||
---
|
||||
name: winmd-api-search
|
||||
description: 'Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values).'
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# WinMD API Search
|
||||
|
||||
This skill helps you find the right Windows API for any capability and get its full details. It searches a local cache of all WinMD metadata from:
|
||||
|
||||
- **Windows Platform SDK** — all `Windows.*` WinRT APIs (always available, no restore needed)
|
||||
- **WinAppSDK / WinUI** — bundled as a baseline in the cache generator (always available, no restore needed)
|
||||
- **NuGet packages** — any additional packages in restored projects that contain `.winmd` files
|
||||
- **Project-output WinMD** — class libraries (C++/WinRT, C#) that produce `.winmd` as build output
|
||||
|
||||
Even on a fresh clone with no restore or build, you still get full Platform SDK + WinAppSDK coverage.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- User wants to build a feature and you need to find which API provides that capability
|
||||
- User asks "how do I do X?" where X involves a platform feature (camera, files, notifications, sensors, AI, etc.)
|
||||
- You need the exact methods, properties, events, or enumeration values of a type before writing code
|
||||
- You're unsure which control, class, or interface to use for a UI or system task
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **.NET SDK 8.0 or later** — required to build the cache generator. Install from [dotnet.microsoft.com](https://dotnet.microsoft.com/download) if not available.
|
||||
|
||||
## Cache Setup (Required Before First Use)
|
||||
|
||||
All query and search commands read from a local JSON cache. **You must generate the cache before running any queries.**
|
||||
|
||||
```powershell
|
||||
# All projects in the repo (recommended for first run)
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1
|
||||
|
||||
# Single project
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1 -ProjectDir <project-folder>
|
||||
```
|
||||
|
||||
No project restore or build is needed for baseline coverage (Platform SDK + WinAppSDK). For additional NuGet packages, the project needs `dotnet restore` (which generates `project.assets.json`) or a `packages.config` file.
|
||||
|
||||
Cache is stored at `Generated Files\winmd-cache\`, deduplicated per-package+version.
|
||||
|
||||
### What gets indexed
|
||||
|
||||
| Source | When available |
|
||||
|--------|----------------|
|
||||
| Windows Platform SDK | Always (reads from local SDK install) |
|
||||
| WinAppSDK (latest) | Always (bundled as baseline in cache generator) |
|
||||
| WinAppSDK Runtime | When installed on the system (detected via `Get-AppxPackage`) |
|
||||
| Project NuGet packages | After `dotnet restore` or with `packages.config` |
|
||||
| Project-output `.winmd` | After project build (class libraries that produce WinMD) |
|
||||
|
||||
> **Note:** This cache directory should be in `.gitignore` — it's generated, not source.
|
||||
|
||||
## How to Use
|
||||
|
||||
Pick the path that matches the situation:
|
||||
|
||||
---
|
||||
|
||||
### Discover — "I don't know which API to use"
|
||||
|
||||
The user describes a capability in their own words. You need to find the right API.
|
||||
|
||||
**0. Ensure the cache exists**
|
||||
|
||||
If the cache hasn't been generated yet, run `Update-WinMdCache.ps1` first — see [Cache Setup](#cache-setup-required-before-first-use) above.
|
||||
|
||||
**1. Translate user language → search keywords**
|
||||
|
||||
Map the user's daily language to programming terms. Try multiple variations:
|
||||
|
||||
| User says | Search keywords to try (in order) |
|
||||
|-----------|-----------------------------------|
|
||||
| "take a picture" | `camera`, `capture`, `photo`, `MediaCapture` |
|
||||
| "load from disk" | `file open`, `picker`, `FileOpen`, `StorageFile` |
|
||||
| "describe what's in it" | `image description`, `Vision`, `Recognition` |
|
||||
| "show a popup" | `dialog`, `flyout`, `popup`, `ContentDialog` |
|
||||
| "drag and drop" | `drag`, `drop`, `DragDrop` |
|
||||
| "save settings" | `settings`, `ApplicationData`, `LocalSettings` |
|
||||
|
||||
Start with simple everyday words. If results are weak or irrelevant, try the more technical variation.
|
||||
|
||||
**2. Run searches**
|
||||
|
||||
```powershell
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action search -Query "<keyword>"
|
||||
```
|
||||
|
||||
This returns ranked namespaces with top matching types and the **JSON file path**.
|
||||
|
||||
If results have **low scores (below 60) or are irrelevant**, fall back to searching online documentation:
|
||||
|
||||
1. Use web search to find the right API on Microsoft Learn, for example:
|
||||
- `site:learn.microsoft.com/uwp/api <capability keywords>` for `Windows.*` APIs
|
||||
- `site:learn.microsoft.com/windows/windows-app-sdk/api/winrt <capability keywords>` for `Microsoft.*` WinAppSDK APIs
|
||||
2. Read the documentation pages to identify which type matches the user's requirement.
|
||||
3. Once you know the type name, come back and use `-Action members` or `-Action enums` to get the exact local signatures.
|
||||
|
||||
**3. Read the JSON to choose the right API**
|
||||
|
||||
Read the file at the path(s) from the top results. The JSON has all types in that namespace — full members, signatures, parameters, return types, enumeration values.
|
||||
|
||||
Read and decide which types and members fit the user's requirement.
|
||||
|
||||
**4. Look up official documentation for context**
|
||||
|
||||
The cache contains only signatures — no descriptions or usage guidance. For explanations, examples, and remarks, look up the type on Microsoft Learn:
|
||||
|
||||
| Namespace prefix | Documentation base URL |
|
||||
|-----------------|----------------------|
|
||||
| `Windows.*` | `https://learn.microsoft.com/uwp/api/{fully.qualified.typename}` |
|
||||
| `Microsoft.*` (WinAppSDK) | `https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/{fully.qualified.typename}` |
|
||||
|
||||
For example, `Microsoft.UI.Xaml.Controls.NavigationView` maps to:
|
||||
`https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.navigationview`
|
||||
|
||||
**5. Use the API knowledge to answer or write code**
|
||||
|
||||
---
|
||||
|
||||
### Lookup — "I know the API, show me the details"
|
||||
|
||||
You already know (or suspect) the type or namespace name. Go direct:
|
||||
|
||||
```powershell
|
||||
# Get all members of a known type
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.NavigationView"
|
||||
|
||||
# Get enum values
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
|
||||
# List all types in a namespace
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
|
||||
# Browse namespaces
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
```
|
||||
|
||||
If you need full detail beyond what `-Action members` shows, use `-Action search` to get the JSON file path, then read the JSON file directly.
|
||||
|
||||
---
|
||||
|
||||
### Other Commands
|
||||
|
||||
```powershell
|
||||
# List cached projects
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action projects
|
||||
|
||||
# List packages for a project
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action packages
|
||||
|
||||
# Show stats
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action stats
|
||||
```
|
||||
|
||||
> If only one project is cached, `-Project` is auto-selected.
|
||||
> If multiple projects exist, add `-Project <name>` (use `-Action projects` to see available names).
|
||||
> In scan mode, manifest names include a short hash suffix to avoid collisions; you can pass the base project name without the suffix if it's unambiguous.
|
||||
|
||||
## Search Scoring
|
||||
|
||||
The search ranks type names and member names against your query:
|
||||
|
||||
| Score | Match type | Example |
|
||||
|-------|-----------|---------|
|
||||
| 100 | Exact name | `Button` → `Button` |
|
||||
| 80 | Starts with | `Navigation` → `NavigationView` |
|
||||
| 60 | Contains | `Dialog` → `ContentDialog` |
|
||||
| 50 | PascalCase initials | `ASB` → `AutoSuggestBox` |
|
||||
| 40 | Multi-keyword AND | `navigation item` → `NavigationViewItem` |
|
||||
| 20 | Fuzzy character match | `NavVw` → `NavigationView` |
|
||||
|
||||
Results are grouped by namespace. Higher-scored namespaces appear first.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| "Cache not found" | Run `Update-WinMdCache.ps1` |
|
||||
| "Multiple projects cached" | Add `-Project <name>` |
|
||||
| "Namespace not found" | Use `-Action namespaces` to list available ones |
|
||||
| "Type not found" | Use fully qualified name (e.g., `Microsoft.UI.Xaml.Controls.Button`) |
|
||||
| Stale after NuGet update | Re-run `Update-WinMdCache.ps1` |
|
||||
| Cache in git history | Add `Generated Files/` to `.gitignore` |
|
||||
|
||||
## References
|
||||
|
||||
- [Windows Platform SDK API reference](https://learn.microsoft.com/uwp/api/) — documentation for `Windows.*` namespaces
|
||||
- [Windows App SDK API reference](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces
|
||||
@@ -1,505 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Query WinMD API metadata from cached JSON files.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads pre-built JSON cache of WinMD types, members, and namespaces.
|
||||
The cache is organized per-package (deduplicated) with project manifests
|
||||
that map each project to its referenced packages.
|
||||
|
||||
Supports listing namespaces, types, members, searching, enum value lookup,
|
||||
and listing cached projects/packages.
|
||||
|
||||
.PARAMETER Action
|
||||
The query action to perform:
|
||||
- projects : List cached projects
|
||||
- packages : List packages for a project
|
||||
- stats : Show aggregate statistics for a project
|
||||
- namespaces : List all namespaces (optional -Filter prefix)
|
||||
- types : List types in a namespace (-Namespace required)
|
||||
- members : List members of a type (-TypeName required)
|
||||
- search : Search types and members by name (-Query required)
|
||||
- enums : List enum values (-TypeName required)
|
||||
|
||||
.PARAMETER Project
|
||||
Project name to query. Auto-selected if only one project is cached.
|
||||
Use -Action projects to list available projects.
|
||||
|
||||
.PARAMETER Namespace
|
||||
Namespace to query types from (used with -Action types).
|
||||
|
||||
.PARAMETER TypeName
|
||||
Full type name e.g. "Microsoft.UI.Xaml.Controls.Button" (used with -Action members, enums).
|
||||
|
||||
.PARAMETER Query
|
||||
Search query string (used with -Action search).
|
||||
|
||||
.PARAMETER Filter
|
||||
Optional prefix filter for namespaces (used with -Action namespaces).
|
||||
|
||||
.PARAMETER CacheDir
|
||||
Path to the winmd-cache directory. Defaults to "Generated Files\winmd-cache"
|
||||
relative to the workspace root.
|
||||
|
||||
.PARAMETER MaxResults
|
||||
Maximum number of results to return for search. Defaults to 30.
|
||||
|
||||
.EXAMPLE
|
||||
.\Invoke-WinMdQuery.ps1 -Action projects
|
||||
.\Invoke-WinMdQuery.ps1 -Action packages -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action stats -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
.\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
.\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.Button"
|
||||
.\Invoke-WinMdQuery.ps1 -Action search -Query "NavigationView"
|
||||
.\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet('projects', 'packages', 'stats', 'namespaces', 'types', 'members', 'search', 'enums')]
|
||||
[string]$Action,
|
||||
|
||||
[string]$Project,
|
||||
[string]$Namespace,
|
||||
[string]$TypeName,
|
||||
[string]$Query,
|
||||
[string]$Filter,
|
||||
[string]$CacheDir,
|
||||
[int]$MaxResults = 30
|
||||
)
|
||||
|
||||
# ─── Resolve cache directory ─────────────────────────────────────────────────
|
||||
|
||||
if (-not $CacheDir) {
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$scriptDir = $PSScriptRoot
|
||||
$root = (Resolve-Path (Join-Path $scriptDir '..\..\..\..')).Path
|
||||
$CacheDir = Join-Path $root 'Generated Files\winmd-cache'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $CacheDir)) {
|
||||
Write-Error "Cache not found at: $CacheDir`nRun: .\Update-WinMdCache.ps1 (from .github\skills\winmd-api-search\scripts\)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Project resolution helpers ──────────────────────────────────────────────
|
||||
|
||||
function Get-CachedProjects {
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) { return @() }
|
||||
Get-ChildItem $projectsDir -Filter '*.json' | ForEach-Object { $_.BaseName }
|
||||
}
|
||||
|
||||
function Resolve-ProjectManifest {
|
||||
param([string]$Name)
|
||||
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Name) {
|
||||
$path = Join-Path $projectsDir "$Name.json"
|
||||
if (-not (Test-Path $path)) {
|
||||
# Scan mode appends a hash suffix -- try prefix match
|
||||
$matching = @(Get-ChildItem $projectsDir -Filter "${Name}_*.json" -ErrorAction SilentlyContinue)
|
||||
if ($matching.Count -eq 1) {
|
||||
return Get-Content $matching[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
if ($matching.Count -gt 1) {
|
||||
$names = ($matching | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects match '$Name'. Specify the full name: $names"
|
||||
exit 1
|
||||
}
|
||||
$available = (Get-CachedProjects) -join ', '
|
||||
Write-Error "Project '$Name' not found. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
return Get-Content $path -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
# Auto-select if only one project
|
||||
$manifests = Get-ChildItem $projectsDir -Filter '*.json' -ErrorAction SilentlyContinue
|
||||
if ($manifests.Count -eq 0) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
if ($manifests.Count -eq 1) {
|
||||
return Get-Content $manifests[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
$available = ($manifests | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects cached -- use -Project to specify. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-PackageCacheDirs {
|
||||
param($Manifest)
|
||||
$dirs = @()
|
||||
foreach ($pkg in $Manifest.packages) {
|
||||
$dir = Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version
|
||||
if (Test-Path $dir) {
|
||||
$dirs += $dir
|
||||
}
|
||||
}
|
||||
return $dirs
|
||||
}
|
||||
|
||||
# ─── Action: projects ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Projects {
|
||||
$projects = Get-CachedProjects
|
||||
if ($projects.Count -eq 0) {
|
||||
Write-Output "No projects cached."
|
||||
return
|
||||
}
|
||||
Write-Output "Cached projects ($($projects.Count)):"
|
||||
foreach ($p in $projects) {
|
||||
$manifest = Get-Content (Join-Path (Join-Path $CacheDir 'projects') "$p.json") -Raw | ConvertFrom-Json
|
||||
$pkgCount = $manifest.packages.Count
|
||||
Write-Output " $p ($pkgCount package(s))"
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: packages ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Packages {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
Write-Output "Packages for project '$($manifest.projectName)' ($($manifest.packages.Count)):"
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- $($meta.totalTypes) types, $($meta.totalMembers) members"
|
||||
} else {
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- (cache missing)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: stats ───────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Stats {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$totalTypes = 0
|
||||
$totalMembers = 0
|
||||
$totalNamespaces = 0
|
||||
$totalWinMd = 0
|
||||
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
$totalTypes += $meta.totalTypes
|
||||
$totalMembers += $meta.totalMembers
|
||||
$totalNamespaces += $meta.totalNamespaces
|
||||
$totalWinMd += $meta.winMdFiles.Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "WinMD Index Statistics -- $($manifest.projectName)"
|
||||
Write-Output "======================================"
|
||||
Write-Output " Packages: $($manifest.packages.Count)"
|
||||
Write-Output " Namespaces: $totalNamespaces (may overlap across packages)"
|
||||
Write-Output " Types: $totalTypes"
|
||||
Write-Output " Members: $totalMembers"
|
||||
Write-Output " WinMD files: $totalWinMd"
|
||||
}
|
||||
|
||||
# ─── Action: namespaces ──────────────────────────────────────────────────────
|
||||
|
||||
function Get-Namespaces {
|
||||
param([string]$Prefix)
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$allNs = @()
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (Test-Path $nsFile) {
|
||||
$allNs += (Get-Content $nsFile -Raw | ConvertFrom-Json)
|
||||
}
|
||||
}
|
||||
|
||||
$allNs = $allNs | Sort-Object -Unique
|
||||
if ($Prefix) {
|
||||
$allNs = $allNs | Where-Object { $_ -like "$Prefix*" }
|
||||
}
|
||||
$allNs | ForEach-Object { Write-Output $_ }
|
||||
}
|
||||
|
||||
# ─── Action: types ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-TypesInNamespace {
|
||||
param([string]$Ns)
|
||||
if (-not $Ns) {
|
||||
Write-Error "-Namespace is required for 'types' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$safeFile = $Ns.Replace('.', '_') + '.json'
|
||||
$found = $false
|
||||
$seen = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
$found = $true
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
if ($seen.ContainsKey($t.fullName)) { continue }
|
||||
$seen[$t.fullName] = $true
|
||||
Write-Output "$($t.kind) $($t.fullName)$(if ($t.baseType) { " : $($t.baseType)" } else { '' })"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $found) {
|
||||
Write-Error "Namespace not found: $Ns"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: members ─────────────────────────────────────────────────────────
|
||||
|
||||
function Get-MembersOfType {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'members' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 0) {
|
||||
Write-Error "-TypeName must include a namespace (for example: 'MyNamespace.MyType'). Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
Write-Output "$($type.kind) $($type.fullName)"
|
||||
if ($type.baseType) { Write-Output " Extends: $($type.baseType)" }
|
||||
Write-Output ""
|
||||
foreach ($m in $type.members) {
|
||||
Write-Output " [$($m.kind)] $($m.signature)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Action: search ──────────────────────────────────────────────────────────
|
||||
# Ranks namespaces by best match score on type names and member names.
|
||||
# Outputs: ranked namespaces with top matching types and the JSON file path.
|
||||
# The agent can then read the JSON file to inspect all members intelligently.
|
||||
|
||||
function Search-WinMd {
|
||||
param([string]$SearchQuery, [int]$Max)
|
||||
if (-not $SearchQuery) {
|
||||
Write-Error "-Query is required for 'search' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
# Collect: namespace -> { bestScore, matchingTypes[], filePath }
|
||||
$nsResults = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (-not (Test-Path $nsFile)) { continue }
|
||||
$nsList = Get-Content $nsFile -Raw | ConvertFrom-Json
|
||||
|
||||
foreach ($n in $nsList) {
|
||||
$safeFile = $n.Replace('.', '_') + '.json'
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
$typeScore = Get-MatchScore -Name $t.name -FullName $t.fullName -Query $SearchQuery
|
||||
|
||||
# Also search member names for matches
|
||||
$bestMemberScore = 0
|
||||
$matchingMember = $null
|
||||
if ($t.members) {
|
||||
foreach ($m in $t.members) {
|
||||
$memberName = $m.name
|
||||
$mScore = Get-MatchScore -Name $memberName -FullName "$($t.fullName).$memberName" -Query $SearchQuery
|
||||
if ($mScore -gt $bestMemberScore) {
|
||||
$bestMemberScore = $mScore
|
||||
$matchingMember = $m.signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$score = [Math]::Max($typeScore, $bestMemberScore)
|
||||
if ($score -le 0) { continue }
|
||||
|
||||
if (-not $nsResults.ContainsKey($n)) {
|
||||
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePaths = @() }
|
||||
}
|
||||
$entry = $nsResults[$n]
|
||||
if ($score -gt $entry.BestScore) { $entry.BestScore = $score }
|
||||
if ($entry.FilePaths -notcontains $filePath) {
|
||||
$entry.FilePaths += $filePath
|
||||
}
|
||||
|
||||
if ($typeScore -ge $bestMemberScore) {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) [$typeScore]"; Score = $typeScore }
|
||||
} else {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) -> $matchingMember [$bestMemberScore]"; Score = $bestMemberScore }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($nsResults.Count -eq 0) {
|
||||
Write-Output "No results found for: $SearchQuery"
|
||||
return
|
||||
}
|
||||
|
||||
$ranked = $nsResults.GetEnumerator() |
|
||||
Sort-Object { $_.Value.BestScore } -Descending |
|
||||
Select-Object -First $Max
|
||||
|
||||
foreach ($r in $ranked) {
|
||||
$ns = $r.Key
|
||||
$info = $r.Value
|
||||
Write-Output "[$($info.BestScore)] $ns"
|
||||
foreach ($fp in $info.FilePaths) {
|
||||
Write-Output " File: $fp"
|
||||
}
|
||||
# Show top 5 highest-scoring matching types in this namespace
|
||||
$info.Types | Sort-Object { $_.Score } -Descending |
|
||||
Select-Object -First 5 |
|
||||
ForEach-Object { Write-Output " $($_.Text)" }
|
||||
Write-Output ""
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Search scoring ──────────────────────────────────────────────────────────
|
||||
# Simple ranked scoring on type names. Higher = better.
|
||||
# 100 = exact name 80 = starts-with 60 = substring
|
||||
# 50 = PascalCase 40 = multi-keyword 20 = fuzzy subsequence
|
||||
|
||||
function Get-MatchScore {
|
||||
param([string]$Name, [string]$FullName, [string]$Query)
|
||||
|
||||
$q = $Query.Trim()
|
||||
if (-not $q) { return 0 }
|
||||
|
||||
if ($Name -eq $q) { return 100 }
|
||||
if ($Name -like "$q*") { return 80 }
|
||||
if ($Name -like "*$q*" -or $FullName -like "*$q*") { return 60 }
|
||||
|
||||
$initials = ($Name.ToCharArray() | Where-Object { [char]::IsUpper($_) }) -join ''
|
||||
if ($initials.Length -ge 2 -and $initials -like "*$q*") { return 50 }
|
||||
|
||||
$words = $q -split '\s+' | Where-Object { $_.Length -gt 0 }
|
||||
if ($words.Count -gt 1) {
|
||||
$allFound = $true
|
||||
foreach ($w in $words) {
|
||||
if ($Name -notlike "*$w*" -and $FullName -notlike "*$w*") {
|
||||
$allFound = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($allFound) { return 40 }
|
||||
}
|
||||
|
||||
if (Test-FuzzySubsequence -Text $Name -Pattern $q) { return 20 }
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function Test-FuzzySubsequence {
|
||||
param([string]$Text, [string]$Pattern)
|
||||
$ti = 0
|
||||
$tLower = $Text.ToLowerInvariant()
|
||||
$pLower = $Pattern.ToLowerInvariant()
|
||||
foreach ($ch in $pLower.ToCharArray()) {
|
||||
$idx = $tLower.IndexOf($ch, $ti)
|
||||
if ($idx -lt 0) { return $false }
|
||||
$ti = $idx + 1
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
# ─── Action: enums ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-EnumValues {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'enums' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 1) {
|
||||
Write-Error "-TypeName must be a fully-qualified type name including namespace, e.g. 'Namespace.TypeName'. Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
if ($type.kind -ne 'Enum') {
|
||||
Write-Error "$FullName is not an Enum (kind: $($type.kind))"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "Enum $($type.fullName)"
|
||||
if ($type.enumValues) {
|
||||
$type.enumValues | ForEach-Object { Write-Output " $_" }
|
||||
} else {
|
||||
Write-Output " (no values)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Dispatch ─────────────────────────────────────────────────────────────────
|
||||
|
||||
switch ($Action) {
|
||||
'projects' { Show-Projects }
|
||||
'packages' { Show-Packages }
|
||||
'stats' { Show-Stats }
|
||||
'namespaces' { Get-Namespaces -Prefix $Filter }
|
||||
'types' { Get-TypesInNamespace -Ns $Namespace }
|
||||
'members' { Get-MembersOfType -FullName $TypeName }
|
||||
'search' { Search-WinMd -SearchQuery $Query -Max $MaxResults }
|
||||
'enums' { Get-EnumValues -FullName $TypeName }
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate or refresh the WinMD cache for the Agent Skill.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds and runs the standalone cache generator to export cached JSON files
|
||||
from all WinMD metadata found in project NuGet packages and Windows SDK.
|
||||
|
||||
The cache is per-package+version: if two projects reference the same
|
||||
package at the same version, the WinMD data is parsed once and shared.
|
||||
|
||||
Supports single project or recursive scan of an entire repo.
|
||||
|
||||
.PARAMETER ProjectDir
|
||||
Path to a project directory (contains .csproj/.vcxproj), or a project file itself.
|
||||
Defaults to scanning the workspace root.
|
||||
|
||||
.PARAMETER Scan
|
||||
Recursively discover all .csproj/.vcxproj files under ProjectDir.
|
||||
|
||||
.PARAMETER OutputDir
|
||||
Path to the cache output directory. Defaults to "Generated Files\winmd-cache".
|
||||
|
||||
.EXAMPLE
|
||||
.\Update-WinMdCache.ps1
|
||||
.\Update-WinMdCache.ps1 -ProjectDir BlankWinUI
|
||||
.\Update-WinMdCache.ps1 -Scan -ProjectDir .
|
||||
.\Update-WinMdCache.ps1 -ProjectDir "src\MyApp\MyApp.csproj"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ProjectDir,
|
||||
[switch]$Scan,
|
||||
[string]$OutputDir = 'Generated Files\winmd-cache'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')).Path
|
||||
$generatorProj = Join-Path (Join-Path $PSScriptRoot 'cache-generator') 'CacheGenerator.csproj'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WinAppSDK version detection -- look only at the repo root folder (no recursion)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-WinAppSdkVersionFromDirectoryPackagesProps {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a Directory.Packages.props
|
||||
(Central Package Management) at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$propsFile = Join-Path $RepoRoot 'Directory.Packages.props'
|
||||
if (-not (Test-Path $propsFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $propsFile -Raw
|
||||
$node = $xml.SelectNodes('//PackageVersion') |
|
||||
Where-Object { $_.Include -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.Version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $propsFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-WinAppSdkVersionFromPackagesConfig {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a packages.config at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$configFile = Join-Path $RepoRoot 'packages.config'
|
||||
if (-not (Test-Path $configFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $configFile -Raw
|
||||
$node = $xml.SelectNodes('//package') |
|
||||
Where-Object { $_.id -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $configFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Try Directory.Packages.props first (CPM), then packages.config
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromDirectoryPackagesProps -RepoRoot $root
|
||||
if (-not $winAppSdkVersion) {
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromPackagesConfig -RepoRoot $root
|
||||
}
|
||||
if ($winAppSdkVersion) {
|
||||
Write-Host "Detected WinAppSDK version from repo: $winAppSdkVersion" -ForegroundColor Cyan
|
||||
} else {
|
||||
Write-Host "No WinAppSDK version found at repo root; will use latest (Version=*)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Default: if no ProjectDir, scan the workspace root
|
||||
if (-not $ProjectDir) {
|
||||
$ProjectDir = $root
|
||||
$Scan = $true
|
||||
}
|
||||
|
||||
Push-Location $root
|
||||
|
||||
try {
|
||||
# Detect installed .NET SDK -- require >= 8.0, prefer stable over preview
|
||||
$dotnetSdks = dotnet --list-sdks 2>$null
|
||||
$bestMajor = $dotnetSdks |
|
||||
Where-Object { $_ -notmatch 'preview|rc|alpha|beta' } |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
# Fall back to preview SDKs if no stable SDK found
|
||||
if (-not $bestMajor) {
|
||||
$bestMajor = $dotnetSdks |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
if (-not $bestMajor) {
|
||||
Write-Error "No .NET SDK >= 8.0 found. Install from https://dotnet.microsoft.com/download"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$targetFramework = "net$bestMajor.0"
|
||||
Write-Host "Using .NET SDK: $targetFramework" -ForegroundColor Cyan
|
||||
|
||||
# Build MSBuild properties -- pass detected WinAppSDK version when available
|
||||
$sdkVersionProp = ''
|
||||
if ($winAppSdkVersion) {
|
||||
$sdkVersionProp = "-p:WinAppSdkVersion=$winAppSdkVersion"
|
||||
}
|
||||
|
||||
Write-Host "Building cache generator..." -ForegroundColor Cyan
|
||||
$restoreArgs = @($generatorProj, "-p:TargetFramework=$targetFramework", '--nologo', '-v', 'q')
|
||||
if ($sdkVersionProp) { $restoreArgs += $sdkVersionProp }
|
||||
dotnet restore @restoreArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Restore failed"
|
||||
exit 1
|
||||
}
|
||||
$buildArgs = @($generatorProj, '-c', 'Release', '--nologo', '-v', 'q', "-p:TargetFramework=$targetFramework", '--no-restore')
|
||||
if ($sdkVersionProp) { $buildArgs += $sdkVersionProp }
|
||||
dotnet build @buildArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run the built executable directly (avoids dotnet run target framework mismatch issues)
|
||||
$generatorDir = Join-Path $PSScriptRoot 'cache-generator'
|
||||
$exePath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
# Fallback: try dll with dotnet
|
||||
$dllPath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.dll"
|
||||
if (Test-Path $dllPath) {
|
||||
$exePath = $null
|
||||
} else {
|
||||
Write-Error "Built executable not found at: $exePath"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$runArgs = @()
|
||||
if ($Scan) {
|
||||
$runArgs += '--scan'
|
||||
}
|
||||
|
||||
# Detect installed WinAppSDK runtime via Get-AppxPackage (the WindowsApps
|
||||
# folder is ACL-restricted so C# cannot enumerate it directly).
|
||||
# WinMD files are architecture-independent metadata, so pick whichever arch
|
||||
# matches the current OS to ensure the package is present.
|
||||
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
|
||||
$runtimePkg = Get-AppxPackage -Name 'Microsoft.WindowsAppRuntime.*' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notmatch 'CBS' -and $_.Architecture -eq $osArch } |
|
||||
Sort-Object -Property Version -Descending |
|
||||
Select-Object -First 1
|
||||
if ($runtimePkg -and $runtimePkg.InstallLocation -and (Test-Path $runtimePkg.InstallLocation)) {
|
||||
Write-Host "Detected WinAppSDK runtime: $($runtimePkg.Name) v$($runtimePkg.Version)" -ForegroundColor Cyan
|
||||
$runArgs += '--winappsdk-runtime'
|
||||
$runArgs += $runtimePkg.InstallLocation
|
||||
}
|
||||
|
||||
$runArgs += $ProjectDir
|
||||
$runArgs += $OutputDir
|
||||
|
||||
Write-Host "Exporting WinMD cache..." -ForegroundColor Cyan
|
||||
if ($exePath) {
|
||||
& $exePath @runArgs
|
||||
} else {
|
||||
dotnet $dllPath @runArgs
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Cache export failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Cache updated at: $OutputDir" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Default fallback; Update-WinMdCache.ps1 overrides via -p:TargetFramework=net{X}.0 -->
|
||||
<TargetFramework Condition="'$(TargetFramework)' == ''">net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<!-- System.Reflection.Metadata is inbox in net9.0+, only needed for net8.0 -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Baseline WinAppSDK packages: downloaded during restore so the cache generator
|
||||
can always index WinAppSDK APIs, even if the target project hasn't been restored.
|
||||
ExcludeAssets="all" means they're downloaded but don't affect this tool's build.
|
||||
|
||||
When the repo has a known version (passed via -p:WinAppSdkVersion=X.Y.Z from
|
||||
Update-WinMdCache.ps1), prefer that version to avoid unnecessary NuGet downloads.
|
||||
Falls back to Version="*" (latest) on fresh clones with no restore.
|
||||
-->
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' != ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="$(WinAppSdkVersion)" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' == ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="*" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build configuration -->
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build targets -->
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level Central Package Management -->
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
165
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
165
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
@@ -1,165 +0,0 @@
|
||||
---
|
||||
name: wpf-to-winui3-migration
|
||||
description: Guide for migrating PowerToys modules from WPF to WinUI 3 (Windows App SDK). Use when asked to migrate WPF code, convert WPF XAML to WinUI, replace System.Windows namespaces with Microsoft.UI.Xaml, update Dispatcher to DispatcherQueue, replace DynamicResource with ThemeResource, migrate imaging APIs from System.Windows.Media.Imaging to Windows.Graphics.Imaging, convert WPF Window to WinUI Window, migrate .resx to .resw resources, migrate custom Observable/RelayCommand to CommunityToolkit.Mvvm source generators, handle WPF-UI (Lepo) to WinUI native control migration, or fix installer/build pipeline issues after migration. Keywords: WPF, WinUI, WinUI3, migration, porting, convert, namespace, XAML, Dispatcher, DispatcherQueue, imaging, BitmapImage, Window, ContentDialog, ThemeResource, DynamicResource, ResourceLoader, resw, resx, CommunityToolkit, ObservableProperty, WPF-UI, SizeToContent, AppWindow, SoftwareBitmap.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# WPF to WinUI 3 Migration Skill
|
||||
|
||||
Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.UI.Xaml.*` / Windows App SDK). Based on patterns validated in the ImageResizer module migration.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Migrate a PowerToys module from WPF to WinUI 3
|
||||
- Convert WPF XAML files to WinUI 3 XAML
|
||||
- Replace `System.Windows` namespaces with `Microsoft.UI.Xaml`
|
||||
- Migrate `Dispatcher` usage to `DispatcherQueue`
|
||||
- Migrate custom `Observable`/`RelayCommand` to CommunityToolkit.Mvvm source generators
|
||||
- Replace WPF-UI (Lepo) controls with native WinUI 3 controls
|
||||
- Convert imaging code from `System.Windows.Media.Imaging` to `Windows.Graphics.Imaging`
|
||||
- Handle WPF `Window` vs WinUI `Window` differences (sizing, positioning, SizeToContent)
|
||||
- Migrate resource files from `.resx` to `.resw` with `ResourceLoader`
|
||||
- Fix installer/build pipeline issues after WinUI 3 migration
|
||||
- Update project files, NuGet packages, and signing config
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Visual Studio 2022 17.4+
|
||||
- Windows App SDK NuGet package (`Microsoft.WindowsAppSDK`)
|
||||
- .NET 8+ with `net8.0-windows10.0.19041.0` TFM
|
||||
- Windows 10 1803+ (April 2018 Update or newer)
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Recommended Order
|
||||
|
||||
1. **Project file** — Update TFM, NuGet packages, set `<UseWinUI>true</UseWinUI>`
|
||||
2. **Data models and business logic** — No UI dependencies, migrate first
|
||||
3. **MVVM framework** — Replace custom Observable/RelayCommand with CommunityToolkit.Mvvm
|
||||
4. **Resource strings** — Migrate `.resx` → `.resw`, introduce `ResourceLoaderInstance`
|
||||
5. **Services and utilities** — Replace `System.Windows` types, async-ify imaging code
|
||||
6. **ViewModels** — Update Dispatcher usage, binding patterns
|
||||
7. **Views/Pages** — Starting from leaf pages with fewest dependencies
|
||||
8. **Main page / shell** — Last, since it depends on everything
|
||||
9. **App.xaml / startup code** — Merge carefully (do NOT overwrite WinUI 3 boilerplate)
|
||||
10. **Installer & build pipeline** — Update WiX, signing, build events
|
||||
11. **Tests** — Adapt for WinUI 3 runtime, async patterns
|
||||
|
||||
### Key Principles
|
||||
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different application lifecycle boilerplate. Merge your resources and initialization code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. This causes phantom build artifacts.
|
||||
- **Use `Lazy<T>` for resource-dependent statics** — `ResourceLoader` is not available at class-load time in all contexts.
|
||||
|
||||
## Quick Reference Tables
|
||||
|
||||
### Namespace Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
|
||||
### Critical API Replacements
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`) |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` | Property vs method |
|
||||
| `Application.Current.Dispatcher` | Store `DispatcherQueue` in static field | See [Threading](./references/threading-and-windowing.md) |
|
||||
| `MessageBox.Show()` | `ContentDialog` | Must set `XamlRoot` |
|
||||
| `DynamicResource` | `ThemeResource` | Theme-reactive only |
|
||||
| `clr-namespace:` | `using:` | XAML namespace prefix |
|
||||
| `{x:Static props:Resources.Key}` | `x:Uid` or `ResourceLoader.GetString()` | .resx → .resw |
|
||||
| `DataType="{x:Type m:Foo}"` | Remove or use code-behind | `x:Type` not supported |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `SizeToContent="Height"` | Custom `SizeToContent()` via `AppWindow.Resize()` | See [Windowing](./references/threading-and-windowing.md) |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Mouse → Pointer events |
|
||||
| `Pack URI (pack://...)` | `ms-appx:///` | Resource URI scheme |
|
||||
| `Observable` (custom base) | `ObservableObject` + `[ObservableProperty]` | CommunityToolkit.Mvvm |
|
||||
| `RelayCommand` (custom) | `[RelayCommand]` source generator | CommunityToolkit.Mvvm |
|
||||
| `JpegBitmapEncoder` | `BitmapEncoder.CreateAsync(JpegEncoderId, stream)` | Async, unified API |
|
||||
| `encoder.QualityLevel = 85` | `BitmapPropertySet { "ImageQuality", 0.85f }` | int 1-100 → float 0-1 |
|
||||
|
||||
### NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` |
|
||||
| `WPF-UI` (Lepo) | Remove — use native WinUI 3 controls |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` |
|
||||
| (none) | `Microsoft.WindowsAppSDK` |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` |
|
||||
| (none) | `WinUIEx` (optional, for window helpers) |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` |
|
||||
|
||||
### XAML Syntax Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` |
|
||||
| `<ui:FluentWindow>` / `<ui:Button>` (WPF-UI) | Native `<Window>` / `<Button>` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / handle via KeyDown |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property |
|
||||
| `<behaviors:Interaction.Triggers>` | Migrate to code-behind or WinUI behaviors |
|
||||
|
||||
## Detailed Reference Docs
|
||||
|
||||
Read only the section relevant to your current task:
|
||||
|
||||
- [Namespace and API Mapping](./references/namespace-api-mapping.md) — Full type mapping, NuGet changes, project file, CsWinRT interop
|
||||
- [XAML Migration Guide](./references/xaml-migration.md) — XAML syntax, WPF-UI removal, markup extensions, styles, resources, data binding
|
||||
- [Threading and Window Management](./references/threading-and-windowing.md) — Dispatcher, DispatcherQueue, SizeToContent, AppWindow, HWND interop, custom entry point
|
||||
- [Imaging API Migration](./references/imaging-migration.md) — BitmapEncoder/Decoder, SoftwareBitmap, CodecHelper, async patterns, int→uint
|
||||
- [PowerToys-Specific Patterns](./references/powertoys-patterns.md) — MVVM migration, ResourceLoader, Lazy init, installer, signing, test adaptation, build pipeline
|
||||
|
||||
## Common Pitfalls (from ImageResizer migration)
|
||||
|
||||
| Pitfall | Solution |
|
||||
|---------|----------|
|
||||
| `ContentDialog` throws "does not have a XamlRoot" | Set `dialog.XamlRoot = this.Content.XamlRoot` before `ShowAsync()` |
|
||||
| `FilePicker` throws error in desktop app | Call `WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd)` |
|
||||
| `Window.Dispatcher` returns null | Use `Window.DispatcherQueue` instead |
|
||||
| Resources on `Window` element not found | Move resources to root layout container (`Grid.Resources`) |
|
||||
| `VisualStateManager` on `Window` fails | Use `UserControl` or `Page` inside the Window |
|
||||
| Satellite assembly installer errors (`WIX0103`) | Remove `.resources.dll` refs from `Resources.wxs`; WinUI 3 uses `.pri` |
|
||||
| Phantom `.exe`/`.deps.json` in root output dir | Avoid Exe→WinExe `ProjectReference`; use Library project |
|
||||
| `ResourceLoader` crash at static init | Wrap in `Lazy<T>` or null-coalescing property — see [Lazy Init](./references/powertoys-patterns.md#lazy-initialization-for-resource-dependent-statics) |
|
||||
| `SizeToContent` not available | Implement manual content measurement + `AppWindow.Resize()` with DPI scaling |
|
||||
| `x:Bind` default mode is `OneTime` | Explicitly set `Mode=OneWay` or `Mode=TwoWay` |
|
||||
| `DynamicResource` / `x:Static` not compiling | Replace with `ThemeResource` / `ResourceLoader` or `x:Uid` |
|
||||
| `IValueConverter.Convert` signature mismatch | Last param: `CultureInfo` → `string` (language tag) |
|
||||
| Test project can't resolve WPF types | Add `<UseWPF>true</UseWPF>` temporarily; remove after imaging migration |
|
||||
| Pixel dimension type mismatch (`int` vs `uint`) | WinRT uses `uint` for pixel sizes — add `u` suffix in test assertions |
|
||||
| `$(SolutionDir)` empty in standalone project build | Use `$(MSBuildThisFileDirectory)` with relative paths instead |
|
||||
| JPEG quality value wrong after migration | WPF: int 1-100; WinRT: float 0.0-1.0 |
|
||||
| MSIX packaging fails in PreBuildEvent | Move to PostBuildEvent; artifacts not ready at PreBuild time |
|
||||
| RC file icon path with forward slashes | Use double-backslash escaping: `..\\ui\\Assets\\icon.ico` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Build fails after namespace rename | Check for lingering `System.Windows` usings; some types have no direct equivalent |
|
||||
| Missing `PresentationCore.dll` at runtime | Ensure ALL imaging code uses `Windows.Graphics.Imaging`, not `System.Windows.Media.Imaging` |
|
||||
| `DataContext` not working on Window | WinUI 3 `Window` is not a `DependencyObject`; use a root `Page` or `UserControl` |
|
||||
| XAML designer not available | WinUI 3 does not support XAML Designer; use Hot Reload instead |
|
||||
| NuGet restore failures | Run `build-essentials.cmd` after adding `Microsoft.WindowsAppSDK` package |
|
||||
| `Parallel.ForEach` compilation error | Migrate to `Parallel.ForEachAsync` for async imaging operations |
|
||||
| Signing check fails on leaked artifacts | Run `generateAllFileComponents.ps1`; verify only `WinUI3Apps\\` paths in signing config |
|
||||
@@ -1,287 +0,0 @@
|
||||
# Imaging API Migration
|
||||
|
||||
Migrating from WPF (`System.Windows.Media.Imaging` / `PresentationCore.dll`) to WinRT (`Windows.Graphics.Imaging`). Based on the ImageResizer migration.
|
||||
|
||||
## Why This Migration Is Required
|
||||
|
||||
WinUI 3 apps deployed as self-contained do NOT include `PresentationCore.dll`. Any code using `System.Windows.Media.Imaging` will throw `FileNotFoundException` at runtime. ALL imaging code must use WinRT APIs.
|
||||
|
||||
| Purpose | Namespace |
|
||||
|---------|-----------|
|
||||
| UI display (`Image.Source`) | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| Image processing (encode/decode/transform) | `Windows.Graphics.Imaging` |
|
||||
|
||||
## Architecture Change: Pipeline vs Declarative
|
||||
|
||||
The fundamental architecture differs:
|
||||
|
||||
**WPF**: In-memory pipeline of bitmap objects. Decode → transform → encode synchronously.
|
||||
```csharp
|
||||
var decoder = BitmapDecoder.Create(stream, ...);
|
||||
var transform = new TransformedBitmap(decoder.Frames[0], new ScaleTransform(...));
|
||||
var encoder = new JpegBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(transform, ...));
|
||||
encoder.Save(outputStream);
|
||||
```
|
||||
|
||||
**WinRT**: Declarative transform model. Configure transforms on the encoder, which handles pixel manipulation internally. All async.
|
||||
```csharp
|
||||
var decoder = await BitmapDecoder.CreateAsync(winrtStream);
|
||||
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
|
||||
encoder.BitmapTransform.ScaledWidth = newWidth;
|
||||
encoder.BitmapTransform.ScaledHeight = newHeight;
|
||||
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
|
||||
await encoder.FlushAsync();
|
||||
```
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
### Decoders
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapDecoder.Create(stream, options, cache)` | `BitmapDecoder.CreateAsync(stream)` | Async, auto-detects format |
|
||||
| `JpegBitmapDecoder` / `PngBitmapDecoder` / etc. | `BitmapDecoder.CreateAsync(stream)` | Single unified decoder |
|
||||
| `decoder.Frames[0]` | `await decoder.GetFrameAsync(0)` | Async frame access |
|
||||
| `decoder.Frames.Count` | `decoder.FrameCount` (uint) | `int` → `uint` |
|
||||
| `decoder.CodecInfo.ContainerFormat` | `decoder.DecoderInformation.CodecId` | Different property path |
|
||||
| `decoder.Frames[0].PixelWidth` (int) | `decoder.PixelWidth` (uint) | `int` → `uint` |
|
||||
| `WmpBitmapDecoder` | Not available | WMP/HDP not supported |
|
||||
|
||||
### Encoders
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `new JpegBitmapEncoder()` | `BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream)` | Async factory |
|
||||
| `new PngBitmapEncoder()` | `BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream)` | No interlace control |
|
||||
| `encoder.Frames.Add(frame)` | `encoder.SetSoftwareBitmap(bitmap)` | Different API |
|
||||
| `encoder.Save(stream)` | `await encoder.FlushAsync()` | Async |
|
||||
|
||||
### Encoder Properties (Strongly-Typed → BitmapPropertySet)
|
||||
|
||||
WPF had type-specific encoder subclasses. WinRT uses a generic property set:
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
case JpegBitmapEncoder jpeg: jpeg.QualityLevel = 85; // int 1-100
|
||||
case PngBitmapEncoder png: png.Interlace = PngInterlaceOption.On;
|
||||
case TiffBitmapEncoder tiff: tiff.Compression = TiffCompressOption.Lzw;
|
||||
|
||||
// WinRT — JPEG quality (float 0.0-1.0)
|
||||
await encoder.BitmapProperties.SetPropertiesAsync(new BitmapPropertySet
|
||||
{
|
||||
{ "ImageQuality", new BitmapTypedValue(0.85f, PropertyType.Single) }
|
||||
});
|
||||
|
||||
// WinRT — TIFF compression (via BitmapPropertySet at creation time)
|
||||
var props = new BitmapPropertySet
|
||||
{
|
||||
{ "TiffCompressionMethod", new BitmapTypedValue((byte)2, PropertyType.UInt8) }
|
||||
};
|
||||
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.TiffEncoderId, stream, props);
|
||||
```
|
||||
|
||||
**JPEG quality scale change**: WPF int `1-100` → WinRT float `0.0-1.0`. Divide by 100.
|
||||
|
||||
### Bitmap Types
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapSource` | `SoftwareBitmap` | Central pixel-data type |
|
||||
| `BitmapImage` | `BitmapImage` (in `Microsoft.UI.Xaml.Media.Imaging`) | UI display only |
|
||||
| `FormatConvertedBitmap` | `SoftwareBitmap.Convert()` | |
|
||||
| `TransformedBitmap` + `ScaleTransform` | `BitmapTransform` via encoder | Declarative |
|
||||
| `CroppedBitmap` | `BitmapTransform.Bounds` | |
|
||||
|
||||
### Metadata
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapMetadata` | `BitmapProperties` | Different API surface |
|
||||
| `BitmapMetadata.Clone()` | No equivalent | Cannot selectively clone |
|
||||
| Selective metadata removal | Not supported | All-or-nothing only |
|
||||
|
||||
**Two encoder creation strategies for metadata:**
|
||||
- `CreateForTranscodingAsync()` — preserves ALL metadata from source
|
||||
- `CreateAsync()` — creates fresh encoder with NO metadata
|
||||
|
||||
This eliminated ~258 lines of manual metadata manipulation code (`BitmapMetadataExtension.cs`) in ImageResizer.
|
||||
|
||||
### Interpolation Modes
|
||||
|
||||
| WPF `BitmapScalingMode` | WinRT `BitmapInterpolationMode` |
|
||||
|------------------------|-------------------------------|
|
||||
| `HighQuality` / `Fant` | `Fant` |
|
||||
| `Linear` | `Linear` |
|
||||
| `NearestNeighbor` | `NearestNeighbor` |
|
||||
| `Unspecified` / `LowQuality` | `Linear` |
|
||||
|
||||
## Stream Interop
|
||||
|
||||
WinRT imaging requires `IRandomAccessStream` instead of `System.IO.Stream`:
|
||||
|
||||
```csharp
|
||||
using var stream = File.OpenRead(path);
|
||||
var winrtStream = stream.AsRandomAccessStream(); // Extension method
|
||||
var decoder = await BitmapDecoder.CreateAsync(winrtStream);
|
||||
```
|
||||
|
||||
**Critical**: For transcode, seek the input stream back to 0 before creating the encoder:
|
||||
```csharp
|
||||
winrtStream.Seek(0);
|
||||
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
|
||||
```
|
||||
|
||||
## CodecHelper Pattern (from ImageResizer)
|
||||
|
||||
WPF stored container format GUIDs in `settings.json`. WinRT uses different codec IDs. Create a `CodecHelper` to bridge them:
|
||||
|
||||
```csharp
|
||||
internal static class CodecHelper
|
||||
{
|
||||
// Maps WPF container format GUIDs (stored in settings JSON) to WinRT encoder IDs
|
||||
private static readonly Dictionary<Guid, Guid> LegacyGuidToEncoderId = new()
|
||||
{
|
||||
[new Guid("19e4a5aa-5662-4fc5-a0c0-1758028e1057")] = BitmapEncoder.JpegEncoderId,
|
||||
[new Guid("1b7cfaf4-713f-473c-bbcd-6137425faeaf")] = BitmapEncoder.PngEncoderId,
|
||||
[new Guid("0af1d87e-fcfe-4188-bdeb-a7906471cbe3")] = BitmapEncoder.BmpEncoderId,
|
||||
[new Guid("163bcc30-e2e9-4f0b-961d-a3e9fdb788a3")] = BitmapEncoder.TiffEncoderId,
|
||||
[new Guid("1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5")] = BitmapEncoder.GifEncoderId,
|
||||
};
|
||||
|
||||
// Maps decoder IDs to corresponding encoder IDs
|
||||
private static readonly Dictionary<Guid, Guid> DecoderIdToEncoderId = new()
|
||||
{
|
||||
[BitmapDecoder.JpegDecoderId] = BitmapEncoder.JpegEncoderId,
|
||||
[BitmapDecoder.PngDecoderId] = BitmapEncoder.PngEncoderId,
|
||||
// ...
|
||||
};
|
||||
|
||||
public static Guid GetEncoderIdFromLegacyGuid(Guid legacyGuid)
|
||||
=> LegacyGuidToEncoderId.GetValueOrDefault(legacyGuid, Guid.Empty);
|
||||
|
||||
public static Guid GetEncoderIdForDecoder(BitmapDecoder decoder)
|
||||
=> DecoderIdToEncoderId.GetValueOrDefault(decoder.DecoderInformation.CodecId, Guid.Empty);
|
||||
}
|
||||
```
|
||||
|
||||
This preserves backward compatibility with existing `settings.json` files that contain WPF-era GUIDs.
|
||||
|
||||
## ImagingEnums Pattern (from ImageResizer)
|
||||
|
||||
WPF-specific enums (`PngInterlaceOption`, `TiffCompressOption`) from `System.Windows.Media.Imaging` are used in settings JSON. Create custom enums with identical integer values for backward-compatible deserialization:
|
||||
|
||||
```csharp
|
||||
// Replace System.Windows.Media.Imaging.PngInterlaceOption
|
||||
public enum PngInterlaceOption { Default = 0, On = 1, Off = 2 }
|
||||
|
||||
// Replace System.Windows.Media.Imaging.TiffCompressOption
|
||||
public enum TiffCompressOption { Default = 0, None = 1, Ccitt3 = 2, Ccitt4 = 3, Lzw = 4, Rle = 5, Zip = 6 }
|
||||
```
|
||||
|
||||
## Async Migration Patterns
|
||||
|
||||
### Method Signatures
|
||||
|
||||
All imaging operations become async:
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| `void Execute(file, settings)` | `async Task ExecuteAsync(file, settings)` |
|
||||
| `IEnumerable<Error> Process()` | `async Task<IEnumerable<Error>> ProcessAsync()` |
|
||||
|
||||
### Parallel Processing
|
||||
|
||||
```csharp
|
||||
// WPF (synchronous)
|
||||
Parallel.ForEach(Files, new ParallelOptions { MaxDegreeOfParallelism = ... },
|
||||
(file, state, i) => { Execute(file, settings); });
|
||||
|
||||
// WinRT (async)
|
||||
await Parallel.ForEachAsync(Files, new ParallelOptions { MaxDegreeOfParallelism = ... },
|
||||
async (file, ct) => { await ExecuteAsync(file, settings); });
|
||||
```
|
||||
|
||||
### CLI Async Bridge
|
||||
|
||||
CLI entry points must bridge async to sync:
|
||||
```csharp
|
||||
return RunSilentModeAsync(cliOptions).GetAwaiter().GetResult();
|
||||
```
|
||||
|
||||
### Task.Factory.StartNew → Task.Run
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
_ = Task.Factory.StartNew(StartExecutingWork, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
|
||||
// WinUI 3
|
||||
_ = Task.Run(() => StartExecutingWorkAsync());
|
||||
```
|
||||
|
||||
## SoftwareBitmap as Interface Type
|
||||
|
||||
When modules expose imaging interfaces (e.g., AI super-resolution), change parameter/return types:
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
BitmapSource ApplySuperResolution(BitmapSource source, int scale, string filePath);
|
||||
|
||||
// WinRT
|
||||
SoftwareBitmap ApplySuperResolution(SoftwareBitmap source, int scale, string filePath);
|
||||
```
|
||||
|
||||
This eliminates manual `BitmapSource ↔ SoftwareBitmap` conversion code (unsafe `IMemoryBufferByteAccess` COM interop).
|
||||
|
||||
## MultiFrame Image Handling
|
||||
|
||||
```csharp
|
||||
// WinRT multi-frame encode (e.g., multi-page TIFF, animated GIF)
|
||||
for (uint i = 0; i < decoder.FrameCount; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
await encoder.GoToNextFrameAsync();
|
||||
|
||||
var frame = await decoder.GetFrameAsync(i);
|
||||
var bitmap = await frame.GetSoftwareBitmapAsync(
|
||||
frame.BitmapPixelFormat,
|
||||
BitmapAlphaMode.Premultiplied,
|
||||
transform,
|
||||
ExifOrientationMode.IgnoreExifOrientation,
|
||||
ColorManagementMode.DoNotColorManage);
|
||||
encoder.SetSoftwareBitmap(bitmap);
|
||||
}
|
||||
await encoder.FlushAsync();
|
||||
```
|
||||
|
||||
## int → uint for Pixel Dimensions
|
||||
|
||||
WinRT uses `uint` for all pixel dimensions. This affects:
|
||||
- `decoder.PixelWidth` / `decoder.PixelHeight` — `uint`
|
||||
- `BitmapTransform.ScaledWidth` / `ScaledHeight` — `uint`
|
||||
- `SoftwareBitmap` constructor — `uint` parameters
|
||||
- Test assertions: `Assert.AreEqual(96, ...)` → `Assert.AreEqual(96u, ...)`
|
||||
|
||||
## Display SoftwareBitmap in UI
|
||||
|
||||
```csharp
|
||||
var source = new SoftwareBitmapSource();
|
||||
// Must convert to Bgra8/Premultiplied for display
|
||||
if (bitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
|
||||
bitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
|
||||
{
|
||||
bitmap = SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
}
|
||||
await source.SetBitmapAsync(bitmap);
|
||||
myImage.Source = source;
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
| Feature | WPF | WinRT | Impact |
|
||||
|---------|-----|-------|--------|
|
||||
| PNG interlace | `PngBitmapEncoder.Interlace` | Not available | Always non-interlaced |
|
||||
| Metadata stripping | Selective via `BitmapMetadata.Clone()` | All-or-nothing | Orientation EXIF also removed |
|
||||
| Pixel formats | Many (`Pbgra32`, `Bgr24`, `Indexed8`, ...) | Primarily `Bgra8`, `Rgba8`, `Gray8/16` | Convert to `Bgra8` |
|
||||
| WMP/HDP format | `WmpBitmapDecoder` | Not available | Not supported |
|
||||
| Pixel differences | WPF scaler | `BitmapInterpolationMode.Fant` | Not bit-identical |
|
||||
@@ -1,226 +0,0 @@
|
||||
# Namespace and API Mapping Reference
|
||||
|
||||
Complete reference for mapping WPF types to WinUI 3 equivalents, based on the ImageResizer migration.
|
||||
|
||||
## Root Namespace Mapping
|
||||
|
||||
| WPF Namespace | WinUI 3 Namespace |
|
||||
|---------------|-------------------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
| WPF Type | WinUI 3 Type |
|
||||
|----------|-------------|
|
||||
| `System.Windows.Application` | `Microsoft.UI.Xaml.Application` |
|
||||
| `System.Windows.Window` | `Microsoft.UI.Xaml.Window` (NOT a DependencyObject) |
|
||||
| `System.Windows.DependencyObject` | `Microsoft.UI.Xaml.DependencyObject` |
|
||||
| `System.Windows.DependencyProperty` | `Microsoft.UI.Xaml.DependencyProperty` |
|
||||
| `System.Windows.FrameworkElement` | `Microsoft.UI.Xaml.FrameworkElement` |
|
||||
| `System.Windows.UIElement` | `Microsoft.UI.Xaml.UIElement` |
|
||||
| `System.Windows.Visibility` | `Microsoft.UI.Xaml.Visibility` |
|
||||
| `System.Windows.Thickness` | `Microsoft.UI.Xaml.Thickness` |
|
||||
| `System.Windows.CornerRadius` | `Microsoft.UI.Xaml.CornerRadius` |
|
||||
| `System.Windows.Media.Color` | `Windows.UI.Color` (note: `Windows.UI`, not `Microsoft.UI`) |
|
||||
| `System.Windows.Media.Colors` | `Microsoft.UI.Colors` |
|
||||
|
||||
## Controls Mapping
|
||||
|
||||
### Direct Mapping (namespace-only change)
|
||||
|
||||
These controls exist in both frameworks with the same name — change `System.Windows.Controls` to `Microsoft.UI.Xaml.Controls`:
|
||||
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
|
||||
### Controls With Different Names or Behavior
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `MessageBox` | `ContentDialog` | Must set `XamlRoot` before `ShowAsync()` |
|
||||
| `ContextMenu` | `MenuFlyout` | Different API surface |
|
||||
| `TabControl` | `TabView` | Different API |
|
||||
| `Menu` | `MenuBar` | Different API |
|
||||
| `StatusBar` | Custom `StackPanel` layout | No built-in equivalent |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### WPF-UI (Lepo) to Native WinUI 3
|
||||
|
||||
ImageResizer used the `WPF-UI` library (Lepo) for Fluent styling. These must be replaced with native WinUI 3 equivalents:
|
||||
|
||||
| WPF-UI (Lepo) | WinUI 3 Native | Notes |
|
||||
|----------------|---------------|-------|
|
||||
| `<ui:FluentWindow>` | `<Window>` | Native window + `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:Button>` | `<Button>` | Native button |
|
||||
| `<ui:NumberBox>` | `<NumberBox>` | Built into WinUI 3 |
|
||||
| `<ui:ProgressRing>` | `<ProgressRing>` | Built into WinUI 3 |
|
||||
| `<ui:SymbolIcon>` | `<SymbolIcon>` or `<FontIcon>` | Built into WinUI 3 |
|
||||
| `<ui:InfoBar>` | `<InfoBar>` | Built into WinUI 3 |
|
||||
| `<ui:TitleBar>` | Custom title bar via `SetTitleBar()` | Use `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:ThemesDictionary>` | `<XamlControlsResources>` | In merged dictionaries |
|
||||
| `<ui:ControlsDictionary>` | Remove | Not needed — WinUI 3 has its own control styles |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` | Named style keys |
|
||||
|
||||
## Input Event Mapping
|
||||
|
||||
| WPF Event | WinUI 3 Event | Notes |
|
||||
|-----------|--------------|-------|
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Check `IsLeftButtonPressed` on args |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Check pointer properties |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` with right button check |
|
||||
| `MouseMove` | `PointerMoved` | Uses `PointerRoutedEventArgs` |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Different event args |
|
||||
| `MouseEnter` | `PointerEntered` | |
|
||||
| `MouseLeave` | `PointerExited` | |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Different event args |
|
||||
| `KeyDown` | `KeyDown` | Same name, args type: `KeyRoutedEventArgs` |
|
||||
| `PreviewKeyDown` | No direct equivalent | Use `KeyDown` with handled pattern |
|
||||
|
||||
## IValueConverter Signature Change
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Convert(object value, Type targetType, object parameter, CultureInfo culture)` | `Convert(object value, Type targetType, object parameter, string language)` |
|
||||
| `ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)` | `ConvertBack(object value, Type targetType, object parameter, string language)` |
|
||||
|
||||
Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All converter classes must be updated.
|
||||
|
||||
## Types That Moved to Different Hierarchies
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `System.Windows.Threading.Dispatcher` | `Microsoft.UI.Dispatching.DispatcherQueue` | Completely different API |
|
||||
| `System.Windows.Threading.DispatcherPriority` | `Microsoft.UI.Dispatching.DispatcherQueuePriority` | Only 3 levels: High/Normal/Low |
|
||||
| `System.Windows.Interop.HwndSource` | `WinRT.Interop.WindowNative` | For HWND interop |
|
||||
| `System.Windows.Interop.WindowInteropHelper` | `WinRT.Interop.WindowNative.GetWindowHandle()` | |
|
||||
| `System.Windows.SystemColors` | Resource keys via `ThemeResource` | No direct static class |
|
||||
| `System.Windows.SystemParameters` | Win32 API or `DisplayInformation` | No direct equivalent |
|
||||
|
||||
## NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| Built into .NET (no NuGet needed) | `Microsoft.WindowsAppSDK` | Required |
|
||||
| `PresentationCore` / `PresentationFramework` | `Microsoft.WinUI` (transitive) | |
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` | |
|
||||
| `WPF-UI` (Lepo) | **Remove** — use native WinUI 3 controls | |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) | |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` | |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` | Required |
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Extensions` | Optional |
|
||||
| (none) | `Microsoft.Web.WebView2` | If using WebView |
|
||||
|
||||
## Project File Changes
|
||||
|
||||
### WPF .csproj
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
### WinUI 3 .csproj
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<SelfContained>true</SelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Assets\ImageResizer\ImageResizer.ico</ApplicationIcon>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
|
||||
<ProjectPriFileName>PowerToys.ModuleName.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
Key changes:
|
||||
- `UseWPF` → `UseWinUI`
|
||||
- TFM: `net8.0-windows` → `net8.0-windows10.0.19041.0`
|
||||
- Add `WindowsPackageType=None` for unpackaged desktop apps
|
||||
- Add `SelfContained=true` + `WindowsAppSDKSelfContained=true`
|
||||
- Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs` entry point
|
||||
- Set `ProjectPriFileName` to match your module's assembly name
|
||||
- Move icon from `Resources/` to `Assets/<Module>/`
|
||||
|
||||
### XAML ApplicationDefinition Setup
|
||||
|
||||
WinUI 3 requires explicit `ApplicationDefinition` declaration:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<Page Remove="ImageResizerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="ImageResizerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### CsWinRT Interop (for GPO and native references)
|
||||
|
||||
If the module references native C++ projects (like `GPOWrapper`):
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
Change `GPOWrapperProjection.csproj` reference to direct `GPOWrapper.vcxproj` reference.
|
||||
|
||||
### InternalsVisibleTo Migration
|
||||
|
||||
Move from code file to `.csproj`:
|
||||
|
||||
```csharp
|
||||
// DELETE: Properties/InternalsVisibleTo.cs
|
||||
// [assembly: InternalsVisibleTo("ImageResizer.Test")]
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- ADD to .csproj: -->
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ImageResizer.Test" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Items to Remove from .csproj
|
||||
|
||||
```xml
|
||||
<!-- DELETE: WPF resource embedding -->
|
||||
<EmbeddedResource Update="Properties\Resources.resx">...</EmbeddedResource>
|
||||
<Resource Include="Resources\ImageResizer.ico" />
|
||||
<Compile Update="Properties\Resources.Designer.cs">...</Compile>
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" /> <!-- from CLI project -->
|
||||
```
|
||||
@@ -1,516 +0,0 @@
|
||||
# PowerToys-Specific Migration Patterns
|
||||
|
||||
Patterns and conventions specific to the PowerToys codebase, based on the ImageResizer migration.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Before (WPF Module)
|
||||
|
||||
```
|
||||
src/modules/<module>/
|
||||
├── <Module>UI/
|
||||
│ ├── <Module>UI.csproj # OutputType=WinExe, UseWPF=true
|
||||
│ ├── App.xaml / App.xaml.cs
|
||||
│ ├── MainWindow.xaml / .cs
|
||||
│ ├── Views/
|
||||
│ ├── ViewModels/
|
||||
│ ├── Helpers/
|
||||
│ │ ├── Observable.cs # Custom INotifyPropertyChanged
|
||||
│ │ └── RelayCommand.cs # Custom ICommand
|
||||
│ ├── Properties/
|
||||
│ │ ├── Resources.resx # WPF resource strings
|
||||
│ │ ├── Resources.Designer.cs
|
||||
│ │ └── InternalsVisibleTo.cs
|
||||
│ └── Telemetry/
|
||||
├── <Module>CLI/
|
||||
│ └── <Module>CLI.csproj # OutputType=Exe
|
||||
└── tests/
|
||||
```
|
||||
|
||||
### After (WinUI 3 Module)
|
||||
|
||||
```
|
||||
src/modules/<module>/
|
||||
├── <Module>UI/
|
||||
│ ├── <Module>UI.csproj # OutputType=WinExe, UseWinUI=true
|
||||
│ ├── Program.cs # Custom entry point (DISABLE_XAML_GENERATED_MAIN)
|
||||
│ ├── app.manifest # Single manifest file
|
||||
│ ├── ImageResizerXAML/
|
||||
│ │ ├── App.xaml / App.xaml.cs # WinUI 3 App class
|
||||
│ │ ├── MainWindow.xaml / .cs
|
||||
│ │ └── Views/
|
||||
│ ├── Converters/ # WinUI 3 IValueConverter (string language)
|
||||
│ ├── ViewModels/
|
||||
│ ├── Helpers/
|
||||
│ │ └── ResourceLoaderInstance.cs # Static ResourceLoader accessor
|
||||
│ ├── Utilities/
|
||||
│ │ └── CodecHelper.cs # WPF→WinRT codec ID mapping (if imaging)
|
||||
│ ├── Models/
|
||||
│ │ └── ImagingEnums.cs # Custom enums replacing WPF imaging enums
|
||||
│ ├── Strings/
|
||||
│ │ └── en-us/
|
||||
│ │ └── Resources.resw # WinUI 3 resource strings
|
||||
│ └── Assets/
|
||||
│ └── <Module>/
|
||||
│ └── <Module>.ico # Moved from Resources/
|
||||
├── <Module>Common/ # NEW: shared library for CLI
|
||||
│ └── <Module>Common.csproj # OutputType=Library
|
||||
├── <Module>CLI/
|
||||
│ └── <Module>CLI.csproj # References Common, NOT UI
|
||||
└── tests/
|
||||
```
|
||||
|
||||
### Critical: CLI Dependency Pattern
|
||||
|
||||
**Do NOT** create `ProjectReference` from Exe to WinExe. This causes phantom build artifacts (`.exe`, `.deps.json`, `.runtimeconfig.json`) in the root output directory.
|
||||
|
||||
```
|
||||
WRONG: ImageResizerCLI (Exe) → ImageResizerUI (WinExe) ← phantom artifacts
|
||||
CORRECT: ImageResizerCLI (Exe) → ImageResizerCommon (Library)
|
||||
ImageResizerUI (WinExe) → ImageResizerCommon (Library)
|
||||
```
|
||||
|
||||
Follow the `FancyZonesCLI` → `FancyZonesEditorCommon` pattern.
|
||||
|
||||
### Files to Delete
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `Properties/Resources.resx` | Replaced by `Strings/en-us/Resources.resw` |
|
||||
| `Properties/Resources.Designer.cs` | Auto-generated; no longer needed |
|
||||
| `Properties/InternalsVisibleTo.cs` | Moved to `.csproj` `<InternalsVisibleTo>` |
|
||||
| `Helpers/Observable.cs` | Replaced by `CommunityToolkit.Mvvm.ObservableObject` |
|
||||
| `Helpers/RelayCommand.cs` | Replaced by `CommunityToolkit.Mvvm.Input` |
|
||||
| `Resources/*.ico` / `Resources/*.png` | Moved to `Assets/<Module>/` |
|
||||
| WPF `.dev.manifest` / `.prod.manifest` | Replaced by single `app.manifest` |
|
||||
| WPF-specific converters | Replaced by WinUI 3 converters with `string language` |
|
||||
|
||||
---
|
||||
|
||||
## MVVM Migration: Custom → CommunityToolkit.Mvvm Source Generators
|
||||
|
||||
### Observable Base Class → ObservableObject + [ObservableProperty]
|
||||
|
||||
**Before (custom Observable):**
|
||||
```csharp
|
||||
public class ResizeSize : Observable
|
||||
{
|
||||
private int _id;
|
||||
public int Id { get => _id; set => Set(ref _id, value); }
|
||||
|
||||
private ResizeFit _fit;
|
||||
public ResizeFit Fit
|
||||
{
|
||||
get => _fit;
|
||||
set
|
||||
{
|
||||
Set(ref _fit, value);
|
||||
UpdateShowHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _showHeight = true;
|
||||
public bool ShowHeight { get => _showHeight; set => Set(ref _showHeight, value); }
|
||||
private void UpdateShowHeight() { ShowHeight = Fit == ResizeFit.Stretch || Unit != ResizeUnit.Percent; }
|
||||
}
|
||||
```
|
||||
|
||||
**After (CommunityToolkit.Mvvm source generators):**
|
||||
```csharp
|
||||
public partial class ResizeSize : ObservableObject // MUST be partial
|
||||
{
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("Id")]
|
||||
private int _id;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowHeight))] // Replaces manual UpdateShowHeight()
|
||||
private ResizeFit _fit;
|
||||
|
||||
// Computed property — no backing field, no manual update method
|
||||
public bool ShowHeight => Fit == ResizeFit.Stretch || Unit != ResizeUnit.Percent;
|
||||
}
|
||||
```
|
||||
|
||||
Key changes:
|
||||
- Class must be `partial` for source generators
|
||||
- `Observable` → `ObservableObject` (from CommunityToolkit.Mvvm)
|
||||
- Manual `Set(ref _field, value)` → `[ObservableProperty]` attribute
|
||||
- `PropertyChanged` dependencies → `[NotifyPropertyChangedFor(nameof(...))]`
|
||||
- Computed properties with manual `UpdateXxx()` → direct expression body
|
||||
|
||||
### Custom Name Setter with Transform
|
||||
|
||||
For properties that transform the value before storing:
|
||||
|
||||
```csharp
|
||||
// Cannot use [ObservableProperty] because of value transformation
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, ReplaceTokens(value)); // SetProperty from ObservableObject
|
||||
}
|
||||
```
|
||||
|
||||
### RelayCommand → [RelayCommand] Source Generator
|
||||
|
||||
```csharp
|
||||
// DELETE: Helpers/RelayCommand.cs (custom ICommand)
|
||||
|
||||
// Before
|
||||
public ICommand ResizeCommand { get; } = new RelayCommand(Execute);
|
||||
|
||||
// After
|
||||
[RelayCommand]
|
||||
private void Resize() { /* ... */ }
|
||||
// Source generator creates ResizeCommand property automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resource String Migration (.resx → .resw)
|
||||
|
||||
### ResourceLoaderInstance Helper
|
||||
|
||||
```csharp
|
||||
internal static class ResourceLoaderInstance
|
||||
{
|
||||
internal static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("PowerToys.ImageResizer.pri");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Use the single-argument `ResourceLoader` constructor. The two-argument version (`ResourceLoader("file.pri", "path/Resources")`) may fail if the resource map path doesn't match the actual PRI structure.
|
||||
|
||||
### Usage
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
using ImageResizer.Properties;
|
||||
string text = Resources.MyStringKey;
|
||||
|
||||
// WinUI 3
|
||||
string text = ResourceLoaderInstance.ResourceLoader.GetString("MyStringKey");
|
||||
```
|
||||
|
||||
### Lazy Initialization for Resource-Dependent Statics
|
||||
|
||||
`ResourceLoader` is not available at class-load time in all contexts (CLI mode, test harness). Use lazy initialization:
|
||||
|
||||
**Before (crashes at class load):**
|
||||
```csharp
|
||||
private static readonly CompositeFormat _format =
|
||||
CompositeFormat.Parse(Resources.Error_Format);
|
||||
|
||||
private static readonly Dictionary<string, string> _tokens = new()
|
||||
{
|
||||
["$small$"] = Resources.Small,
|
||||
["$medium$"] = Resources.Medium,
|
||||
};
|
||||
```
|
||||
|
||||
**After (lazy, safe):**
|
||||
```csharp
|
||||
private static CompositeFormat _format;
|
||||
private static CompositeFormat Format => _format ??=
|
||||
CompositeFormat.Parse(ResourceLoaderInstance.ResourceLoader.GetString("Error_Format"));
|
||||
|
||||
private static readonly Lazy<Dictionary<string, string>> _tokens = new(() =>
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["$small$"] = ResourceLoaderInstance.ResourceLoader.GetString("Small"),
|
||||
["$medium$"] = ResourceLoaderInstance.ResourceLoader.GetString("Medium"),
|
||||
});
|
||||
// Usage: _tokens.Value.TryGetValue(...)
|
||||
```
|
||||
|
||||
### XAML: x:Static → x:Uid
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Button Content="{x:Static p:Resources.Cancel}" />
|
||||
<!-- WinUI 3 -->
|
||||
<Button x:Uid="Cancel" />
|
||||
```
|
||||
|
||||
In `.resw`, use property-suffixed keys: `Cancel.Content`, `Header.Text`, etc.
|
||||
|
||||
---
|
||||
|
||||
## CLI Options Migration
|
||||
|
||||
`System.CommandLine.Option<T>` constructor signature changed:
|
||||
|
||||
```csharp
|
||||
// WPF era — string[] aliases
|
||||
public DestinationOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Destination)
|
||||
|
||||
// WinUI 3 — single string name
|
||||
public DestinationOption()
|
||||
: base(_aliases[0], ResourceLoaderInstance.ResourceLoader.GetString("CLI_Option_Destination"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installer Updates
|
||||
|
||||
### WiX Changes
|
||||
|
||||
#### 1. Remove Satellite Assembly References
|
||||
|
||||
Remove from `installer/PowerToysSetupVNext/Resources.wxs`:
|
||||
- `<Component>` entries for `<Module>.resources.dll`
|
||||
- `<RemoveFolder>` entries for locale directories
|
||||
- Module from `WinUI3AppsInstallFolder` `ParentDirectory` loop
|
||||
|
||||
#### 2. Update File Component Generation
|
||||
|
||||
Run `generateAllFileComponents.ps1` after migration. For Exe→WinExe dependency issues, add cleanup logic:
|
||||
|
||||
```powershell
|
||||
# Strip phantom ImageResizer files from BaseApplications.wxs
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.exe', ''
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.deps\.json', ''
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.runtimeconfig\.json', ''
|
||||
```
|
||||
|
||||
#### 3. Output Directory
|
||||
|
||||
WinUI 3 modules output to `WinUI3Apps/`:
|
||||
```xml
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
```
|
||||
|
||||
### ESRP Signing
|
||||
|
||||
Update `.pipelines/ESRPSigning_core.json` — all module binaries must use `WinUI3Apps\\` paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"FileList": [
|
||||
"WinUI3Apps\\PowerToys.ImageResizer.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerExt.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerContextMenu.dll"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Pipeline Fixes
|
||||
|
||||
### $(SolutionDir) → $(MSBuildThisFileDirectory)
|
||||
|
||||
`$(SolutionDir)` is empty when building individual projects outside the solution. Replace with relative paths from the project file:
|
||||
|
||||
```xml
|
||||
<!-- Before (breaks on standalone project build) -->
|
||||
<Exec Command="powershell $(SolutionDir)tools\build\convert-resx-to-rc.ps1" />
|
||||
|
||||
<!-- After (works always) -->
|
||||
<Exec Command="powershell $(MSBuildThisFileDirectory)..\..\..\..\tools\build\convert-resx-to-rc.ps1" />
|
||||
```
|
||||
|
||||
### MSIX Packaging: PreBuild → PostBuild
|
||||
|
||||
MSIX packaging must happen AFTER the build (artifacts not ready at PreBuild):
|
||||
|
||||
```xml
|
||||
<!-- Before -->
|
||||
<PreBuildEvent>MakeAppx.exe pack /d . /p "$(OutDir)Package.msix" /o</PreBuildEvent>
|
||||
|
||||
<!-- After -->
|
||||
<PostBuildEvent>
|
||||
if exist "$(OutDir)Package.msix" del "$(OutDir)Package.msix"
|
||||
MakeAppx.exe pack /d "$(MSBuildThisFileDirectory)." /p "$(OutDir)Package.msix" /o
|
||||
</PostBuildEvent>
|
||||
```
|
||||
|
||||
### RC File Icon Path Escaping
|
||||
|
||||
Windows Resource Compiler requires double-backslash paths:
|
||||
|
||||
```c
|
||||
// Before (breaks)
|
||||
IDI_ICON1 ICON "..\\ui\Assets\ImageResizer\ImageResizer.ico"
|
||||
// After
|
||||
IDI_ICON1 ICON "..\\ui\\Assets\\ImageResizer\\ImageResizer.ico"
|
||||
```
|
||||
|
||||
### BOM/Encoding Normalization
|
||||
|
||||
Migration may strip UTF-8 BOM from C# files (`// Copyright` → `// Copyright`). This is cosmetic and safe, but be aware it will show as changes in diff.
|
||||
|
||||
---
|
||||
|
||||
## Test Adaptation
|
||||
|
||||
### Tests Requiring WPF Runtime
|
||||
|
||||
If tests still need WPF types (e.g., comparing old vs new output), temporarily add:
|
||||
```xml
|
||||
<UseWPF>true</UseWPF>
|
||||
```
|
||||
Remove this after fully migrating all test code to WinRT APIs.
|
||||
|
||||
### Tests Using ResourceLoader
|
||||
|
||||
Unit tests cannot easily initialize WinUI 3 `ResourceLoader`. Options:
|
||||
- Hardcode expected strings in tests: `"Value must be between '{0}' and '{1}'."`
|
||||
- Delete tests that only verify resource string lookup
|
||||
- Avoid creating `App` instances in test harness (WinUI App cannot be instantiated in tests)
|
||||
|
||||
### Async Test Methods
|
||||
|
||||
All imaging tests become async:
|
||||
```csharp
|
||||
// Before
|
||||
[TestMethod]
|
||||
public void ResizesImage() { ... }
|
||||
|
||||
// After
|
||||
[TestMethod]
|
||||
public async Task ResizesImageAsync() { ... }
|
||||
```
|
||||
|
||||
### uint Assertions
|
||||
|
||||
```csharp
|
||||
// Before
|
||||
Assert.AreEqual(96, image.Frames[0].PixelWidth);
|
||||
// After
|
||||
Assert.AreEqual(96u, decoder.PixelWidth);
|
||||
```
|
||||
|
||||
### Pixel Data Access in Tests
|
||||
|
||||
```csharp
|
||||
// Before (WPF)
|
||||
public static Color GetFirstPixel(this BitmapSource source)
|
||||
{
|
||||
var pixel = new byte[4];
|
||||
new FormatConvertedBitmap(
|
||||
new CroppedBitmap(source, new Int32Rect(0, 0, 1, 1)),
|
||||
PixelFormats.Bgra32, null, 0).CopyPixels(pixel, 4, 0);
|
||||
return Color.FromArgb(pixel[3], pixel[2], pixel[1], pixel[0]);
|
||||
}
|
||||
|
||||
// After (WinRT)
|
||||
public static async Task<(byte R, byte G, byte B, byte A)> GetFirstPixelAsync(
|
||||
this BitmapDecoder decoder)
|
||||
{
|
||||
using var bitmap = await decoder.GetSoftwareBitmapAsync(
|
||||
BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
var buffer = new Windows.Storage.Streams.Buffer(
|
||||
(uint)(bitmap.PixelWidth * bitmap.PixelHeight * 4));
|
||||
bitmap.CopyToBuffer(buffer);
|
||||
using var reader = DataReader.FromBuffer(buffer);
|
||||
byte b = reader.ReadByte(), g = reader.ReadByte(),
|
||||
r = reader.ReadByte(), a = reader.ReadByte();
|
||||
return (r, g, b, a);
|
||||
}
|
||||
```
|
||||
|
||||
### Metadata Assertions
|
||||
|
||||
```csharp
|
||||
// Before
|
||||
Assert.AreEqual("Test", ((BitmapMetadata)image.Frames[0].Metadata).Comment);
|
||||
|
||||
// After
|
||||
var props = await decoder.BitmapProperties.GetPropertiesAsync(
|
||||
new[] { "System.Photo.DateTaken" });
|
||||
Assert.IsTrue(props.ContainsKey("System.Photo.DateTaken"),
|
||||
"Metadata should be preserved during transcode");
|
||||
```
|
||||
|
||||
### AllowUnsafeBlocks for SoftwareBitmap Tests
|
||||
|
||||
If tests access pixel data via `IMemoryBufferByteAccess`, add:
|
||||
```xml
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings JSON Backward Compatibility
|
||||
|
||||
- Settings are stored in `%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\`
|
||||
- Schema must remain backward-compatible across upgrades
|
||||
- Add new fields with defaults; never remove or rename existing fields
|
||||
- Create custom enums matching WPF enum integer values for deserialization (e.g., `ImagingEnums.cs`)
|
||||
- See: `src/settings-ui/Settings.UI.Library/`
|
||||
|
||||
## IPC Contract
|
||||
|
||||
If the module communicates with the runner or settings UI:
|
||||
1. Update BOTH sides of the IPC contract
|
||||
2. Test settings changes are received by the module
|
||||
3. Test module state changes are reflected in settings UI
|
||||
4. Reference: `doc/devdocs/core/settings/runner-ipc.md`
|
||||
|
||||
---
|
||||
|
||||
## Checklist for PowerToys Module Migration
|
||||
|
||||
### Project & Dependencies
|
||||
- [ ] Update `.csproj`: `UseWPF` → `UseWinUI`, TFM → `net8.0-windows10.0.19041.0`
|
||||
- [ ] Add `WindowsPackageType=None`, `SelfContained=true`, `WindowsAppSDKSelfContained=true`
|
||||
- [ ] Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs`
|
||||
- [ ] Replace NuGet packages (WPF-UI → remove, add WindowsAppSDK, etc.)
|
||||
- [ ] Update project references (GPOWrapperProjection → GPOWrapper + CsWinRT)
|
||||
- [ ] Move `InternalsVisibleTo` from code to `.csproj`
|
||||
- [ ] Extract CLI shared logic to Library project (avoid Exe→WinExe dependency)
|
||||
|
||||
### MVVM & Resources
|
||||
- [ ] Replace custom `Observable`/`RelayCommand` with CommunityToolkit.Mvvm source generators
|
||||
- [ ] Migrate `.resx` → `.resw` (`Properties/Resources.resx` → `Strings/en-us/Resources.resw`)
|
||||
- [ ] Create `ResourceLoaderInstance` helper
|
||||
- [ ] Wrap resource-dependent statics in `Lazy<T>` or null-coalescing properties
|
||||
- [ ] Delete `Properties/Resources.Designer.cs`, `Observable.cs`, `RelayCommand.cs`
|
||||
|
||||
### XAML
|
||||
- [ ] Replace `clr-namespace:` → `using:` in all xmlns declarations
|
||||
- [ ] Remove WPF-UI (Lepo) xmlns and controls — use native WinUI 3
|
||||
- [ ] Replace `{x:Static p:Resources.Key}` → `x:Uid` with `.resw` keys
|
||||
- [ ] Replace `{DynamicResource}` → `{ThemeResource}`
|
||||
- [ ] Replace `DataType="{x:Type ...}"` → `x:DataType="..."`
|
||||
- [ ] Replace `<Style.Triggers>` → `VisualStateManager`
|
||||
- [ ] Add `<XamlControlsResources/>` to `App.xaml` merged dictionaries
|
||||
- [ ] Move `Window.Resources` to root container's `Resources`
|
||||
- [ ] Run XamlStyler: `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
|
||||
### Code-Behind & APIs
|
||||
- [ ] Replace all `System.Windows.*` namespaces with `Microsoft.UI.Xaml.*`
|
||||
- [ ] Replace `Dispatcher` with `DispatcherQueue`
|
||||
- [ ] Store `DispatcherQueue` reference explicitly (no `Application.Current.Dispatcher`)
|
||||
- [ ] Implement `SizeToContent()` via AppWindow if needed
|
||||
- [ ] Update `ContentDialog` calls to set `XamlRoot`
|
||||
- [ ] Update `FilePicker` calls with HWND initialization
|
||||
- [ ] Migrate imaging code to `Windows.Graphics.Imaging` (async, `SoftwareBitmap`)
|
||||
- [ ] Create `CodecHelper` for legacy GUID → WinRT codec ID mapping (if imaging)
|
||||
- [ ] Create custom imaging enums for JSON backward compatibility (if imaging)
|
||||
- [ ] Update all `IValueConverter` signatures (`CultureInfo` → `string`)
|
||||
|
||||
### Build & Installer
|
||||
- [ ] Update WiX installer: remove satellite assembly refs from `Resources.wxs`
|
||||
- [ ] Run `generateAllFileComponents.ps1`; handle phantom artifacts
|
||||
- [ ] Update ESRP signing paths to `WinUI3Apps\\`
|
||||
- [ ] Fix `$(SolutionDir)` → `$(MSBuildThisFileDirectory)` in build events
|
||||
- [ ] Move MSIX packaging from PreBuild to PostBuild
|
||||
- [ ] Fix RC file path escaping (double-backslash)
|
||||
- [ ] Verify output dir is `WinUI3Apps/`
|
||||
|
||||
### Testing & Validation
|
||||
- [ ] Update test project: async methods, `uint` assertions
|
||||
- [ ] Handle ResourceLoader unavailability in tests (hardcode strings or skip)
|
||||
- [ ] Build clean: `cd` to project folder, `tools/build/build.cmd`, exit code 0
|
||||
- [ ] Run tests for affected module
|
||||
- [ ] Verify settings JSON backward compatibility
|
||||
- [ ] Test IPC contracts (runner ↔ settings UI)
|
||||
@@ -1,314 +0,0 @@
|
||||
# Threading and Window Management Migration
|
||||
|
||||
Based on patterns from the ImageResizer migration.
|
||||
|
||||
## Dispatcher → DispatcherQueue
|
||||
|
||||
### API Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Dispatcher.Invoke(Action)` | `DispatcherQueue.TryEnqueue(Action)` |
|
||||
| `Dispatcher.BeginInvoke(Action)` | `DispatcherQueue.TryEnqueue(Action)` |
|
||||
| `Dispatcher.Invoke(DispatcherPriority, Action)` | `DispatcherQueue.TryEnqueue(DispatcherQueuePriority, Action)` |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` |
|
||||
| `Dispatcher.VerifyAccess()` | Check `DispatcherQueue.HasThreadAccess` (no exception-throwing method) |
|
||||
|
||||
### Priority Mapping
|
||||
|
||||
WinUI 3 has only 3 levels: `High`, `Normal`, `Low`.
|
||||
|
||||
| WPF `DispatcherPriority` | WinUI 3 `DispatcherQueuePriority` |
|
||||
|-------------------------|----------------------------------|
|
||||
| `Send` | `High` |
|
||||
| `Normal` / `Input` / `Loaded` / `Render` / `DataBind` | `Normal` |
|
||||
| `Background` / `ContextIdle` / `ApplicationIdle` / `SystemIdle` | `Low` |
|
||||
|
||||
### Pattern: Global DispatcherQueue Access (from ImageResizer)
|
||||
|
||||
WPF provided `Application.Current.Dispatcher` globally. WinUI 3 requires explicit storage:
|
||||
|
||||
```csharp
|
||||
// Store DispatcherQueue at app startup
|
||||
private static DispatcherQueue _uiDispatcherQueue;
|
||||
|
||||
public static void InitializeDispatcher()
|
||||
{
|
||||
_uiDispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
```
|
||||
|
||||
Usage with thread-check pattern (from `Settings.Reload()`):
|
||||
```csharp
|
||||
var currentDispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
if (currentDispatcher != null)
|
||||
{
|
||||
// Already on UI thread
|
||||
ReloadCore(jsonSettings);
|
||||
}
|
||||
else if (_uiDispatcherQueue != null)
|
||||
{
|
||||
// Dispatch to UI thread
|
||||
_uiDispatcherQueue.TryEnqueue(() => ReloadCore(jsonSettings));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback (e.g., CLI mode, no UI)
|
||||
ReloadCore(jsonSettings);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: DispatcherQueue in ViewModels (from ProgressViewModel)
|
||||
|
||||
```csharp
|
||||
public class ProgressViewModel
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
public ProgressViewModel()
|
||||
{
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
private void OnProgressChanged(double progress)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Progress = progress;
|
||||
// other UI updates...
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: Async Dispatch (await)
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
await this.Dispatcher.InvokeAsync(() => { /* UI work */ });
|
||||
|
||||
// WinUI 3 (using TaskCompletionSource)
|
||||
var tcs = new TaskCompletionSource();
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
try { /* UI work */ tcs.SetResult(); }
|
||||
catch (Exception ex) { tcs.SetException(ex); }
|
||||
});
|
||||
await tcs.Task;
|
||||
```
|
||||
|
||||
### C++/WinRT Threading
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `winrt::resume_foreground(CoreDispatcher)` | `wil::resume_foreground(DispatcherQueue)` |
|
||||
| `CoreDispatcher.RunAsync()` | `DispatcherQueue.TryEnqueue()` |
|
||||
|
||||
Add `Microsoft.Windows.ImplementationLibrary` NuGet for `wil::resume_foreground`.
|
||||
|
||||
---
|
||||
|
||||
## Window Management
|
||||
|
||||
### WPF Window vs WinUI 3 Window
|
||||
|
||||
| Feature | WPF `Window` | WinUI 3 `Window` |
|
||||
|---------|-------------|------------------|
|
||||
| Base class | `ContentControl` → `DependencyObject` | **NOT** a control, NOT a `DependencyObject` |
|
||||
| `Resources` property | Yes | No — use root container's `Resources` |
|
||||
| `DataContext` property | Yes | No — use root `Page`/`UserControl` |
|
||||
| `VisualStateManager` | Yes | No — use inside child controls |
|
||||
| `Load`/`Unload` events | Yes | No |
|
||||
| `SizeToContent` | Yes (`Height`/`Width`/`WidthAndHeight`) | No — must implement manually |
|
||||
| `WindowState` (min/max/normal) | Yes | No — use `AppWindow.Presenter` |
|
||||
| `WindowStyle` | Yes | No — use `AppWindow` title bar APIs |
|
||||
| `ResizeMode` | Yes | No — use `AppWindow.Presenter` |
|
||||
| `WindowStartupLocation` | Yes | No — calculate manually |
|
||||
| `Icon` | `Window.Icon` | `AppWindow.SetIcon()` |
|
||||
| `Title` | `Window.Title` | `AppWindow.Title` (or `Window.Title`) |
|
||||
| Size (Width/Height) | Yes | No — use `AppWindow.Resize()` |
|
||||
| Position (Left/Top) | Yes | No — use `AppWindow.Move()` |
|
||||
| `IsDefault`/`IsCancel` on buttons | Yes | No — handle Enter/Escape in code-behind |
|
||||
|
||||
### Getting AppWindow from Window
|
||||
|
||||
```csharp
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using WinRT.Interop;
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
```
|
||||
|
||||
### Pattern: SizeToContent Replacement (from ImageResizer)
|
||||
|
||||
WinUI 3 has no `SizeToContent`. ImageResizer implemented a manual equivalent:
|
||||
|
||||
```csharp
|
||||
private void SizeToContent()
|
||||
{
|
||||
if (Content is not FrameworkElement content)
|
||||
return;
|
||||
|
||||
// Measure desired content size
|
||||
content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
var desiredHeight = content.DesiredSize.Height + WindowChromeHeight + Padding;
|
||||
|
||||
// Account for DPI scaling
|
||||
var scaleFactor = Content.XamlRoot.RasterizationScale;
|
||||
var pixelHeight = (int)(desiredHeight * scaleFactor);
|
||||
var pixelWidth = (int)(WindowWidth * scaleFactor);
|
||||
|
||||
// Resize via AppWindow
|
||||
var hwnd = WindowNative.GetWindowHandle(this);
|
||||
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
var appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.Resize(new Windows.Graphics.SizeInt32(pixelWidth, pixelHeight));
|
||||
}
|
||||
```
|
||||
|
||||
**Key details:**
|
||||
- `WindowChromeHeight` ≈ 32px for the title bar
|
||||
- Must multiply by `RasterizationScale` for DPI-aware sizing
|
||||
- Call `SizeToContent()` after page navigation or content changes
|
||||
- Unsubscribe previous event handlers before subscribing new ones to avoid memory leaks
|
||||
|
||||
### Window Positioning (Center Screen)
|
||||
|
||||
```csharp
|
||||
var displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
||||
var centerX = (displayArea.WorkArea.Width - appWindow.Size.Width) / 2;
|
||||
var centerY = (displayArea.WorkArea.Height - appWindow.Size.Height) / 2;
|
||||
appWindow.Move(new Windows.Graphics.PointInt32(centerX, centerY));
|
||||
```
|
||||
|
||||
### Window State (Minimize/Maximize)
|
||||
|
||||
```csharp
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Maximize();
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Minimize();
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Restore();
|
||||
```
|
||||
|
||||
### Title Bar Customization
|
||||
|
||||
```csharp
|
||||
// Extend content into title bar
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(AppTitleBar); // AppTitleBar is a XAML element
|
||||
|
||||
// Or via AppWindow API
|
||||
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||
{
|
||||
var titleBar = appWindow.TitleBar;
|
||||
titleBar.ExtendsContentIntoTitleBar = true;
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
}
|
||||
```
|
||||
|
||||
### Tracking the Main Window
|
||||
|
||||
```csharp
|
||||
public partial class App : Application
|
||||
{
|
||||
public static Window MainWindow { get; private set; }
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
MainWindow = new MainWindow();
|
||||
MainWindow.Activate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ContentDialog Requires XamlRoot
|
||||
|
||||
```csharp
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = "Confirm",
|
||||
Content = "Are you sure?",
|
||||
PrimaryButtonText = "Yes",
|
||||
CloseButtonText = "No",
|
||||
XamlRoot = this.Content.XamlRoot // REQUIRED
|
||||
};
|
||||
var result = await dialog.ShowAsync();
|
||||
```
|
||||
|
||||
### File Pickers Require HWND
|
||||
|
||||
```csharp
|
||||
var picker = new FileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".jpg");
|
||||
|
||||
// REQUIRED for desktop apps
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
```
|
||||
|
||||
### Window Close Handling
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; this.Hide(); }
|
||||
|
||||
// WinUI 3
|
||||
this.AppWindow.Closing += (s, e) => { e.Cancel = true; this.AppWindow.Hide(); };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Entry Point (DISABLE_XAML_GENERATED_MAIN)
|
||||
|
||||
ImageResizer uses a custom `Program.cs` entry point instead of the WinUI 3 auto-generated `Main`. This is needed for:
|
||||
- CLI mode (process files without showing UI)
|
||||
- Custom initialization before the WinUI 3 App starts
|
||||
- Single-instance enforcement
|
||||
|
||||
### Setup
|
||||
|
||||
In `.csproj`:
|
||||
```xml
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
|
||||
```
|
||||
|
||||
Create `Program.cs`:
|
||||
```csharp
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// CLI mode — no UI
|
||||
return RunCli(args);
|
||||
}
|
||||
|
||||
// GUI mode
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
Application.Start((p) =>
|
||||
{
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WPF App Constructor Removal
|
||||
|
||||
WPF modules often created `new App()` to initialize the WPF `Application` and get `Application.Current.Dispatcher`. This is no longer needed — the WinUI 3 `Application.Start()` handles this.
|
||||
|
||||
```csharp
|
||||
// DELETE (WPF pattern):
|
||||
_imageResizerApp = new App();
|
||||
// REPLACE with: Store DispatcherQueue explicitly (see Global DispatcherQueue Access above)
|
||||
```
|
||||
@@ -1,365 +0,0 @@
|
||||
# XAML Migration Guide
|
||||
|
||||
Detailed reference for migrating XAML from WPF to WinUI 3, based on the ImageResizer migration.
|
||||
|
||||
## XML Namespace Declaration Changes
|
||||
|
||||
### Before (WPF)
|
||||
|
||||
```xml
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MyApp"
|
||||
xmlns:m="clr-namespace:ImageResizer.Models"
|
||||
xmlns:p="clr-namespace:ImageResizer.Properties"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
x:Class="MyApp.MainWindow">
|
||||
```
|
||||
|
||||
### After (WinUI 3)
|
||||
|
||||
```xml
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyApp"
|
||||
xmlns:m="using:ImageResizer.Models"
|
||||
xmlns:converters="using:ImageResizer.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Class="MyApp.MainWindow">
|
||||
```
|
||||
|
||||
### Key Changes
|
||||
|
||||
| WPF Syntax | WinUI 3 Syntax | Notes |
|
||||
|------------|---------------|-------|
|
||||
| `clr-namespace:Foo` | `using:Foo` | CLR namespace mapping |
|
||||
| `clr-namespace:Foo;assembly=Bar` | `using:Foo` | Assembly qualification not needed |
|
||||
| `xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"` | **Remove entirely** | WPF-UI namespace no longer needed |
|
||||
| `xmlns:p="clr-namespace:...Properties"` | **Remove** | No more `.resx` string bindings |
|
||||
| `sys:String` (from mscorlib) | `x:String` | XAML intrinsic types |
|
||||
| `sys:Int32` | `x:Int32` | XAML intrinsic types |
|
||||
| `sys:Boolean` | `x:Boolean` | XAML intrinsic types |
|
||||
| `sys:Double` | `x:Double` | XAML intrinsic types |
|
||||
|
||||
## Unsupported Markup Extensions
|
||||
|
||||
| WPF Markup Extension | WinUI 3 Alternative |
|
||||
|----------------------|---------------------|
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` (theme-reactive) or `{StaticResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` to a static property, or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported; use code-behind |
|
||||
| `{x:Array}` | Not supported; create collections in code-behind |
|
||||
| `{x:Code}` | Not supported |
|
||||
|
||||
### DynamicResource → ThemeResource
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<TextBlock Foreground="{DynamicResource MyBrush}" />
|
||||
|
||||
<!-- WinUI 3 -->
|
||||
<TextBlock Foreground="{ThemeResource MyBrush}" />
|
||||
```
|
||||
|
||||
`ThemeResource` automatically updates when the app theme changes (Light/Dark/HighContrast). For truly dynamic non-theme resources, set values in code-behind or use data binding.
|
||||
|
||||
### x:Static Resource Strings → x:Uid
|
||||
|
||||
This is the most pervasive XAML change. WPF used `{x:Static}` to bind to strongly-typed `.resx` resource strings. WinUI 3 uses `x:Uid` with `.resw` files.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Button Content="{x:Static p:Resources.Cancel}" />
|
||||
<TextBlock Text="{x:Static p:Resources.Input_Header}" />
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Button x:Uid="Cancel" />
|
||||
<TextBlock x:Uid="Input_Header" />
|
||||
```
|
||||
|
||||
In `Strings/en-us/Resources.resw`:
|
||||
```xml
|
||||
<data name="Cancel.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Input_Header.Text" xml:space="preserve">
|
||||
<value>Select a size</value>
|
||||
</data>
|
||||
```
|
||||
|
||||
The `x:Uid` suffix (`.Content`, `.Text`, `.Header`, `.PlaceholderText`, etc.) matches the target property name.
|
||||
|
||||
### DataType with x:Type → Remove
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<DataTemplate DataType="{x:Type m:ResizeSize}">
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<DataTemplate x:DataType="m:ResizeSize">
|
||||
```
|
||||
|
||||
## WPF-UI (Lepo) Controls Removal
|
||||
|
||||
If the module uses the `WPF-UI` library, replace all Lepo controls with native WinUI 3 equivalents.
|
||||
|
||||
### Window
|
||||
|
||||
```xml
|
||||
<!-- WPF (WPF-UI) -->
|
||||
<ui:FluentWindow
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<ui:TitleBar Title="Image Resizer" />
|
||||
...
|
||||
</ui:FluentWindow>
|
||||
|
||||
<!-- WinUI 3 (native) -->
|
||||
<Window>
|
||||
<!-- Title bar managed via code-behind: this.ExtendsContentIntoTitleBar = true; -->
|
||||
...
|
||||
</Window>
|
||||
```
|
||||
|
||||
### App.xaml Resources
|
||||
|
||||
```xml
|
||||
<!-- WPF (WPF-UI) -->
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemesDictionary Theme="Dark" />
|
||||
<ui:ControlsDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<!-- WinUI 3 (native) -->
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### Common Control Replacements
|
||||
|
||||
```xml
|
||||
<!-- WPF-UI NumberBox -->
|
||||
<ui:NumberBox Value="{Binding Width}" />
|
||||
<!-- WinUI 3 -->
|
||||
<NumberBox Value="{x:Bind ViewModel.Width, Mode=TwoWay}" />
|
||||
|
||||
<!-- WPF-UI InfoBar -->
|
||||
<ui:InfoBar Title="Warning" Message="..." IsOpen="True" Severity="Warning" />
|
||||
<!-- WinUI 3 -->
|
||||
<InfoBar Title="Warning" Message="..." IsOpen="True" Severity="Warning" />
|
||||
|
||||
<!-- WPF-UI ProgressRing -->
|
||||
<ui:ProgressRing IsIndeterminate="True" />
|
||||
<!-- WinUI 3 -->
|
||||
<ProgressRing IsActive="True" />
|
||||
|
||||
<!-- WPF-UI SymbolIcon -->
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
<!-- WinUI 3 -->
|
||||
<SymbolIcon Symbol="Add" />
|
||||
```
|
||||
|
||||
### Button Patterns
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Button IsDefault="True" Content="OK" />
|
||||
<Button IsCancel="True" Content="Cancel" />
|
||||
|
||||
<!-- WinUI 3 (no IsDefault/IsCancel) -->
|
||||
<Button Style="{StaticResource AccentButtonStyle}" Content="OK" />
|
||||
<Button Content="Cancel" />
|
||||
<!-- Handle Enter/Escape keys in code-behind if needed -->
|
||||
```
|
||||
|
||||
## Style and Template Changes
|
||||
|
||||
### Triggers → VisualStateManager
|
||||
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Style TargetType="Button">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="LightBlue"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="LightBlue"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
```
|
||||
|
||||
### No Binding in Setter.Value
|
||||
|
||||
```xml
|
||||
<!-- WPF (works) -->
|
||||
<Setter Property="Foreground" Value="{Binding TextColor}"/>
|
||||
|
||||
<!-- WinUI 3 (does NOT work — use StaticResource) -->
|
||||
<Setter Property="Foreground" Value="{StaticResource TextColorBrush}"/>
|
||||
```
|
||||
|
||||
### Visual State Name Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `MouseOver` | `PointerOver` |
|
||||
| `Disabled` | `Disabled` |
|
||||
| `Pressed` | `Pressed` |
|
||||
|
||||
## Resource Dictionary Changes
|
||||
|
||||
### Window.Resources → Grid.Resources
|
||||
|
||||
WinUI 3 `Window` is NOT a `DependencyObject` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Window>
|
||||
<Window.Resources>
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Window.Resources>
|
||||
<Grid>...</Grid>
|
||||
</Window>
|
||||
|
||||
<!-- WinUI 3 -->
|
||||
<Window>
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Grid.Resources>
|
||||
...
|
||||
</Grid>
|
||||
</Window>
|
||||
```
|
||||
|
||||
### Theme Dictionaries
|
||||
|
||||
```xml
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="#FF000000"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="#FFFFFFFF"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="{ThemeResource SystemColorWindowTextColor}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
```
|
||||
|
||||
## URI Scheme Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `pack://application:,,,/MyAssembly;component/image.png` | `ms-appx:///Assets/image.png` |
|
||||
| `pack://application:,,,/image.png` | `ms-appx:///image.png` |
|
||||
| Relative path `../image.png` | `ms-appx:///image.png` |
|
||||
|
||||
Assets directory convention: `Resources/` → `Assets/<Module>/`
|
||||
|
||||
## Data Binding Changes
|
||||
|
||||
### {Binding} vs {x:Bind}
|
||||
|
||||
Both are available. Prefer `{x:Bind}` for compile-time safety and performance.
|
||||
|
||||
| Feature | `{Binding}` | `{x:Bind}` |
|
||||
|---------|------------|------------|
|
||||
| Default mode | `OneWay` | **`OneTime`** (explicit `Mode=OneWay` required!) |
|
||||
| Context | `DataContext` | Code-behind class |
|
||||
| Resolution | Runtime | Compile-time |
|
||||
| Performance | Reflection-based | Compiled |
|
||||
| Function binding | No | Yes |
|
||||
|
||||
### WPF-Specific Binding Features to Remove
|
||||
|
||||
```xml
|
||||
<!-- These WPF-only features must be removed or rewritten -->
|
||||
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<!-- WinUI 3: UpdateSourceTrigger not needed; TextBox uses PropertyChanged by default -->
|
||||
<TextBox Text="{x:Bind ViewModel.Value, Mode=TwoWay}" />
|
||||
|
||||
{Binding RelativeSource={RelativeSource Self}, ...}
|
||||
<!-- WinUI 3: Use x:Bind which binds to the page itself, or use ElementName -->
|
||||
|
||||
<ItemsControl ItemsSource="{Binding}" />
|
||||
<!-- WinUI 3: Must specify explicit path -->
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Items}" />
|
||||
```
|
||||
|
||||
## WPF-Only Window Properties to Remove
|
||||
|
||||
These properties exist on WPF `Window` but not WinUI 3:
|
||||
|
||||
```xml
|
||||
<!-- Remove from XAML — handle in code-behind via AppWindow API -->
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
ExtendsContentIntoTitleBar="True" <!-- Set in code-behind -->
|
||||
```
|
||||
|
||||
## XAML Control Property Changes
|
||||
|
||||
| WPF Property | WinUI 3 Property | Notes |
|
||||
|-------------|-----------------|-------|
|
||||
| `Focusable` | `IsTabStop` | Different name |
|
||||
| `SnapsToDevicePixels` | Not available | WinUI handles pixel snapping internally |
|
||||
| `UseLayoutRounding` | `UseLayoutRounding` | Same |
|
||||
| `IsHitTestVisible` | `IsHitTestVisible` | Same |
|
||||
| `TextBox.VerticalScrollBarVisibility` | `ScrollViewer.VerticalScrollBarVisibility` (attached) | Attached property |
|
||||
|
||||
## XAML Formatting (XamlStyler)
|
||||
|
||||
After migration, run XamlStyler to normalize formatting:
|
||||
- Alphabetize xmlns declarations and element attributes
|
||||
- Add UTF-8 BOM to all XAML files
|
||||
- Normalize comment spacing: `<!-- text -->` → `<!-- text -->`
|
||||
|
||||
PowerToys command: `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -360,8 +360,3 @@ src/common/Telemetry/*.etl
|
||||
# PowerToysInstaller Build Temp Files
|
||||
installer/*/*.wxs.bk
|
||||
/src/modules/awake/.claude
|
||||
|
||||
# Squad / Copilot agents — local-only, not committed
|
||||
.squad/
|
||||
.squad-workstream
|
||||
.github/agents/
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"StylesReportTool\\PowerToys.StylesReportTool.exe",
|
||||
|
||||
"CalculatorEngineCommon.dll",
|
||||
"PowerToys.Common.UI.Controls.dll",
|
||||
"PowerToys.ManagedTelemetry.dll",
|
||||
"PowerToys.ManagedCommon.dll",
|
||||
"PowerToys.ManagedCsWin32.dll",
|
||||
@@ -106,13 +105,7 @@
|
||||
"PowerToys.SvgThumbnailProvider.dll",
|
||||
"PowerToys.SvgThumbnailProvider.exe",
|
||||
"PowerToys.SvgThumbnailProviderCpp.dll",
|
||||
"PowerToys.KeyboardManager.dll",
|
||||
|
||||
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
|
||||
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe",
|
||||
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.dll",
|
||||
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
|
||||
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
|
||||
"WinUI3Apps\\PowerToys.HostsModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.HostsUILib.dll",
|
||||
"WinUI3Apps\\PowerToys.Hosts.dll",
|
||||
@@ -217,6 +210,9 @@
|
||||
"PowerToys.PowerAccentModuleInterface.dll",
|
||||
"PowerToys.PowerAccentKeyboardService.dll",
|
||||
|
||||
"PowerToys.PowerDisplayModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.PowerDisplay.dll",
|
||||
"WinUI3Apps\\PowerToys.PowerDisplay.exe",
|
||||
"PowerDisplay.Lib.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.PowerRenameExt.dll",
|
||||
|
||||
@@ -13,36 +13,9 @@ Param(
|
||||
|
||||
# Root folder Path for processing
|
||||
[Parameter(Mandatory=$False,Position=4)]
|
||||
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json",
|
||||
|
||||
# Use Azure Pipeline artifact as source for metapackage
|
||||
[Parameter(Mandatory=$False,Position=5)]
|
||||
[boolean]$useArtifactSource = $False,
|
||||
|
||||
# Azure DevOps organization URL
|
||||
[Parameter(Mandatory=$False,Position=6)]
|
||||
[string]$azureDevOpsOrg = "https://dev.azure.com/microsoft",
|
||||
|
||||
# Azure DevOps project name
|
||||
[Parameter(Mandatory=$False,Position=7)]
|
||||
[string]$azureDevOpsProject = "ProjectReunion",
|
||||
|
||||
# Pipeline build ID (or "latest" for latest build)
|
||||
[Parameter(Mandatory=$False,Position=8)]
|
||||
[string]$buildId = "",
|
||||
|
||||
# Artifact name containing the NuGet packages
|
||||
[Parameter(Mandatory=$False,Position=9)]
|
||||
[string]$artifactName = "WindowsAppSDK_Nuget_And_MSIX",
|
||||
|
||||
# Metapackage name to look for in artifact
|
||||
[Parameter(Mandatory=$False,Position=10)]
|
||||
[string]$metaPackageName = "Microsoft.WindowsAppSDK"
|
||||
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
|
||||
)
|
||||
|
||||
# Script-level constants
|
||||
$script:PackageVersionRegex = '^(.+?)\.(\d+\..*)$'
|
||||
|
||||
|
||||
|
||||
function Read-FileWithEncoding {
|
||||
@@ -84,7 +57,7 @@ function Add-NuGetSourceAndMapping {
|
||||
|
||||
# Ensure packageSources exists
|
||||
if (-not $Xml.configuration.packageSources) {
|
||||
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSources"))
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
|
||||
}
|
||||
$sources = $Xml.configuration.packageSources
|
||||
|
||||
@@ -93,13 +66,13 @@ function Add-NuGetSourceAndMapping {
|
||||
if (-not $sourceNode) {
|
||||
$sourceNode = $Xml.CreateElement("add")
|
||||
$sourceNode.SetAttribute("key", $Key)
|
||||
$null = $sources.AppendChild($sourceNode)
|
||||
$sources.AppendChild($sourceNode) | Out-Null
|
||||
}
|
||||
$sourceNode.SetAttribute("value", $Value)
|
||||
|
||||
# Ensure packageSourceMapping exists
|
||||
if (-not $Xml.configuration.packageSourceMapping) {
|
||||
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping"))
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
|
||||
}
|
||||
$mapping = $Xml.configuration.packageSourceMapping
|
||||
|
||||
@@ -107,7 +80,7 @@ function Add-NuGetSourceAndMapping {
|
||||
$invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
|
||||
if ($invalidNodes) {
|
||||
foreach ($node in $invalidNodes) {
|
||||
$null = $mapping.RemoveChild($node)
|
||||
$mapping.RemoveChild($node) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +91,9 @@ function Add-NuGetSourceAndMapping {
|
||||
$mappingSource.SetAttribute("key", $Key)
|
||||
# Insert at top for priority
|
||||
if ($mapping.HasChildNodes) {
|
||||
$null = $mapping.InsertBefore($mappingSource, $mapping.FirstChild)
|
||||
$mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
|
||||
} else {
|
||||
$null = $mapping.AppendChild($mappingSource)
|
||||
$mapping.AppendChild($mappingSource) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,273 +110,14 @@ function Add-NuGetSourceAndMapping {
|
||||
foreach ($pattern in $Patterns) {
|
||||
$pkg = $Xml.CreateElement("package")
|
||||
$pkg.SetAttribute("pattern", $pattern)
|
||||
$null = $mappingSource.AppendChild($pkg)
|
||||
$mappingSource.AppendChild($pkg) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Download-ArtifactFromPipeline {
|
||||
param (
|
||||
[string]$Organization,
|
||||
[string]$Project,
|
||||
[string]$BuildId,
|
||||
[string]$ArtifactName,
|
||||
[string]$OutputDir
|
||||
)
|
||||
|
||||
Write-Host "Downloading artifact '$ArtifactName' from build $BuildId..."
|
||||
$null = New-Item -ItemType Directory -Path $OutputDir -Force
|
||||
|
||||
try {
|
||||
# Authenticate with Azure DevOps using System Access Token (if available)
|
||||
if ($env:SYSTEM_ACCESSTOKEN) {
|
||||
Write-Host "Authenticating with Azure DevOps using System Access Token..."
|
||||
$env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN
|
||||
} else {
|
||||
Write-Host "No SYSTEM_ACCESSTOKEN found, assuming az CLI is already authenticated..."
|
||||
}
|
||||
|
||||
# Use az CLI to download artifact
|
||||
& az pipelines runs artifact download `
|
||||
--organization $Organization `
|
||||
--project $Project `
|
||||
--run-id $BuildId `
|
||||
--artifact-name $ArtifactName `
|
||||
--path $OutputDir
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Successfully downloaded artifact to $OutputDir"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Failed to download artifact. Exit code: $LASTEXITCODE"
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error downloading artifact: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NuspecDependencies {
|
||||
param (
|
||||
[string]$NupkgPath,
|
||||
[string]$TargetFramework = ""
|
||||
)
|
||||
|
||||
$tempDir = Join-Path $env:TEMP "nuspec_parse_$(Get-Random)"
|
||||
|
||||
try {
|
||||
# Extract .nupkg (it's a zip file)
|
||||
# Workaround: Expand-Archive may not recognize .nupkg extension, so copy to .zip first
|
||||
$tempZip = Join-Path $env:TEMP "temp_$(Get-Random).zip"
|
||||
Copy-Item $NupkgPath -Destination $tempZip -Force
|
||||
Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force
|
||||
Remove-Item $tempZip -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Find .nuspec file
|
||||
$nuspecFile = Get-ChildItem -Path $tempDir -Filter "*.nuspec" -Recurse | Select-Object -First 1
|
||||
|
||||
if (-not $nuspecFile) {
|
||||
Write-Warning "No .nuspec file found in $NupkgPath"
|
||||
return @{}
|
||||
}
|
||||
|
||||
[xml]$nuspec = Get-Content $nuspecFile.FullName
|
||||
|
||||
# Extract package info
|
||||
$packageId = $nuspec.package.metadata.id
|
||||
$version = $nuspec.package.metadata.version
|
||||
Write-Host "Parsing $packageId version $version"
|
||||
|
||||
# Parse dependencies
|
||||
$dependencies = @{}
|
||||
$depGroups = $nuspec.package.metadata.dependencies.group
|
||||
|
||||
if ($depGroups) {
|
||||
# Dependencies are grouped by target framework
|
||||
foreach ($group in $depGroups) {
|
||||
$fx = $group.targetFramework
|
||||
Write-Host " Target Framework: $fx"
|
||||
|
||||
foreach ($dep in $group.dependency) {
|
||||
$depId = $dep.id
|
||||
$depVer = $dep.version
|
||||
# Remove version range brackets if present (e.g., "[2.0.0]" -> "2.0.0")
|
||||
$depVer = $depVer -replace '[\[\]]', ''
|
||||
$dependencies[$depId] = $depVer
|
||||
Write-Host " - ${depId} : ${depVer}"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No grouping, direct dependencies
|
||||
$deps = $nuspec.package.metadata.dependencies.dependency
|
||||
if ($deps) {
|
||||
foreach ($dep in $deps) {
|
||||
$depId = $dep.id
|
||||
$depVer = $dep.version
|
||||
$depVer = $depVer -replace '[\[\]]', ''
|
||||
$dependencies[$depId] = $depVer
|
||||
Write-Host " - ${depId} : ${depVer}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to parse nuspec: $_"
|
||||
return @{}
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-ArtifactBasedDependencies {
|
||||
param (
|
||||
[string]$ArtifactDir,
|
||||
[string]$MetaPackageName,
|
||||
[string]$SourceUrl,
|
||||
[string]$OutputDir
|
||||
)
|
||||
|
||||
Write-Host "Resolving dependencies from artifact-based metapackage..."
|
||||
$null = New-Item -ItemType Directory -Path $OutputDir -Force
|
||||
|
||||
# Find the metapackage in artifact
|
||||
$metaNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.*.nupkg" |
|
||||
Where-Object { $_.Name -notmatch "Runtime" } |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $metaNupkg) {
|
||||
Write-Warning "Metapackage $MetaPackageName not found in artifact"
|
||||
return @{}
|
||||
}
|
||||
|
||||
# Extract version from filename
|
||||
if ($metaNupkg.Name -match "$MetaPackageName\.(.+)\.nupkg") {
|
||||
$metaVersion = $Matches[1]
|
||||
Write-Host "Found metapackage: $MetaPackageName version $metaVersion"
|
||||
} else {
|
||||
Write-Warning "Could not extract version from $($metaNupkg.Name)"
|
||||
return @{}
|
||||
}
|
||||
|
||||
# Parse dependencies from metapackage
|
||||
$dependencies = Get-NuspecDependencies -NupkgPath $metaNupkg.FullName
|
||||
|
||||
# Copy metapackage to output directory
|
||||
Copy-Item $metaNupkg.FullName -Destination $OutputDir -Force
|
||||
Write-Host "Copied metapackage to $OutputDir"
|
||||
|
||||
# Prepare package versions hashtable - initialize with metapackage version
|
||||
$packageVersions = @{ $MetaPackageName = $metaVersion }
|
||||
|
||||
# Copy Runtime package from artifact (it's not in feed) and extract its version
|
||||
$runtimeNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.Runtime.*.nupkg" | Select-Object -First 1
|
||||
if ($runtimeNupkg) {
|
||||
Copy-Item $runtimeNupkg.FullName -Destination $OutputDir -Force
|
||||
Write-Host "Copied Runtime package to $OutputDir"
|
||||
|
||||
# Extract version from Runtime package filename
|
||||
if ($runtimeNupkg.Name -match "$MetaPackageName\.Runtime\.(.+)\.nupkg") {
|
||||
$runtimeVersion = $Matches[1]
|
||||
$packageVersions["$MetaPackageName.Runtime"] = $runtimeVersion
|
||||
Write-Host "Extracted Runtime package version: $runtimeVersion"
|
||||
} else {
|
||||
Write-Warning "Could not extract version from Runtime package: $($runtimeNupkg.Name)"
|
||||
}
|
||||
}
|
||||
|
||||
# Download other dependencies from feed (excluding Runtime as it's already copied)
|
||||
# Create temp nuget.config that includes both local packages and remote feed
|
||||
# This allows NuGet to find packages already copied from artifact
|
||||
$tempConfig = Join-Path $env:TEMP "nuget_artifact_$(Get-Random).config"
|
||||
$tempConfigContent = @"
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key='LocalPackages' value='$OutputDir' />
|
||||
<add key='RemoteFeed' value='$SourceUrl' />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
"@
|
||||
Set-Content -Path $tempConfig -Value $tempConfigContent
|
||||
|
||||
try {
|
||||
foreach ($depId in $dependencies.Keys) {
|
||||
# Skip Runtime as it's already copied from artifact
|
||||
if ($depId -like "*Runtime*") {
|
||||
# Don't overwrite the version we extracted from the Runtime package filename
|
||||
if (-not $packageVersions.ContainsKey($depId)) {
|
||||
$packageVersions[$depId] = $dependencies[$depId]
|
||||
}
|
||||
Write-Host "Skipping $depId (already in artifact)"
|
||||
continue
|
||||
}
|
||||
|
||||
$depVersion = $dependencies[$depId]
|
||||
Write-Host "Downloading dependency: $depId version $depVersion from feed..."
|
||||
|
||||
& nuget install $depId `
|
||||
-Version $depVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $OutputDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$packageVersions[$depId] = $depVersion
|
||||
Write-Host " Successfully downloaded $depId"
|
||||
} else {
|
||||
Write-Warning " Failed to download $depId version $depVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Parse all downloaded packages to get actual versions
|
||||
$directories = Get-ChildItem -Path $OutputDir -Directory
|
||||
$allLocalPackages = @()
|
||||
|
||||
# Add metapackage and runtime to the list (they are .nupkg files, not directories)
|
||||
$allLocalPackages += $MetaPackageName
|
||||
if ($packageVersions.ContainsKey("$MetaPackageName.Runtime")) {
|
||||
$allLocalPackages += "$MetaPackageName.Runtime"
|
||||
}
|
||||
|
||||
foreach ($dir in $directories) {
|
||||
if ($dir.Name -match $script:PackageVersionRegex) {
|
||||
$pkgId = $Matches[1]
|
||||
$pkgVer = $Matches[2]
|
||||
$allLocalPackages += $pkgId
|
||||
if (-not $packageVersions.ContainsKey($pkgId)) {
|
||||
$packageVersions[$pkgId] = $pkgVer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Update nuget.config dynamically during pipeline execution
|
||||
# This modification is temporary and won't be committed back to the repo
|
||||
$nugetConfig = Join-Path $rootPath "nuget.config"
|
||||
$configData = Read-FileWithEncoding -Path $nugetConfig
|
||||
[xml]$xml = $configData.Content
|
||||
|
||||
Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $OutputDir -Patterns $allLocalPackages
|
||||
|
||||
$xml.Save($nugetConfig)
|
||||
Write-Host "Updated nuget.config with localpackages mapping (temporary, for pipeline execution only)."
|
||||
|
||||
return ,$packageVersions
|
||||
}
|
||||
|
||||
function Resolve-WinAppSdkSplitDependencies {
|
||||
Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
|
||||
$installDir = Join-Path $rootPath "localpackages\output"
|
||||
$null = New-Item -ItemType Directory -Path $installDir -Force
|
||||
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
||||
|
||||
# Create a temporary nuget.config to avoid interference from the repo's config
|
||||
$tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
|
||||
@@ -417,24 +131,14 @@ function Resolve-WinAppSdkSplitDependencies {
|
||||
if ($propsContent -match '<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="([^"]+)"') {
|
||||
$buildToolsVersion = $Matches[1]
|
||||
Write-Host "Downloading Microsoft.Windows.SDK.BuildTools version $buildToolsVersion..."
|
||||
& nuget install Microsoft.Windows.SDK.BuildTools `
|
||||
-Version $buildToolsVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $installDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
$nugetArgsBuildTools = "install Microsoft.Windows.SDK.BuildTools -Version $buildToolsVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgsBuildTools" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Download package to inspect nuspec and keep it for the build
|
||||
& nuget install Microsoft.WindowsAppSDK `
|
||||
-Version $WinAppSDKVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $installDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
$nugetArgs = "install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgs" | Out-Null
|
||||
|
||||
# Parse dependencies from the installed folders
|
||||
# Folder structure is typically {PackageId}.{Version}
|
||||
@@ -468,101 +172,52 @@ function Resolve-WinAppSdkSplitDependencies {
|
||||
}
|
||||
}
|
||||
|
||||
# Main logic: choose between artifact-based or feed-based approach
|
||||
if ($useArtifactSource) {
|
||||
Write-Host "=== Using Artifact-Based Source ===" -ForegroundColor Cyan
|
||||
Write-Host "Organization: $azureDevOpsOrg"
|
||||
Write-Host "Project: $azureDevOpsProject"
|
||||
Write-Host "Build ID: $buildId"
|
||||
Write-Host "Artifact: $artifactName"
|
||||
|
||||
if ([string]::IsNullOrEmpty($buildId) -or $buildId -eq 'N/A') {
|
||||
Write-Error "buildId parameter is required when using artifact source. Please provide a valid Windows App SDK Build ID."
|
||||
Write-Host "Tip: You can find the build ID from the Windows App SDK pipeline run in Azure DevOps."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Download artifact
|
||||
$artifactDir = Join-Path $rootPath "localpackages\artifact"
|
||||
$downloadSuccess = Download-ArtifactFromPipeline `
|
||||
-Organization $azureDevOpsOrg `
|
||||
-Project $azureDevOpsProject `
|
||||
-BuildId $buildId `
|
||||
-ArtifactName $artifactName `
|
||||
-OutputDir $artifactDir
|
||||
|
||||
if (-not $downloadSuccess) {
|
||||
Write-Host "Failed to download artifact"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies from artifact
|
||||
$installDir = Join-Path $rootPath "localpackages\output"
|
||||
$packageVersions = Resolve-ArtifactBasedDependencies `
|
||||
-ArtifactDir $artifactDir `
|
||||
-MetaPackageName $metaPackageName `
|
||||
-SourceUrl $sourceLink `
|
||||
-OutputDir $installDir
|
||||
|
||||
if ($packageVersions.Count -eq 0) {
|
||||
Write-Error "Failed to resolve dependencies from artifact"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$WinAppSDKVersion = $packageVersions[$metaPackageName]
|
||||
Write-Host "WinAppSDK Version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
|
||||
# Execute nuget list and capture the output
|
||||
if ($useExperimentalVersion) {
|
||||
# The nuget list for experimental versions will cost more time
|
||||
# So, we will not use -AllVersions to wast time
|
||||
# But it can only get the latest experimental version
|
||||
Write-Host "Fetching WindowsAppSDK with experimental versions"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-Prerelease
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions
|
||||
} else {
|
||||
Write-Host "=== Using Feed-Based Source ===" -ForegroundColor Cyan
|
||||
|
||||
# Execute nuget list and capture the output
|
||||
if ($useExperimentalVersion) {
|
||||
# The nuget list for experimental versions will cost more time
|
||||
# So, we will not use -AllVersions to wast time
|
||||
# But it can only get the latest experimental version
|
||||
Write-Host "Fetching WindowsAppSDK with experimental versions"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-Prerelease
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions
|
||||
} else {
|
||||
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-AllVersions
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
|
||||
}
|
||||
|
||||
Write-Host "Latest versions found: $latestVersions"
|
||||
# Extract the latest version number from the output
|
||||
$latestVersion = $latestVersions -split "`n" | `
|
||||
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
|
||||
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
|
||||
Sort-Object -Descending | `
|
||||
Select-Object -First 1
|
||||
|
||||
if ($latestVersion) {
|
||||
$WinAppSDKVersion = $latestVersion
|
||||
Write-Host "Extracted version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
} else {
|
||||
Write-Host "Failed to extract version number from nuget list output"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies for 1.8+
|
||||
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
|
||||
|
||||
Resolve-WinAppSdkSplitDependencies
|
||||
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-AllVersions
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
|
||||
}
|
||||
|
||||
Write-Host "Latest versions found: $latestVersions"
|
||||
# Extract the latest version number from the output
|
||||
$latestVersion = $latestVersions -split "`n" | `
|
||||
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
|
||||
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
|
||||
Sort-Object -Descending | `
|
||||
Select-Object -First 1
|
||||
|
||||
if ($latestVersion) {
|
||||
$WinAppSDKVersion = $latestVersion
|
||||
Write-Host "Extracted version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
} else {
|
||||
Write-Host "Failed to extract version number from nuget list output"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies for 1.8+
|
||||
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
|
||||
|
||||
Resolve-WinAppSdkSplitDependencies
|
||||
|
||||
# Update Directory.Packages.props file
|
||||
Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
@@ -571,16 +226,9 @@ Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Obje
|
||||
|
||||
foreach ($pkgId in $packageVersions.Keys) {
|
||||
$ver = $packageVersions[$pkgId]
|
||||
|
||||
# Skip packages with empty versions to prevent corruption
|
||||
if ([string]::IsNullOrWhiteSpace($ver)) {
|
||||
Write-Warning "Skipping ${pkgId}: version is empty"
|
||||
continue
|
||||
}
|
||||
|
||||
# Escape dots in package ID for regex
|
||||
$pkgIdRegex = $pkgId -replace '\.', '\.'
|
||||
|
||||
|
||||
$newVersionString = "<PackageVersion Include=""$pkgId"" Version=""$ver"" />"
|
||||
$oldVersionString = "<PackageVersion Include=""$pkgIdRegex"" Version=""[-.0-9a-zA-Z]*"" />"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ schedules:
|
||||
always: false # only run if there's code changes!
|
||||
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
vmImage: windows-2019
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# NOTE: When using artifact mode (useArtifactSource: true), the pipeline needs
|
||||
# permission to access System.AccessToken. This is automatically handled by the
|
||||
# script if SYSTEM_ACCESSTOKEN environment variable is available.
|
||||
# If you encounter authentication errors, ensure the job has oauth access enabled.
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
@@ -42,23 +37,6 @@ parameters:
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters (optional)
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
displayName: "Use Artifact Source (instead of feed)"
|
||||
default: false
|
||||
- name: buildId
|
||||
type: string
|
||||
displayName: "Windows App SDK Build ID (required only if using artifact source)"
|
||||
default: 'N/A'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
displayName: "Source Project (for artifact mode, default: ProjectReunion)"
|
||||
default: 'ProjectReunion'
|
||||
- name: artifactName
|
||||
type: string
|
||||
displayName: "Artifact Name (for artifact mode, default: WindowsAppSDK_Nuget_And_MSIX)"
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
|
||||
extends:
|
||||
template: templates/pipeline-ci-build.yml
|
||||
@@ -71,7 +49,3 @@ extends:
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
|
||||
@@ -74,25 +74,6 @@ parameters:
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
- name: csProjectsToPublish
|
||||
type: object
|
||||
default:
|
||||
@@ -210,9 +191,6 @@ jobs:
|
||||
& '.pipelines/applyXamlStyling.ps1' -Passive
|
||||
displayName: Verify XAML formatting
|
||||
|
||||
- task: NuGetAuthenticate@1
|
||||
displayName: Authenticate NuGet feeds for verification
|
||||
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
|
||||
displayName: Verify Nuget package versions for PowerToys.slnx
|
||||
@@ -248,12 +226,6 @@ jobs:
|
||||
parameters:
|
||||
versionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
metaPackageName: ${{ parameters.metaPackageName }}
|
||||
|
||||
- ${{ if eq(parameters.useLatestWinAppSDK, false)}}:
|
||||
- template: .\steps-restore-nuget.yml
|
||||
|
||||
@@ -108,6 +108,9 @@ jobs:
|
||||
sdk: true
|
||||
version: '9.0'
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
displayName: Ensure VSTest Platform
|
||||
|
||||
- pwsh: |-
|
||||
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
|
||||
displayName: Download and install WinAppDriver
|
||||
@@ -149,7 +152,46 @@ jobs:
|
||||
inputs:
|
||||
displaySettings: 'optimal'
|
||||
|
||||
- script: |
|
||||
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZones.UITests\FancyZones.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
|
||||
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZonesEditor.UITests\FancyZonesEditor.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
|
||||
displayName: "Run UI Tests"
|
||||
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
|
||||
- task: VSTest@3
|
||||
displayName: Run UI Tests
|
||||
inputs:
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
testSelector: 'testAssemblies'
|
||||
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
|
||||
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
|
||||
testAssemblyVer2: |
|
||||
**\*UITest*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
|
||||
- ${{ each module in parameters.uiTestModules }}:
|
||||
- task: VSTest@3
|
||||
displayName: Run UI Test - ${{ module }}
|
||||
inputs:
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
testSelector: 'testAssemblies'
|
||||
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
|
||||
testAssemblyVer2: |
|
||||
**\*${{ module }}*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
@@ -34,25 +34,6 @@ parameters:
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
|
||||
stages:
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
@@ -84,12 +65,6 @@ stages:
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
metaPackageName: ${{ parameters.metaPackageName }}
|
||||
timeoutInMinutes: 90
|
||||
|
||||
- stage: Build_SDK
|
||||
|
||||
@@ -5,25 +5,6 @@ parameters:
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
|
||||
steps:
|
||||
- task: NuGetAuthenticate@1
|
||||
@@ -31,20 +12,12 @@ steps:
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: Update WinAppSDK Versions
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
filePath: '$(build.sourcesdirectory)\.pipelines\UpdateVersions.ps1'
|
||||
arguments: >
|
||||
-winAppSdkVersionNumber ${{ parameters.versionNumber }}
|
||||
-useExperimentalVersion $${{ parameters.useExperimentalVersion }}
|
||||
-rootPath "$(build.sourcesdirectory)"
|
||||
-useArtifactSource $${{ parameters.useArtifactSource }}
|
||||
-azureDevOpsOrg "${{ parameters.azureDevOpsOrg }}"
|
||||
-azureDevOpsProject "${{ parameters.azureDevOpsProject }}"
|
||||
-buildId "${{ parameters.buildId }}"
|
||||
-artifactName "${{ parameters.artifactName }}"
|
||||
-metaPackageName "${{ parameters.metaPackageName }}"
|
||||
|
||||
# - task: NuGetCommand@2
|
||||
# displayName: 'Restore NuGet packages (slnx)'
|
||||
@@ -63,4 +36,3 @@ steps:
|
||||
feedsToUse: 'config'
|
||||
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
|
||||
workingDirectory: '$(build.sourcesdirectory)'
|
||||
arguments: '/p:NoWarn=NU1602,NU1604'
|
||||
|
||||
@@ -93,8 +93,7 @@ if ($noticeMatch.Success) {
|
||||
# Test-only packages that are allowed to be in NOTICE.md but not in the build
|
||||
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
|
||||
$allowedExtraPackages = @(
|
||||
"- Moq",
|
||||
"- MSTest"
|
||||
"- Moq"
|
||||
)
|
||||
|
||||
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
|
||||
@@ -17,10 +17,10 @@ $nonDirectoryAssetsItems = Get-ChildItem $targetAssetsDir -Attributes !Directory
|
||||
$directoryAssetsItems = Get-ChildItem $targetAssetsDir -Attributes Directory
|
||||
|
||||
if ($directoryAssetsItems.Count -le 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: No directories detected in " $nonDirectoryAssetsItems ". Are you sure this is the right path?`r`n"
|
||||
Write-Host -ForegroundColor Red "No directories detected in " $nonDirectoryAssetsItems ". Are you sure this is the right path?`r`n"
|
||||
$totalFailures++;
|
||||
} elseif ($nonDirectoryAssetsItems.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected " $nonDirectoryAssetsItems " files in " $targetAssetsDir ". Each application should use a named subdirectory for assets.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected " $nonDirectoryAssetsItems " files in " $targetAssetsDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "Only directories detected in " $targetAssetsDir "`r`n"
|
||||
@@ -29,7 +29,7 @@ if ($directoryAssetsItems.Count -le 0) {
|
||||
# Make sure there's no resources.pri file. Each application should use a different name for their own resources file path.
|
||||
$resourcesPriFiles = Get-ChildItem $targetDir -Filter resources.pri
|
||||
if ($resourcesPriFiles.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected a resources.pri file in " $targetDir ". Each application should use a unique name for its resources file.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected a resources.pri file in " $targetDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "No resources.pri file detected in " $targetDir "`r`n"
|
||||
@@ -38,7 +38,7 @@ if ($resourcesPriFiles.Count -gt 0) {
|
||||
# Each application should have their XAML files in their own paths to avoid these conflicts.
|
||||
$resourcesPriFiles = Get-ChildItem $targetDir -Filter *.xbf
|
||||
if ($resourcesPriFiles.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected a .xbf file in " $targetDir ". Ensure all XAML files are placed in a subdirectory in each application.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected a .xbf file in " $targetDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "No .xbf files detected in " $targetDir "`r`n"
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
<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 >:{ -->
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)src\Version.props" />
|
||||
<Import Project="src\Version.props" />
|
||||
<PropertyGroup>
|
||||
<Copyright>Copyright (C) Microsoft Corporation. All rights reserved.</Copyright>
|
||||
<AssemblyCopyright>Copyright (C) Microsoft Corporation. All rights reserved.</AssemblyCopyright>
|
||||
@@ -20,24 +17,6 @@
|
||||
<NuGetAuditMode>direct</NuGetAuditMode>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
|
||||
|
||||
<!-- Enable Microsoft.Testing.Platform -->
|
||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<TestingPlatformDotNetTestSupport>true</TestingPlatformDotNetTestSupport>
|
||||
<TestingPlatformCommandLineArguments>$(TestingPlatformCommandLineArguments) --report-trx</TestingPlatformCommandLineArguments>
|
||||
<!-- No arm64 agents to run the tests. -->
|
||||
<TestingPlatformDisableCustomTestTarget Condition="'$(Platform)' == 'ARM64'">true</TestingPlatformDisableCustomTestTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
UI tests are run in dedicated UI test jobs/pipelines.
|
||||
In CI, the main build uses `/t:Build;Test` across the full solution, so
|
||||
prevent UI test projects from being executed in that pass.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(TF_BUILD)' != '' and $(MSBuildProjectName.Contains('UITest'))">
|
||||
<TestingPlatformDisableCustomTestTarget>true</TestingPlatformDisableCustomTestTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
@@ -82,7 +61,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
|
||||
<ForceImportBeforeCppProps>$(RepoRoot)Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -91,8 +70,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<Compile Include="$(RepoRoot)src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
|
||||
<AdditionalFiles Include="$(RepoRoot)src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -100,15 +79,7 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- In CI, we build and test with `/t:Build;Test` -->
|
||||
<!-- So, for non-test projects, we want the target to be there and it's basically doing nothing -->
|
||||
<!-- For C# test projects, Microsoft.Testing.Platform should inject Test target here: -->
|
||||
<!-- https://github.com/microsoft/testfx/blob/5ad21909704db501f58f27d4a7ec241edd761af5/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L270-L273 -->
|
||||
<!-- For C++ test projects, the RunVSTest SDK will do its job -->
|
||||
<Target Name="Test" />
|
||||
|
||||
<!-- Add ability to run tests via "msbuild /t:Test" using the RunVSTest SDK -->
|
||||
<!-- This is only needed for C++, as we use Microsoft.Testing.Platform for C# -->
|
||||
<!-- Add ability to run tests via "msbuild /t:Test" -->
|
||||
<!--
|
||||
Work around an MSBuild bug where Microsoft.Common.Test.targets is missing from the Arm64 installation.
|
||||
See: https://github.com/dotnet/msbuild/pull/9984
|
||||
@@ -118,11 +89,11 @@
|
||||
Once the change referenced above is fixed, the ImportGroup below can be replaced with:
|
||||
<Sdk Name="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
-->
|
||||
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64' AND ('$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj')">
|
||||
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64'">
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition="'$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj'">
|
||||
<PropertyGroup>
|
||||
<VSTestLogger>trx</VSTestLogger>
|
||||
<!--
|
||||
RunVSTest by default uses %VSINSTALLDIR%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
|
||||
<MSTestVersion>3.8.3</MSTestVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
||||
@@ -40,11 +39,12 @@
|
||||
<!-- Including MessagePack to force version, since it's used by StreamJsonRpc but contains vulnerabilities. After StreamJsonRpc updates the version of MessagePack, we can upgrade StreamJsonRpc instead. -->
|
||||
<PackageVersion Include="MessagePack" Version="3.1.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.5.250829002" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.9.1-preview.1.25474.6" />
|
||||
@@ -63,7 +63,7 @@
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.66.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
|
||||
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3179.45" />
|
||||
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3405.78" />
|
||||
<!-- Package Microsoft.Win32.SystemEvents added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Drawing.Common but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
|
||||
@@ -77,17 +77,14 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.231216.1"/>
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.260209005" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.260203002" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.47" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.260209005" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental4" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.130-experimental" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
<!-- Moq to stay below v4.20 due to behavior change. need to be sure fixed -->
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="MSTest" Version="$(MSTestVersion)" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="$(MSTestVersion)" />
|
||||
<PackageVersion Include="MSTest" Version="3.8.3" />
|
||||
<PackageVersion Include="NJsonSchema" Version="11.4.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="NLog" Version="5.2.8" />
|
||||
|
||||
@@ -1582,7 +1582,6 @@ SOFTWARE.
|
||||
- ModernWpfUI
|
||||
- Moq
|
||||
- MSTest
|
||||
- MSTest.TestFramework
|
||||
- NJsonSchema
|
||||
- NLog
|
||||
- NLog.Extensions.Logging
|
||||
@@ -1603,4 +1602,4 @@ SOFTWARE.
|
||||
- WinUIEx
|
||||
- WmiLight
|
||||
- WPF-UI
|
||||
- WyHash
|
||||
- WyHash
|
||||
@@ -4,6 +4,10 @@
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Folder Name="/common/">
|
||||
<Project Path="src/common/AllExperiments/AllExperiments.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/common/CalculatorEngineCommon/CalculatorEngineCommon.vcxproj" Id="2cf78cf7-8feb-4be1-9591-55fa25b48fc6" />
|
||||
<Project Path="src/common/Common.Search/Common.Search.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
@@ -13,10 +17,6 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/common/Common.UI.Controls/Common.UI.Controls.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/common/COMUtils/COMUtils.vcxproj" Id="7319089e-46d6-4400-bc65-e39bdf1416ee" />
|
||||
<Project Path="src/common/Display/Display.vcxproj" Id="caba8dfb-823b-4bf2-93ac-3f31984150d9" />
|
||||
<Project Path="src/common/FilePreviewCommon/FilePreviewCommon.csproj">
|
||||
@@ -196,10 +196,6 @@
|
||||
<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">
|
||||
@@ -223,10 +219,6 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
<Deploy />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
@@ -279,6 +271,16 @@
|
||||
<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" />
|
||||
@@ -299,7 +301,7 @@
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/CommandPalette/Tests/">
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Common.UnitTests/Microsoft.CmdPal.Common.UnitTests.csproj">
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Core.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
@@ -497,31 +499,6 @@
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngine/KeyboardManagerEngine.vcxproj" Id="ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd" />
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManagerEngineLibrary.vcxproj" Id="e496b7fc-1e99-4bab-849b-0e8367040b02" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/">
|
||||
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
|
||||
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
|
||||
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/Tests/">
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/keyboardmanager/Tests/">
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEditorTest/KeyboardManagerEditorTest.vcxproj" Id="62173d9a-6724-4c00-a1c8-fb646480a9ec" />
|
||||
<Project Path="src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj" Id="7f4b3a60-bc27-45a7-8000-68b0b6ea7466" />
|
||||
@@ -713,13 +690,11 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<!-- TEMPORARILY_DISABLED: PowerDisplay
|
||||
<Project Path="src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/powerdisplay/PowerDisplayModuleInterface/PowerDisplayModuleInterface.vcxproj" Id="d1234567-8901-2345-6789-abcdef012345" />
|
||||
-->
|
||||
</Folder>
|
||||
<Folder Name="/modules/PowerDisplay/Tests/">
|
||||
<Project Path="src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/PowerDisplay.Lib.UnitTests.csproj">
|
||||
@@ -745,6 +720,31 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/">
|
||||
<Project Path="src/modules/MouseUtils/CursorWrap/CursorWrap.vcxproj" Id="48a1db8c-5df8-4fb3-9e14-2b67f3f2d8b5" />
|
||||
<Project Path="src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj" Id="e94fd11c-0591-456f-899f-efc0ca548336" />
|
||||
<Project Path="src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj" Id="782a61be-9d85-4081-b35c-1ccc9dcc1e88" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common/MouseJump.Common.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseJump/MouseJump.vcxproj" Id="8a08d663-4995-40e3-b42c-3f910625f284" />
|
||||
<Project Path="src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MousePointerCrosshairs/MousePointerCrosshairs.vcxproj" Id="eae14c0e-7a6b-45da-9080-a7d8c077ba6e" />
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseUtils/Tests/">
|
||||
<Project Path="src/modules/MouseUtils/MouseJump.Common.UnitTests/MouseJump.Common.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/MouseUtils/MouseUtils.UITests/MouseUtils.UITests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/MouseWithoutBorders/">
|
||||
<Project Path="src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
@@ -1059,6 +1059,16 @@
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/tools/SettingsSearchEvaluation/">
|
||||
<Project Path="tools/SettingsSearchEvaluation/SettingsSearchEvaluation.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="tools/SettingsSearchEvaluation.Tests/SettingsSearchEvaluation.Tests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".vsconfig" />
|
||||
<File Path="Cpp.Build.props" />
|
||||
|
||||
228
README.md
228
README.md
@@ -51,19 +51,19 @@ But to get started quickly, choose one of the installation methods below:
|
||||
Go to the <a href="https://aka.ms/installPowerToys">PowerToys GitHub releases</a>, click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer.
|
||||
|
||||
<!-- items that need to be updated release to release -->
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.99%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.98%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysUserSetup-0.98.0-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysUserSetup-0.98.0-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysSetup-0.98.0-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.98.0/PowerToysSetup-0.98.0-arm64.exe
|
||||
[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.98%22
|
||||
[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22
|
||||
[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-x64.exe
|
||||
[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysUserSetup-0.97.1-arm64.exe
|
||||
[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-x64.exe
|
||||
[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.97.1/PowerToysSetup-0.97.1-arm64.exe
|
||||
|
||||
| Description | Filename |
|
||||
|----------------|----------|
|
||||
| Per user - x64 | [PowerToysUserSetup-0.98.0-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.98.0-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.98.0-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.98.0-arm64.exe][ptMachineArm64] |
|
||||
| Per user - x64 | [PowerToysUserSetup-0.97.1-x64.exe][ptUserX64] |
|
||||
| Per user - ARM64 | [PowerToysUserSetup-0.97.1-arm64.exe][ptUserArm64] |
|
||||
| Machine wide - x64 | [PowerToysSetup-0.97.1-x64.exe][ptMachineX64] |
|
||||
| Machine wide - ARM64 | [PowerToysSetup-0.97.1-arm64.exe][ptMachineArm64] |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -102,14 +102,214 @@ winget install --scope machine Microsoft.PowerToys -s winget
|
||||
There are <a href="https://learn.microsoft.com/windows/powertoys/install#community-driven-install-tools">community driven install methods</a> such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
|
||||
</details>
|
||||
|
||||
## ✨ What's new?
|
||||
## ✨ What's new
|
||||
**Version 0.97.1 (January 2026)**
|
||||
|
||||
[](https://github.com/microsoft/PowerToys/releases)
|
||||
This patch release fixes several important stability issues identified in v0.97.0 based on incoming reports. Check out the [v0.97.0](https://github.com/microsoft/PowerToys/releases/tag/v0.97.0) notes for the full list of changes.
|
||||
|
||||
To see what's new, check out the [release notes](https://github.com/microsoft/PowerToys/releases/tag/v0.98.0).
|
||||
**Highlights**
|
||||
|
||||
### Advanced Paste
|
||||
- #44862: Fixed Settings UI advanced paste page crash by using correct settings repository for null checking.
|
||||
|
||||
### Command Palette
|
||||
- #44886: Fixed personalization section not appearing by using latest MSIX for installation.
|
||||
- #44938: Fixed loading of icons from internet shortcuts. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- #45076: Fixed potential deadlock from lazy-loading AppListItem details. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
|
||||
### Cursor Wrap
|
||||
- #44936: Added improved multi-monitor support; Added laptop lid close detection for dynamic monitor topology updates. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
- #44936: Added new settings dropdown to constrain wrapping to horizontal-only, vertical-only, or both directions. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
|
||||
### Peek
|
||||
- #44995: Fixed Space key triggering Peek during file rename, search, or address bar typing.
|
||||
|
||||
### PowerRename
|
||||
- #44944: Fixed regex `$` not working, preventing users from adding text at the end of filenames.
|
||||
|
||||
### Runner
|
||||
- #44931: Monochrome tray icon now adapts to Windows system theme instead of app theme.
|
||||
- #44982: Fixed right-click menu to dynamically update based on Quick Access enabled/disabled state.
|
||||
|
||||
### GPO / Enterprise
|
||||
- #45028: Added CursorWrap policy definition to ADMX templates. Thanks [@htcfreek](https://github.com/htcfreek)!
|
||||
|
||||
For the full list of v0.97 changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog).
|
||||
|
||||
## Advanced Paste
|
||||
|
||||
- Added hex color previews in clipboard history. Thanks [@crramirez](https://github.com/crramirez)!
|
||||
- Added automatic placeholder endpoints when required fields are left empty.
|
||||
- Fixed a grammar issue in the AI settings description. Thanks [@erik-anderson](https://github.com/erik-anderson)!
|
||||
- Fixed loading order so custom action hotkeys are read correctly.
|
||||
- Updated Advanced Paste descriptions to reflect support for online and local models.
|
||||
- Fixed clipboard history item selection so it doesn’t duplicate entries.
|
||||
- Prevented placeholder endpoints from being saved for providers that don’t need them.
|
||||
- Added image input support for AI transforms and improved clipboard change tracking.
|
||||
|
||||
## Awake
|
||||
|
||||
- Fixed Awake CLI so help, errors, and logs appear correctly in the console. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
|
||||
## Command Palette
|
||||
|
||||
- Fixed background image loading in BlurImageControl. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed SDK packaging paths and added a CI SDK build stage.
|
||||
- Aligned naming and spell-checking with .NET conventions. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added drag-and-drop support for Command Palette items. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added a PowerToys Command Palette extension to discover and launch PowerToys utilities.
|
||||
- Fixed grid view bindings and layout issues. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Fixed a line-break issue in RDC extension toast messages. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Made the Settings button text localizable. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Hid the RDC fallback on the home page and fixed MSTSC working directory handling. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Optimized result list merging for better performance. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Added Small/Medium/Large detail sizes in the extensions API. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)!
|
||||
- Hid fallback commands on the home page when no query is entered. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added back navigation support in the Settings window. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added a Command Palette solution filter. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Updated Extension SDK documentation links to Microsoft Learn. Thanks [@RubenFricke](https://github.com/RubenFricke)!
|
||||
- Added a custom search engine URL setting for Web Search. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added pinyin matching for Chinese input. Thanks [@frg2089](https://github.com/frg2089)!
|
||||
- Bumped Command Palette version to 0.8.
|
||||
- Removed subtitles from built-in top-level commands. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Refined separator styling in the details pane. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added a built-in Remote Desktop extension.
|
||||
- Added a Peek command to the Indexer extension.
|
||||
- Improved default browser detection using the Windows Shell API. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added Escape key behavior options. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added theme and background customization options. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Improved WinGet package app matching. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added an auto-return-home delay setting. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added fallback ranking and global results settings.
|
||||
- Removed the selection indicator in the context menu list. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added a developer ribbon with build and log info. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Updated the “Learn more” string for Command Palette. Thanks [@pratnala](https://github.com/pratnala)!
|
||||
- Added arrow-key navigation for grid views. Thanks [@samrueby](https://github.com/samrueby)!
|
||||
- Fixed version display when running unpackaged. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added a native debugging launch profile. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Reduced redundant property change notifications in the SDK. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Improved section readability and accessibility. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Made gallery spacing uniform. Thanks [@jiripolasek](https://github.com/jiripolasek)!
|
||||
- Added sections and separators for list and grid pages. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)!
|
||||
|
||||
## Crop & Lock
|
||||
|
||||
- Added a screenshot mode that freezes a cropped region into its own window. Thanks [@fm-sys](https://github.com/fm-sys)!
|
||||
|
||||
## Cursor Wrap
|
||||
|
||||
- Improved Cursor Wrap behavior on multi-monitor setups by wrapping only at outer edges. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
|
||||
## FancyZones
|
||||
|
||||
- Fixed editor overlay positioning on mixed-DPI multi-monitor setups. Thanks [@Memphizzz](https://github.com/Memphizzz)!
|
||||
- Added a FancyZones CLI for command-line layout management.
|
||||
|
||||
## File Locksmith
|
||||
|
||||
- Added a File Locksmith CLI for querying, waiting on, or killing file locks.
|
||||
|
||||
## Find My Mouse
|
||||
|
||||
- Improved spotlight edge rendering for clearer Find My Mouse visuals.
|
||||
- Added telemetry to track how Find My Mouse is triggered.
|
||||
|
||||
## Image Resizer
|
||||
|
||||
- Fixed Fill mode cropping when Shrink Only is enabled. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Added a dedicated Image Resizer CLI for scripted resizing.
|
||||
|
||||
## Light Switch
|
||||
|
||||
- Added telemetry events for Light Switch usage and settings changes.
|
||||
- Added a Follow Night Light mode to sync theme changes with Night Light.
|
||||
- Clarified LightSwitchService and LightSwitchStateManager roles in docs.
|
||||
- Added a Quick Access dashboard button to toggle Light Switch quickly.
|
||||
- Ensured Light Switch honors GPO policy states with clear status messaging.
|
||||
|
||||
## Mouse Without Borders
|
||||
|
||||
- Continued refactoring Mouse Without Borders by splitting the large Common class into focused components. Thanks [@mikeclayton](https://github.com/mikeclayton)!
|
||||
- Completed the Common class refactor with Core and IPC helper extraction. Thanks [@mikeclayton](https://github.com/mikeclayton)!
|
||||
|
||||
## Peek
|
||||
|
||||
- Hardened Peek previews with strict resource filtering and safer external link warnings.
|
||||
- Improved SVG preview compatibility by rendering via WebView2.
|
||||
|
||||
## PowerRename
|
||||
|
||||
- Added HEIF/AVIF EXIF metadata extraction and extension status guidance for related previews.
|
||||
- Fixed undefined behavior in file time handling. Thanks [@safocl](https://github.com/safocl)!
|
||||
- Optimized memory allocation for depth-based rename processing.
|
||||
- Fixed Unicode normalization and non‑breaking space matching. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Fixed date token replacements followed by capital letters. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
|
||||
## PowerToys Run Plugins
|
||||
|
||||
- Fixed a plugin name typo and added Project Launcher to the third‑party list. Thanks [@artickc](https://github.com/artickc)!
|
||||
- Added the Open With Antigravity plugin to the third‑party list. Thanks [@artickc](https://github.com/artickc)!
|
||||
|
||||
## PowerToys Run
|
||||
|
||||
- Avoided unnecessary hotkey conflict checks when settings change.
|
||||
- Added QuickAI to the third-party PowerToys Run plugin list. Thanks [@ruslanlap](https://github.com/ruslanlap)!
|
||||
|
||||
## Quick Accent
|
||||
|
||||
- Added localized quotation marks to Quick Accent. Thanks [@warquys](https://github.com/warquys)!
|
||||
- Fixed duplicate and redundant characters in Quick Accent sets. Thanks [@noraa-junker](https://github.com/noraa-junker)!
|
||||
- Fixed DPI positioning issues for Quick Accent on mixed-DPI setups. Thanks [@noraa-junker](https://github.com/noraa-junker)!
|
||||
|
||||
## Settings
|
||||
|
||||
- Added a new tray icon that adapts to theme changes. Thanks [@HO-COOH](https://github.com/HO-COOH)!
|
||||
- Centralized module enable/disable logic for cleaner Settings UI updates.
|
||||
- Simplified Settings utilities by removing ISettingsUtils/ISettingsPath interfaces. Thanks [@noraa-junker](https://github.com/noraa-junker)!
|
||||
- Improved Settings UI consistency and disabled-state visuals.
|
||||
- Added semantic headings to the Dashboard for better accessibility.
|
||||
- Introduced Quick Access as a standalone host with updated Settings integration.
|
||||
- Fixed Dashboard toggle flicker and sort menu checkmarks. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Added Native AOT-compatible settings serialization.
|
||||
- Standardized mouse tool description text. Thanks [@daverayment](https://github.com/daverayment)!
|
||||
- Added a global SettingsUtils singleton to reduce repeated initialization.
|
||||
|
||||
## Development
|
||||
|
||||
- Fixed broken devdocs links to the coding style guide. Thanks [@RubenFricke](https://github.com/RubenFricke)!
|
||||
- Migrated main and installer solutions to .slnx for improved build tooling.
|
||||
- Restored local installer builds after the WiX v5 upgrade with signing and versioning fixes.
|
||||
- Added incremental review tooling and structured AI prompts for PR/issue reviews.
|
||||
- Documented bot commands and cleaned up devdocs structure. Thanks [@noraa-junker](https://github.com/noraa-junker)!
|
||||
- Updated WinAppSDK pipeline defaults to 1.8 and fixed restore handling.
|
||||
- Updated the COMMUNITY list to reflect current roles.
|
||||
- Maintained community member ordering and added a new entry.
|
||||
- Re-enabled centralized PackageReference for native projects with VS auto-restore.
|
||||
- Disabled MSBuild caching by default in CI to avoid build instability.
|
||||
- Updated the latest WinAppSDK daily pipeline for split-dependency restores.
|
||||
- Suppressed experimental build warnings and aligned WrapPanel stretch handling.
|
||||
- Reordered the spell-check expect list for consistent automation.
|
||||
- Migrated native projects to centralized PackageReference management.
|
||||
- Cleaned spell-check dictionary entries and capitalization.
|
||||
- Synced commit/PR prompts and wired VS Code to repo prompt files.
|
||||
- Added VS Code build tasks and improved build script path handling.
|
||||
- Updated Windows App SDK package versions in central package management.
|
||||
- Migrated cmdpal extension native project to PackageReference and fixed outputs.
|
||||
- Reverted PackageReference changes back to packages.config where needed.
|
||||
- Bypassed a release version check for a failing DLL to keep pipelines green.
|
||||
- Consolidated Copilot instructions and fixed prompt frontmatter.
|
||||
- Added signing entries for new Quick Access binaries and CLI version metadata.
|
||||
- Fixed install scope detection to avoid mixed per-user/per-machine installs.
|
||||
- Added a Module Loader tool to quickly test PowerToys modules without full builds. Thanks [@mikehall-ms](https://github.com/mikehall-ms)!
|
||||
- Added update telemetry to understand auto-update checks and downloads.
|
||||
- Updated the telemetry package for new compliance requirements. Thanks [@carlos-zamora](https://github.com/carlos-zamora)!
|
||||
- Documented missing telemetry events in DATA_AND_PRIVACY.
|
||||
- Fixed UI test pipeline restores for .slnx solutions.
|
||||
- Added UI automation coverage for Advanced Paste clipboard history flows.
|
||||
- Stabilized FancyZones UI tests with more reliable selectors and screen recordings.
|
||||
|
||||
## 🛣️ Roadmap
|
||||
We are planning some nice new features and improvements for the next releases – PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.99][github-next-release-work]!
|
||||
We are planning some nice new features and improvements for the next releases – PowerDisplay, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.98][github-next-release-work]!
|
||||
|
||||
## ❤️ PowerToys Community
|
||||
The PowerToys team is extremely grateful to have the [support of an amazing active community][community-link]. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month!
|
||||
|
||||
244
SEMANTIC_SEARCH_API_SUMMARY.md
Normal file
244
SEMANTIC_SEARCH_API_SUMMARY.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Windows App SDK Semantic Search API 总结
|
||||
|
||||
## 1. 环境与依赖
|
||||
|
||||
| 项目 | 版本/值 |
|
||||
|------|---------|
|
||||
| **Windows App SDK** | `2.0.0-experimental3` |
|
||||
| **.NET** | `net9.0-windows10.0.26100.0` |
|
||||
| **AI Search NuGet** | `Microsoft.WindowsAppSDK.AI` (2.0.57-experimental) |
|
||||
| **命名空间** | `Microsoft.Windows.AI.Search.Experimental.AppContentIndex` |
|
||||
| **应用类型** | WinUI 3 MSIX 打包应用 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心 API
|
||||
|
||||
### 2.1 索引管理
|
||||
```csharp
|
||||
// 创建/打开索引
|
||||
var result = AppContentIndexer.GetOrCreateIndex("indexName");
|
||||
if (result.Succeeded) {
|
||||
_indexer = result.Indexer;
|
||||
// result.Status: CreatedNew | OpenedExisting
|
||||
}
|
||||
|
||||
// 等待索引能力就绪
|
||||
await _indexer.WaitForIndexCapabilitiesAsync();
|
||||
|
||||
// 等待索引空闲(建索引完成)
|
||||
await _indexer.WaitForIndexingIdleAsync(TimeSpan.FromSeconds(120));
|
||||
|
||||
// 清理
|
||||
_indexer.RemoveAll(); // 删除所有索引
|
||||
_indexer.Remove(id); // 删除单个
|
||||
_indexer.Dispose();
|
||||
```
|
||||
|
||||
### 2.2 添加内容到索引
|
||||
```csharp
|
||||
// 索引文本 → 自动建立 TextLexical + TextSemantic 索引
|
||||
IndexableAppContent textContent = AppManagedIndexableAppContent.CreateFromString(id, text);
|
||||
_indexer.AddOrUpdate(textContent);
|
||||
|
||||
// 索引图片 → 自动建立 ImageSemantic + ImageOcr 索引
|
||||
IndexableAppContent imageContent = AppManagedIndexableAppContent.CreateFromBitmap(id, softwareBitmap);
|
||||
_indexer.AddOrUpdate(imageContent);
|
||||
```
|
||||
|
||||
### 2.3 查询
|
||||
```csharp
|
||||
// 文本查询
|
||||
TextQueryOptions options = new TextQueryOptions {
|
||||
Language = "en-US", // 可选
|
||||
MatchScope = QueryMatchScope.Unconstrained, // 匹配范围
|
||||
TextMatchType = TextLexicalMatchType.Fuzzy // Fuzzy | Exact
|
||||
};
|
||||
AppIndexTextQuery query = _indexer.CreateTextQuery(searchText, options);
|
||||
IReadOnlyList<TextQueryMatch> matches = query.GetNextMatches(5);
|
||||
|
||||
// 图片查询
|
||||
ImageQueryOptions imgOptions = new ImageQueryOptions {
|
||||
MatchScope = QueryMatchScope.Unconstrained,
|
||||
ImageOcrTextMatchType = TextLexicalMatchType.Fuzzy
|
||||
};
|
||||
AppIndexImageQuery imgQuery = _indexer.CreateImageQuery(searchText, imgOptions);
|
||||
IReadOnlyList<ImageQueryMatch> imgMatches = imgQuery.GetNextMatches(5);
|
||||
```
|
||||
|
||||
### 2.4 能力检查(只读)
|
||||
```csharp
|
||||
IndexCapabilities capabilities = _indexer.GetIndexCapabilities();
|
||||
|
||||
bool textLexicalOK = capabilities.GetCapabilityState(IndexCapability.TextLexical)
|
||||
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
|
||||
bool textSemanticOK = capabilities.GetCapabilityState(IndexCapability.TextSemantic)
|
||||
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
|
||||
bool imageSemanticOK = capabilities.GetCapabilityState(IndexCapability.ImageSemantic)
|
||||
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
|
||||
bool imageOcrOK = capabilities.GetCapabilityState(IndexCapability.ImageOcr)
|
||||
.InitializationStatus == IndexCapabilityInitializationStatus.Initialized;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 四种索引能力
|
||||
|
||||
| 能力 | 说明 | 触发方式 |
|
||||
|------|------|----------|
|
||||
| `TextLexical` | 词法/关键词搜索 | CreateFromString() 自动 |
|
||||
| `TextSemantic` | AI 语义搜索 (Embedding) | CreateFromString() 自动 |
|
||||
| `ImageSemantic` | 图像语义搜索 | CreateFromBitmap() 自动 |
|
||||
| `ImageOcr` | 图片 OCR 文字搜索 | CreateFromBitmap() 自动 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 可控选项(有限)
|
||||
|
||||
### TextQueryOptions
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `Language` | string | 查询语言(可选,如 "en-US")|
|
||||
| `MatchScope` | QueryMatchScope | Unconstrained / Region / ContentItem |
|
||||
| `TextMatchType` | TextLexicalMatchType | **Fuzzy** / Exact(仅影响 Lexical)|
|
||||
|
||||
### ImageQueryOptions
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `MatchScope` | QueryMatchScope | Unconstrained / Region / ContentItem |
|
||||
| `ImageOcrTextMatchType` | TextLexicalMatchType | **Fuzzy** / Exact(仅影响 OCR)|
|
||||
|
||||
### 枚举值说明
|
||||
|
||||
**QueryMatchScope:**
|
||||
- `Unconstrained` - 无约束,同时使用 Lexical + Semantic
|
||||
- `Region` - 限制在特定区域
|
||||
- `ContentItem` - 限制在单个内容项
|
||||
|
||||
**TextLexicalMatchType:**
|
||||
- `Fuzzy` - 模糊匹配,允许拼写错误、近似词
|
||||
- `Exact` - 精确匹配,必须完全一致
|
||||
|
||||
---
|
||||
|
||||
## 5. 关键限制 ⚠️
|
||||
|
||||
| 限制 | 说明 |
|
||||
|------|------|
|
||||
| **不能单独指定 Semantic/Lexical** | 系统自动同时使用所有可用能力 |
|
||||
| **Fuzzy/Exact 只影响 Lexical** | 对 Semantic 搜索无效 |
|
||||
| **能力检查是只读的** | `GetIndexCapabilities()` 只能查看,不能控制 |
|
||||
| **无相似度阈值** | 不能设置 Semantic 匹配的阈值 |
|
||||
| **无结果排序控制** | 无法指定按相关度或其他方式排序 |
|
||||
| **语言需手动传** | 不会自动检测,需开发者指定 |
|
||||
| **无相关度分数** | 查询结果不返回匹配分数 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 典型使用流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ App 启动时 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 1. GetOrCreateIndex("name") // 创建/打开索引 │
|
||||
│ 2. WaitForIndexCapabilitiesAsync() // 等待能力就绪 │
|
||||
│ 3. GetIndexCapabilities() // 检查可用能力 │
|
||||
│ 4. IndexAll() // 索引所有数据 │
|
||||
│ ├─ CreateFromString() × N │
|
||||
│ └─ CreateFromBitmap() × N │
|
||||
│ 5. WaitForIndexingIdleAsync() // 等待索引完成 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 运行时查询 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 1. CreateTextQuery(text, options) // 创建查询 │
|
||||
│ 2. query.GetNextMatches(N) // 获取结果 │
|
||||
│ 3. 处理 TextQueryMatch / ImageQueryMatch │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ App 退出时 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 1. _indexer.RemoveAll() // 清理索引 │
|
||||
│ 2. _indexer.Dispose() // 释放资源 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 查询结果处理
|
||||
|
||||
```csharp
|
||||
// 文本查询结果
|
||||
foreach (var match in textMatches)
|
||||
{
|
||||
if (match.ContentKind == QueryMatchContentKind.AppManagedText)
|
||||
{
|
||||
AppManagedTextQueryMatch textResult = (AppManagedTextQueryMatch)match;
|
||||
string contentId = match.ContentId; // 内容 ID
|
||||
int offset = textResult.TextOffset; // 匹配文本偏移
|
||||
int length = textResult.TextLength; // 匹配文本长度
|
||||
}
|
||||
}
|
||||
|
||||
// 图片查询结果
|
||||
foreach (var match in imageMatches)
|
||||
{
|
||||
if (match.ContentKind == QueryMatchContentKind.AppManagedImage)
|
||||
{
|
||||
AppManagedImageQueryMatch imageResult = (AppManagedImageQueryMatch)match;
|
||||
string contentId = imageResult.ContentId; // 图片 ID
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 能力变化监听
|
||||
|
||||
```csharp
|
||||
// 监听索引能力变化
|
||||
_indexer.Listener.IndexCapabilitiesChanged += (indexer, capabilities) =>
|
||||
{
|
||||
// 重新检查能力状态,更新 UI
|
||||
LoadAppIndexCapabilities();
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 结论
|
||||
|
||||
这是一个**高度封装的黑盒 API**:
|
||||
|
||||
### 优点 ✅
|
||||
- 简单易用,几行代码即可实现搜索
|
||||
- 自动处理 Lexical + Semantic
|
||||
- 支持文本和图片多模态搜索
|
||||
- 系统级集成,无需额外部署模型
|
||||
|
||||
### 缺点 ❌
|
||||
- 无法精细控制搜索类型
|
||||
- 不能只用 Semantic Search
|
||||
- 选项有限,缺乏高级配置
|
||||
- 实验性 API,可能变更
|
||||
|
||||
### 替代方案
|
||||
**如果需要纯 Semantic Search(向量搜索)**,建议:
|
||||
- 直接使用 Embedding 模型生成向量
|
||||
- 配合向量数据库(Azure Cosmos DB、FAISS、Qdrant 等)
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关 NuGet 包
|
||||
|
||||
```xml
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.0.0-experimental3" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK.AI" Version="2.0.57-experimental" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*文档生成日期: 2026-01-21*
|
||||
@@ -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\
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
# Telemetry Events
|
||||
|
||||
PowerToys collects limited telemetry to understand feature usage, reliability, and product quality. When adding a new telemetry event, follow the steps below to ensure the event is properly declared, documented, and available after release.
|
||||
|
||||
**⚠️ Important**: Telemetry must never include personal information, file paths, or user‑generated content.
|
||||
|
||||
## Developer Effort Overview (What to Expect)
|
||||
|
||||
Adding a telemetry event is a **multi-step process** that typically spans several areas of the codebase and documentation.
|
||||
|
||||
At a high level, developers should expect to:
|
||||
|
||||
1. Within one PR:
|
||||
1. Add a new telemetry event(s) to module
|
||||
1. Add the new event(s) DATA_AND_PRIVACY.md
|
||||
1. Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s)
|
||||
|
||||
### Privacy Guidelines
|
||||
|
||||
**NEVER** log:
|
||||
|
||||
- User data (text, files, emails, etc.)
|
||||
- File paths or filenames
|
||||
- Personal information
|
||||
- Sensitive system information
|
||||
- Anything that could identify a specific user
|
||||
|
||||
DO log:
|
||||
|
||||
- Feature usage (which features, how often)
|
||||
- Success/failure status
|
||||
- Timing/performance metrics
|
||||
- Error types (not error messages with user data)
|
||||
- Aggregate counts
|
||||
|
||||
### Event Naming Convention
|
||||
|
||||
Follow this pattern: `UtilityName_EventDescription`
|
||||
|
||||
Examples:
|
||||
|
||||
- `ColorPicker_Session`
|
||||
- `FancyZones_LayoutApplied`
|
||||
- `PowerRename_Rename`
|
||||
- `AdvancedPaste_FormatClicked`
|
||||
- `CmdPal_ExtensionInvoked`
|
||||
|
||||
## Adding Telemetry Events to PowerToys
|
||||
|
||||
PowerToys uses ETW (Event Tracing for Windows) for telemetry in both C++ and C# modules. The telemetry system is:
|
||||
|
||||
- Opt-in by default (disabled since v0.86)
|
||||
- Privacy-focused - never logs personal info, file paths, or user-generated content
|
||||
- Controlled by registry - HKEY_CURRENT_USER\Software\Classes\PowerToys\AllowDataDiagnostics
|
||||
|
||||
### C++ Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [ProjectTelemetry.h](../../src/common/Telemetry/ProjectTelemetry.h) | Declares the global ETW provider g_hProvider |
|
||||
| [TraceBase.h](../../src/common/Telemetry/TraceBase.h) | Base class with RegisterProvider(), UnregisterProvider(), and IsDataDiagnosticsEnabled() check |
|
||||
| [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Privacy tags and telemetry option group macros
|
||||
|
||||
|
||||
#### Pattern for C++ Modules
|
||||
|
||||
1. Create a `Trace` class inheriting from `telemetry::TraceBase` (src/common/Telemetry/TraceBase.h):
|
||||
|
||||
```c
|
||||
// trace.h
|
||||
#pragma once
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
class Trace : public telemetry::TraceBase
|
||||
{
|
||||
public:
|
||||
static void MyEvent(/* parameters */);
|
||||
};
|
||||
```
|
||||
|
||||
2. Implement events using `TraceLoggingWriteWrapper`:
|
||||
|
||||
```cpp
|
||||
// trace.cpp
|
||||
#include "trace.h"
|
||||
#include <common/Telemetry/TraceBase.h>
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::MyEvent(bool enabled)
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"ModuleName_EventName", // Event name
|
||||
TraceLoggingBoolean(enabled, "Enabled"), // Event data
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
```
|
||||
|
||||
**Key C++ Telemetry Macros**
|
||||
|
||||
| Macro | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| `TraceLoggingWriteWrapper` [CustomAction.cpp](../../installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp) | Wraps `TraceLoggingWrite` with `IsDataDiagnosticsEnabled()` check |
|
||||
| `ProjectTelemetryPrivacyDataTag(tag)` [TraceLoggingDefines.h](../../src/common/Telemetry/TraceLoggingDefines.h) | Sets privacy classification |
|
||||
|
||||
### C# Telemetry Implementation
|
||||
|
||||
**Core Components**
|
||||
|
||||
| File | Purpose |
|
||||
| ------------- |:-------------:|
|
||||
| [PowerToysTelemetry.cs](../../src/common/ManagedTelemetry/Telemetry/PowerToysTelemetry.cs) | Singleton `Log` instance with `WriteEvent<T>()` method |
|
||||
| [EventBase.cs](../../src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) | Base class for all events (provides `EventName`, `Version`) |
|
||||
| [IEvent.cs](../../src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) | Interface requiring `PartA_PrivTags` property |
|
||||
| [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Inherits from `EventSource`, defines ETW constants |
|
||||
| [DataDiagnosticsSettings.cs](../../src/common/ManagedTelemetry/Telemetry/DataDiagnosticsSettings.cs) | Registry-based enable/disable check
|
||||
|
||||
#### Pattern for C# Modules
|
||||
|
||||
1. Create an event class inheriting from `EventBase` and implementing `IEvent`:
|
||||
|
||||
```csharp
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace MyModule.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class MyModuleEvent : EventBase, IEvent
|
||||
{
|
||||
// Event properties (logged as telemetry data)
|
||||
public string SomeProperty { get; set; }
|
||||
public int SomeValue { get; set; }
|
||||
|
||||
// Required: Privacy tag
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
// Optional: Set EventName in constructor (defaults to class name)
|
||||
public MyModuleEvent(string prop, int val)
|
||||
{
|
||||
EventName = "MyModule_EventName";
|
||||
SomeProperty = prop;
|
||||
SomeValue = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Log the event:
|
||||
|
||||
```csharp
|
||||
PowerToysTelemetry.Log.WriteEvent(new MyModuleEvent("value", 42));
|
||||
```
|
||||
|
||||
**Privacy Tags (C#)**
|
||||
|
||||
| Tag | Use Case |
|
||||
| ------------- |:-------------:|
|
||||
| `PartA_PrivTags.ProductAndServiceUsage` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Feature usage events
|
||||
| `PartA_PrivTags.ProductAndServicePerformance` [TelemetryBase.cs](../../src/common/Telemetry/TelemetryBase.cs) | Performance/timing events
|
||||
|
||||
### Update DATA_AND_PRIVACY.md file
|
||||
|
||||
Add your new event(s) to [DATA_AND_PRIVACY.md](../../DATA_AND_PRIVACY.md).
|
||||
|
||||
## Launch Product Version Containing the new events
|
||||
|
||||
Events do not become active until they ship in a released PowerToys version. After your PRs are merged:
|
||||
|
||||
- The event will begin firing once users install the version that includes it
|
||||
- In order for PowerToys to process these events, you must complete the next section
|
||||
|
||||
## Next Steps
|
||||
|
||||
Reach out to @carlos-zamora or @chatasweetie so internal scripts can process new event(s).
|
||||
|
||||
## Summary
|
||||
|
||||
Required steps:
|
||||
|
||||
1. In one PR:
|
||||
- Add the event(s) in code
|
||||
- Document event(s) in DATA_AND_PRIVACY.md
|
||||
1. Ship the change in a PowerToys release
|
||||
1. Reach out for next steps
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -141,10 +141,3 @@ Note: The DllHost process loads the DLL only when the context menu is triggered
|
||||
- A signature issue with the MSIX package
|
||||
|
||||
- For development and testing, using the Windows 10 handler can be easier since it doesn't require signing.
|
||||
|
||||
## Restoring Built-in Windows New context menu
|
||||
If the Windows 11 built-in New context menu doesn't reappear on uninstalling PowerToys, some issue with settings etc. here's how to restore the built-in New context menu.
|
||||
|
||||
1. Open Registry Editor
|
||||
1. Go to the key "Computer\HKEY_CURRENT_USER\Software\Classes\Directory\background\ShellEx\ContextMenuHandlers"
|
||||
1. Delete the "New" subkey (i.e. fullpath "Computer\HKEY_CURRENT_USER\Software\Classes\Directory\background\ShellEx\ContextMenuHandlers\New")
|
||||
@@ -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,81 +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.
|
||||
|
||||
### Building Command Palette Extensions
|
||||
|
||||
If you want to build your own extensions for Command Palette, check out the [Command Palette extensibility documentation](https://aka.ms/building-cmdpal-extensions). It covers how to create, package, and distribute custom extensions that integrate with Command Palette.
|
||||
|
||||
## 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).
|
||||
@@ -176,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).
|
||||
|
||||
325
doc/devdocs/sparse-package-investigation.md
Normal file
325
doc/devdocs/sparse-package-investigation.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# Sparse Package + WinUI 3 调查报告
|
||||
|
||||
## 背景
|
||||
|
||||
PowerToys 希望使用 Windows App SDK 的 Semantic Search API (`AppContentIndexer.GetOrCreateIndex`) 来实现语义搜索功能。该 API 要求应用具有 **Package Identity**。
|
||||
|
||||
## 问题现象
|
||||
|
||||
### 1. Semantic Search API 调用失败
|
||||
|
||||
在 [SemanticSearchIndex.cs](../../src/common/Common.Search/SemanticSearch/SemanticSearchIndex.cs) 中调用 `AppContentIndexer.GetOrCreateIndex(_indexName)` 时,抛出 COM 异常:
|
||||
|
||||
```
|
||||
System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
|
||||
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|39_0(Int32 hr)
|
||||
at Microsoft.Windows.AI.Search.AppContentIndexer.GetOrCreateIndex(String indexName)
|
||||
```
|
||||
|
||||
### 2. API 要求
|
||||
|
||||
根据 [Windows App SDK 文档](https://learn.microsoft.com/en-us/windows/ai/apis/content-search),Semantic Search API 需要:
|
||||
- Windows 11 24H2 或更高版本
|
||||
- NPU 硬件支持
|
||||
- **Package Identity**(应用需要有 MSIX 包标识)
|
||||
|
||||
## Sparse Package 方案
|
||||
|
||||
### 什么是 Sparse Package
|
||||
|
||||
Sparse Package(稀疏包)是一种为非打包(unpackaged)Win32 应用提供 Package Identity 的技术,无需完整的 MSIX 打包。
|
||||
|
||||
参考:[Grant package identity by packaging with external location](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
|
||||
|
||||
### 实现架构
|
||||
|
||||
```
|
||||
PowerToysSparse.msix (仅包含 manifest 和图标)
|
||||
│
|
||||
├── AppxManifest.xml (声明应用和依赖)
|
||||
├── Square44x44Logo.png
|
||||
├── Square150x150Logo.png
|
||||
└── StoreLogo.png
|
||||
|
||||
ExternalLocation (指向实际应用目录)
|
||||
│
|
||||
└── ARM64\Debug\
|
||||
├── PowerToys.Settings.exe
|
||||
├── PowerToys.Settings.pri
|
||||
└── ... (其他应用文件)
|
||||
```
|
||||
|
||||
### 关键组件
|
||||
|
||||
| 文件 | 位置 | 作用 |
|
||||
|------|------|------|
|
||||
| AppxManifest.xml | src/PackageIdentity/ | 定义 sparse package 的应用、依赖和能力 |
|
||||
| app.manifest | src/settings-ui/Settings.UI/ | 嵌入 exe 中,声明与 sparse package 的关联 |
|
||||
| BuildSparsePackage.ps1 | src/PackageIdentity/ | 构建和签名脚本 |
|
||||
|
||||
### Publisher 配置
|
||||
|
||||
**重要**:app.manifest 和 AppxManifest.xml 中的 Publisher 必须匹配。
|
||||
|
||||
| 环境 | Publisher |
|
||||
|------|-----------|
|
||||
| 开发环境 | `CN=PowerToys Dev, O=PowerToys, L=Redmond, S=Washington, C=US` |
|
||||
| 生产环境 | `CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US` |
|
||||
|
||||
BuildSparsePackage.ps1 会在本地构建时**自动**将 AppxManifest.xml 的 Publisher 替换为开发环境值,无需手动修改源码。
|
||||
|
||||
## 当前问题:WinUI 3 + Sparse Package 崩溃
|
||||
|
||||
### 现象
|
||||
|
||||
当 Settings.exe(WinUI 3 应用)通过 sparse package 启动时,立即崩溃:
|
||||
|
||||
```
|
||||
Microsoft.UI.Xaml.Markup.XamlParseException (-2144665590):
|
||||
Cannot locate resource from 'ms-appx:///Microsoft.UI.Xaml/Themes/themeresources.xaml'. [Line: 11 Position: 40]
|
||||
```
|
||||
|
||||
### 新观察(2026-01-25)
|
||||
|
||||
对齐 WinAppSDK 版本并恢复 app-local 运行时后,仍可复现**更早期**的崩溃(未写入 Settings 日志):
|
||||
|
||||
- Application Error / WER(AUMID 启动):
|
||||
- Faulting module: `CoreMessagingXP.dll`
|
||||
- Exception code: `0xc0000602`
|
||||
- Faulting module path: `C:\PowerToys\ARM64\Debug\WinUI3Apps\CoreMessagingXP.dll`
|
||||
- 暂时移除 `CoreMessagingXP.dll` 后,出现 .NET Runtime 1026:
|
||||
- `COMException (0x80040111): ClassFactory cannot supply requested class`
|
||||
- 发生在 `Microsoft.UI.Xaml.Application.Start(...)`
|
||||
|
||||
这说明 **“themeresources.xaml 无法解析”并不是唯一/必现的失败模式**,app-local 运行时在 sparse identity 下可能存在更底层的初始化问题。
|
||||
|
||||
### 新观察(2026-01-25 晚间)
|
||||
|
||||
framework-dependent + bootstrap 方向有实质进展:
|
||||
|
||||
- 设置 `WindowsAppSDKSelfContained=false`(仅在 `UseSparseIdentity=true` 时生效)
|
||||
- 添加 `WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp=true`
|
||||
- **从 ExternalLocation 根目录与 `WinUI3Apps` 目录移除 app-local WinAppSDK 运行时文件**
|
||||
- 尤其是 `CoreMessagingXP.dll`,否则会优先加载并导致 `0xc0000602`
|
||||
- **保留/放回 bootstrap DLL**
|
||||
- 必需:`Microsoft.WindowsAppRuntime.Bootstrap.Net.dll`
|
||||
- 建议同时保留:`Microsoft.WindowsAppRuntime.Bootstrap.dll`
|
||||
|
||||
按以上处理后,Settings 通过 AUMID 启动不再崩溃,日志写入恢复。
|
||||
|
||||
### 根本原因分析
|
||||
|
||||
1. **ms-appx:/// URI 机制**
|
||||
- WinUI 3 使用 `ms-appx:///` URI 加载 XAML 资源
|
||||
- 这个 URI scheme 依赖于 MSIX 包的资源索引系统
|
||||
|
||||
2. **框架资源位置**
|
||||
- `themeresources.xaml` 等主题资源在 Windows App Runtime 框架包中
|
||||
- 框架包位置:`C:\Program Files\WindowsApps\Microsoft.WindowsAppRuntime.2.0-experimental4_*\`(应与 WinAppSDK 版本匹配)
|
||||
- 资源编译在框架包的 `resources.pri` 中
|
||||
|
||||
3. **WinAppSDK 版本/依赖不一致(更可能的原因)**
|
||||
- 仓库当前引用 `Microsoft.WindowsAppSDK` **2.0.0-experimental4**(见 `Directory.Packages.props`)
|
||||
- Sparse manifest 仍依赖 **Microsoft.WindowsAppRuntime.2.0-experimental3**(`MinVersion=0.676.658.0`)
|
||||
- 通过包标识启动时会走框架包资源图,如果依赖版本不匹配,WinUI 资源解析可能失败,从而触发上述 `XamlParseException`
|
||||
- 需要先对齐依赖版本,再判断是否是 sparse 本身限制
|
||||
|
||||
4. **app-local 运行时在 sparse identity 下崩溃(已观测)**
|
||||
- 即使对齐 WinAppSDK 版本,也可能在 `CoreMessagingXP.dll` 处崩溃(`0xc0000602`)
|
||||
- 此时 Settings 日志不一定写入,需查看 Application Event Log
|
||||
|
||||
4. **Sparse Package 的限制(待验证)**
|
||||
- 之前推断 `ms-appx:///` 在 sparse package 下无法解析框架依赖资源
|
||||
- 但在修正依赖版本之前无法下结论
|
||||
|
||||
### 对比:WPF 应用可以工作
|
||||
|
||||
WPF 应用(如 ImageResizer)使用 sparse package 时**可以正常工作**,因为:
|
||||
- WPF 不依赖 `ms-appx:///` URI
|
||||
- WPF 资源加载使用不同的机制
|
||||
|
||||
## 已尝试的解决方案
|
||||
|
||||
| 方案 | 结果 | 原因 |
|
||||
|------|------|------|
|
||||
| 复制 PRI 文件到根目录 | ❌ 失败 | `ms-appx:///` 不查找本地 PRI |
|
||||
| 复制 themeresources 到本地 | ❌ 失败 | 资源在 PRI 中,不是独立文件 |
|
||||
| 修改 Settings OutputPath 到根目录 | ❌ 失败 | 问题不在于应用资源位置 |
|
||||
| 复制框架 resources.pri | ❌ 失败 | `ms-appx:///` 机制问题 |
|
||||
| 对齐 WindowsAppRuntime 依赖版本 | ⏳ 待验证 | 先排除依赖不一致导致的资源解析失败 |
|
||||
| app-local 运行时(self-contained)+ sparse identity | ❌ 失败 | Application Error: `CoreMessagingXP.dll` / `0xc0000602` |
|
||||
| 移除 `CoreMessagingXP.dll` | ❌ 失败 | .NET Runtime 1026: `ClassFactory cannot supply requested class` |
|
||||
| framework-dependent + bootstrap + 清理 ExternalLocation 中 app-local 运行时 | ✅ 成功 | 需保留 `Microsoft.WindowsAppRuntime.Bootstrap*.dll` |
|
||||
| 将 resources.pri 打进 sparse MSIX | ✅ 成功 | MRT 可从包内 resources.pri 正常解析字符串 |
|
||||
|
||||
## 当前代码状态
|
||||
|
||||
### 已修正(建议保留)
|
||||
|
||||
1. **Settings.UI 输出路径**
|
||||
- 文件:`src/settings-ui/Settings.UI/PowerToys.Settings.csproj`
|
||||
- 修改:恢复为 `WinUI3Apps`(避免破坏 runner/installer/脚本路径假设)
|
||||
|
||||
2. **AppxManifest.xml 的 Executable 路径**
|
||||
- 文件:`src/PackageIdentity/AppxManifest.xml`
|
||||
- 修改:恢复为 `WinUI3Apps\PowerToys.Settings.exe`
|
||||
|
||||
3. **AppxManifest.xml 的 WindowsAppRuntime 依赖**
|
||||
- 文件:`src/PackageIdentity/AppxManifest.xml`
|
||||
- 修改:更新为 `Microsoft.WindowsAppRuntime.2.0-experimental4`,`MinVersion=0.738.2207.0`(与 `Microsoft.WindowsAppSDK.Runtime` 2.0.0-experimental4 对齐)
|
||||
|
||||
### 未修改(源码中保持生产配置)
|
||||
|
||||
- AppxManifest.xml 的 Publisher 保持 Microsoft Corporation(脚本会自动替换)
|
||||
|
||||
### 验证步骤(建议)
|
||||
|
||||
1. **确认 WindowsAppRuntime 版本已安装**
|
||||
- `Get-AppxPackage -Name Microsoft.WindowsAppRuntime.2.0-experimental4`
|
||||
- 如缺失,可从 NuGet 缓存安装:
|
||||
`Add-AppxPackage -Path "$env:USERPROFILE\.nuget\packages\microsoft.windowsappsdk.runtime\2.0.0-experimental4\tools\MSIX\win10-x64\Microsoft.WindowsAppRuntime.2.0-experimental4.msix"`
|
||||
|
||||
2. **构建并注册 sparse package**
|
||||
- `.\src\PackageIdentity\BuildSparsePackage.ps1 -Platform x64 -Configuration Debug`
|
||||
- `Add-AppxPackage -Path ".\x64\Debug\PowerToysSparse.msix" -ExternalLocation ".\x64\Debug"`
|
||||
|
||||
3. **用包标识启动 Settings**
|
||||
- AUMID:`Microsoft.PowerToys.SparseApp!PowerToys.SettingsUI`
|
||||
- 预期:不再触发 `themeresources.xaml` 解析错误
|
||||
|
||||
## 可能的解决方向
|
||||
|
||||
### 方向 1:等待 Windows App SDK 修复
|
||||
|
||||
- 可能是 Windows App SDK 的已知限制或 bug
|
||||
- 需要在 GitHub issues 中搜索或提交新 issue
|
||||
|
||||
### 方向 2:使用完整 MSIX 打包
|
||||
|
||||
- 不使用 sparse package,而是完整打包
|
||||
- 影响:改变部署模型,增加复杂性
|
||||
|
||||
### 方向 3:创建非 WinUI 3 的 Helper 进程
|
||||
|
||||
- 创建一个 Console App 或 WPF App 作为 helper
|
||||
- 该 helper 具有 package identity,专门调用 Semantic Search API
|
||||
- Settings 通过 IPC 与 helper 通信
|
||||
- 优点:不影响现有 Settings 架构
|
||||
|
||||
### 方向 4:进一步调查 ms-appx:/// 解析
|
||||
|
||||
- 研究是否有配置选项让 sparse package 正确解析框架资源
|
||||
- 可能需要深入 Windows App SDK 源码或联系微软
|
||||
|
||||
### 方向 5:切换为 framework-dependent + Bootstrap(待验证)
|
||||
|
||||
- 设置 `WindowsAppSDKSelfContained=false` 并**重新构建** Settings
|
||||
- 确保外部目录不再携带 app-local WinAppSDK 运行时
|
||||
- 让 `Bootstrap.TryInitialize(...)` 生效,走框架包动态依赖
|
||||
|
||||
## 可复现的工作流(已验证 2026-01-25)
|
||||
|
||||
目标:Settings 使用 sparse identity 启动,WinUI 资源/字符串正常加载。
|
||||
|
||||
### 1) 构建 Settings(framework-dependent + bootstrap no-op)
|
||||
|
||||
在 `PowerToys.Settings.csproj` 中添加(仅在 `UseSparseIdentity=true` 时生效):
|
||||
|
||||
```
|
||||
<PropertyGroup Condition="'$(UseSparseIdentity)'=='true'">
|
||||
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>
|
||||
<WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp>true</WindowsAppSDKBootstrapAutoInitializeOptions_OnPackageIdentity_NoOp>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
构建:
|
||||
|
||||
```
|
||||
MSBuild.exe src\settings-ui\Settings.UI\PowerToys.Settings.csproj /p:Platform=ARM64 /p:Configuration=Debug /p:UseSparseIdentity=true /m:1 /p:CL_MPCount=1 /nodeReuse:false
|
||||
```
|
||||
|
||||
### 2) 清理 ExternalLocation 的 app-local WinAppSDK 运行时
|
||||
|
||||
**必须移除** app-local WinAppSDK 运行时文件,否则会优先加载并崩溃(`CoreMessagingXP.dll` / `0xc0000602`)。
|
||||
|
||||
需清理的目录:
|
||||
- `ARM64\Debug`(ExternalLocation 根)
|
||||
- `ARM64\Debug\WinUI3Apps`
|
||||
|
||||
建议只移除 app-local WinAppSDK 相关文件(保留业务 DLL)。
|
||||
|
||||
**保留/放回 bootstrap DLL(必要):**
|
||||
- `Microsoft.WindowsAppRuntime.Bootstrap.dll`
|
||||
- `Microsoft.WindowsAppRuntime.Bootstrap.Net.dll`
|
||||
|
||||
### 3) 生成与包名一致的 resources.pri
|
||||
|
||||
关键点:resources.pri 的 **ResourceMap name 必须与包名一致**。
|
||||
|
||||
使用 `makepri.exe new` 生成,确保 `/mn` 指向 sparse 包的 `AppxManifest.xml`:
|
||||
|
||||
```
|
||||
makepri.exe new ^
|
||||
/pr C:\PowerToys\src\settings-ui\Settings.UI ^
|
||||
/cf C:\PowerToys\src\settings-ui\Settings.UI\obj\ARM64\Debug\priconfig.xml ^
|
||||
/mn C:\PowerToys\src\PackageIdentity\AppxManifest.xml ^
|
||||
/of C:\PowerToys\ARM64\Debug\resources.pri ^
|
||||
/o
|
||||
```
|
||||
|
||||
### 4) 将 resources.pri 打进 sparse MSIX
|
||||
|
||||
在 `BuildSparsePackage.ps1` 中把 `resources.pri` 放入 staging(脚本已更新):
|
||||
- 优先取 `ARM64\Debug\resources.pri`
|
||||
- 如果不存在则回退 `ARM64\Debug\WinUI3Apps\PowerToys.Settings.pri`
|
||||
|
||||
重新打包:
|
||||
|
||||
```
|
||||
.\src\PackageIdentity\BuildSparsePackage.ps1 -Platform ARM64 -Configuration Debug
|
||||
```
|
||||
|
||||
### 5) 重新注册 sparse 包(如需先卸载)
|
||||
|
||||
如果因为内容变更被阻止,先卸载再安装:
|
||||
|
||||
```
|
||||
Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage
|
||||
Add-AppxPackage -Path .\ARM64\Debug\PowerToysSparse.msix -ExternalLocation .\ARM64\Debug -ForceApplicationShutdown
|
||||
```
|
||||
|
||||
### 6) 启动 Settings(验证)
|
||||
|
||||
```
|
||||
Start-Process "shell:AppsFolder\Microsoft.PowerToys.SparseApp_djwsxzxb4ksa8!PowerToys.SettingsUI"
|
||||
```
|
||||
|
||||
验证要点:
|
||||
- Settings 正常启动,UI 文本显示
|
||||
- 日志正常写入:`%LOCALAPPDATA%\Microsoft\PowerToys\Settings\Logs\0.0.1.0\`
|
||||
|
||||
### 备注(可选)
|
||||
|
||||
如果出现 `ms-appx:///CommunityToolkit...` 资源缺失,可将对应的 `.pri`(从 NuGet 缓存)复制到 `ARM64\Debug\WinUI3Apps`,但在 **resources.pri 已正确打包** 后通常不再需要。
|
||||
|
||||
## 待确认事项
|
||||
|
||||
1. [ ] WinUI 3 + Sparse Package 的兼容性问题是否有官方文档说明?
|
||||
2. [ ] 是否有其他项目成功实现 WinUI 3 + Sparse Package?
|
||||
3. [ ] Windows App SDK GitHub 上是否有相关 issue?
|
||||
4. [ ] 修正依赖版本后,Settings 是否能在 sparse identity 下正常启动?
|
||||
5. [ ] framework-dependent(Bootstrap)方式是否能在 sparse identity 下启动?
|
||||
|
||||
## 相关文件
|
||||
|
||||
- [SemanticSearchIndex.cs](../../src/common/Common.Search/SemanticSearch/SemanticSearchIndex.cs) - Semantic Search 实现
|
||||
- [AppxManifest.xml](../../src/PackageIdentity/AppxManifest.xml) - Sparse package manifest
|
||||
- [BuildSparsePackage.ps1](../../src/PackageIdentity/BuildSparsePackage.ps1) - 构建脚本
|
||||
- [app.manifest](../../src/settings-ui/Settings.UI/app.manifest) - Settings 应用 manifest
|
||||
- [PowerToys.Settings.csproj](../../src/settings-ui/Settings.UI/PowerToys.Settings.csproj) - Settings 项目文件
|
||||
|
||||
## 参考链接
|
||||
|
||||
- [Grant package identity by packaging with external location](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
|
||||
- [Windows App SDK - Content Search API](https://learn.microsoft.com/en-us/windows/ai/apis/content-search)
|
||||
- [Windows App SDK GitHub Issues](https://github.com/microsoft/WindowsAppSDK/issues)
|
||||
BIN
doc/images/overview/CmdPal_Hero.gif
Normal file
BIN
doc/images/overview/CmdPal_Hero.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 256 KiB |
BIN
doc/images/runner/tray.png
Normal file
BIN
doc/images/runner/tray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
496
doc/specs/common-search-library.md
Normal file
496
doc/specs/common-search-library.md
Normal file
@@ -0,0 +1,496 @@
|
||||
# Common.Search Library Specification
|
||||
|
||||
## Overview
|
||||
|
||||
本文档描述 `Common.Search` 库的重构设计,目标是提供一个通用的、可插拔的搜索框架,支持多种搜索引擎实现(Fuzzy Match、Semantic Search 等)。
|
||||
|
||||
## Goals
|
||||
|
||||
1. **解耦** - 搜索引擎与数据源完全解耦
|
||||
2. **可插拔** - 支持替换不同的搜索引擎实现
|
||||
3. **泛型** - 不绑定特定业务类型(如 SettingEntry)
|
||||
4. **可组合** - 支持多引擎组合(即时 Fuzzy + 延迟 Semantic)
|
||||
5. **可复用** - 可被 PowerToys 多个模块使用
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Consumer (e.g., Settings.UI) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ SettingsDataProvider ← 业务特定的数据加载 │
|
||||
│ SettingsSearchService ← 业务特定的搜索服务 │
|
||||
│ SettingEntry : ISearchable ← 业务实体实现搜索契约 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│ uses
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Common.Search (Library) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Core Abstractions │ │
|
||||
│ ├─────────────────────────────────────────────────────────┤ │
|
||||
│ │ ISearchable ← 可搜索内容契约 │ │
|
||||
│ │ ISearchEngine<T> ← 搜索引擎接口 │ │
|
||||
│ │ SearchResult<T> ← 统一结果模型 │ │
|
||||
│ │ SearchOptions ← 搜索选项 │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Implementations │ │
|
||||
│ ├─────────────────────────────────────────────────────────┤ │
|
||||
│ │ FuzzSearch/ │ │
|
||||
│ │ ├── FuzzSearchEngine<T> ← 内存 Fuzzy 搜索 │ │
|
||||
│ │ ├── StringMatcher ← 现有的模糊匹配算法 │ │
|
||||
│ │ └── MatchResult ← Fuzzy 匹配结果 │ │
|
||||
│ │ │ │
|
||||
│ │ SemanticSearch/ │ │
|
||||
│ │ ├── SemanticSearchEngine ← Windows AI Search 封装 │ │
|
||||
│ │ └── SemanticSearchCapabilities │ │
|
||||
│ │ │ │
|
||||
│ │ CompositeSearch/ │ │
|
||||
│ │ └── CompositeSearchEngine<T> ← 多引擎组合 │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Core Interfaces
|
||||
|
||||
### ISearchable
|
||||
|
||||
定义可搜索内容的最小契约。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a searchable item that can be indexed and searched.
|
||||
/// </summary>
|
||||
public interface ISearchable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this item.
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the primary searchable text (e.g., title, header).
|
||||
/// </summary>
|
||||
string SearchableText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets optional secondary searchable text (e.g., description).
|
||||
/// Returns null if not available.
|
||||
/// </summary>
|
||||
string? SecondarySearchableText { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### ISearchEngine<T>
|
||||
|
||||
搜索引擎核心接口。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a pluggable search engine that can index and search items.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items to search, must implement ISearchable.</typeparam>
|
||||
public interface ISearchEngine<T> : IDisposable
|
||||
where T : ISearchable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine is ready to search.
|
||||
/// </summary>
|
||||
bool IsReady { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the engine capabilities.
|
||||
/// </summary>
|
||||
SearchEngineCapabilities Capabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the search engine.
|
||||
/// </summary>
|
||||
Task InitializeAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Indexes a single item.
|
||||
/// </summary>
|
||||
Task IndexAsync(T item, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Indexes multiple items in batch.
|
||||
/// </summary>
|
||||
Task IndexBatchAsync(IEnumerable<T> items, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the index by its ID.
|
||||
/// </summary>
|
||||
Task RemoveAsync(string id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Clears all indexed items.
|
||||
/// </summary>
|
||||
Task ClearAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Searches for items matching the query.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<SearchResult<T>>> SearchAsync(
|
||||
string query,
|
||||
SearchOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
```
|
||||
|
||||
### SearchResult<T>
|
||||
|
||||
统一的搜索结果模型。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a search result with the matched item and scoring information.
|
||||
/// </summary>
|
||||
public sealed class SearchResult<T>
|
||||
where T : ISearchable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the matched item.
|
||||
/// </summary>
|
||||
public required T Item { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relevance score (higher is more relevant).
|
||||
/// </summary>
|
||||
public required double Score { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of match that produced this result.
|
||||
/// </summary>
|
||||
public required SearchMatchKind MatchKind { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the match details for highlighting (optional).
|
||||
/// </summary>
|
||||
public IReadOnlyList<MatchSpan>? MatchSpans { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a span of matched text for highlighting.
|
||||
/// </summary>
|
||||
public readonly record struct MatchSpan(int Start, int Length);
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the kind of match that produced a search result.
|
||||
/// </summary>
|
||||
public enum SearchMatchKind
|
||||
{
|
||||
/// <summary>Exact text match.</summary>
|
||||
Exact,
|
||||
|
||||
/// <summary>Fuzzy/approximate text match.</summary>
|
||||
Fuzzy,
|
||||
|
||||
/// <summary>Semantic/AI-based match.</summary>
|
||||
Semantic,
|
||||
|
||||
/// <summary>Combined match from multiple engines.</summary>
|
||||
Composite,
|
||||
}
|
||||
```
|
||||
|
||||
### SearchOptions
|
||||
|
||||
搜索配置选项。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// Options for configuring search behavior.
|
||||
/// </summary>
|
||||
public sealed class SearchOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of results to return.
|
||||
/// Default is 20.
|
||||
/// </summary>
|
||||
public int MaxResults { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum score threshold (0.0 to 1.0).
|
||||
/// Results below this score are filtered out.
|
||||
/// Default is 0.0 (no filtering).
|
||||
/// </summary>
|
||||
public double MinScore { get; set; } = 0.0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language hint for the search (e.g., "en-US").
|
||||
/// </summary>
|
||||
public string? Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to include match spans for highlighting.
|
||||
/// Default is false.
|
||||
/// </summary>
|
||||
public bool IncludeMatchSpans { get; set; } = false;
|
||||
}
|
||||
```
|
||||
|
||||
### SearchEngineCapabilities
|
||||
|
||||
引擎能力描述。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the capabilities of a search engine.
|
||||
/// </summary>
|
||||
public sealed class SearchEngineCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine supports fuzzy matching.
|
||||
/// </summary>
|
||||
public bool SupportsFuzzyMatch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine supports semantic search.
|
||||
/// </summary>
|
||||
public bool SupportsSemanticSearch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine persists the index to disk.
|
||||
/// </summary>
|
||||
public bool PersistsIndex { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine supports incremental indexing.
|
||||
/// </summary>
|
||||
public bool SupportsIncrementalIndex { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the engine supports match span highlighting.
|
||||
/// </summary>
|
||||
public bool SupportsMatchSpans { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
## Implementations
|
||||
|
||||
### FuzzSearchEngine<T>
|
||||
|
||||
基于现有 StringMatcher 的内存搜索引擎。
|
||||
|
||||
**特点:**
|
||||
- 纯内存,无持久化
|
||||
- 即时响应(毫秒级)
|
||||
- 支持 match spans 高亮
|
||||
- 基于字符的模糊匹配
|
||||
|
||||
**Capabilities:**
|
||||
```csharp
|
||||
new SearchEngineCapabilities
|
||||
{
|
||||
SupportsFuzzyMatch = true,
|
||||
SupportsSemanticSearch = false,
|
||||
PersistsIndex = false,
|
||||
SupportsIncrementalIndex = true,
|
||||
SupportsMatchSpans = true,
|
||||
}
|
||||
```
|
||||
|
||||
### SemanticSearchEngine
|
||||
|
||||
基于 Windows App SDK AI Search API 的语义搜索引擎。
|
||||
|
||||
**特点:**
|
||||
- 系统管理的持久化索引
|
||||
- AI 驱动的语义理解
|
||||
- 需要模型初始化(可能较慢)
|
||||
- 可能不可用(依赖系统支持)
|
||||
|
||||
**Capabilities:**
|
||||
```csharp
|
||||
new SearchEngineCapabilities
|
||||
{
|
||||
SupportsFuzzyMatch = true, // API 同时提供 lexical + semantic
|
||||
SupportsSemanticSearch = true,
|
||||
PersistsIndex = true,
|
||||
SupportsIncrementalIndex = true,
|
||||
SupportsMatchSpans = false, // API 不返回详细位置
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** SemanticSearchEngine 不是泛型的,因为它需要将内容转换为字符串存入系统索引。实现时通过 `ISearchable` 接口提取文本。
|
||||
|
||||
### CompositeSearchEngine<T>
|
||||
|
||||
组合多个搜索引擎,支持 fallback 和结果合并。
|
||||
|
||||
```csharp
|
||||
namespace Common.Search;
|
||||
|
||||
/// <summary>
|
||||
/// A search engine that combines results from multiple engines.
|
||||
/// </summary>
|
||||
public sealed class CompositeSearchEngine<T> : ISearchEngine<T>
|
||||
where T : ISearchable
|
||||
{
|
||||
/// <summary>
|
||||
/// Strategy for combining results from multiple engines.
|
||||
/// </summary>
|
||||
public enum CombineStrategy
|
||||
{
|
||||
/// <summary>Use first ready engine only.</summary>
|
||||
FirstReady,
|
||||
|
||||
/// <summary>Merge results from all ready engines.</summary>
|
||||
MergeAll,
|
||||
|
||||
/// <summary>Use primary, fallback to secondary if primary not ready.</summary>
|
||||
PrimaryWithFallback,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**典型用法:** Fuzzy 作为即时响应,Semantic 准备好后增强结果。
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/common/Common.Search/
|
||||
├── Common.Search.csproj
|
||||
├── GlobalSuppressions.cs
|
||||
├── ISearchable.cs
|
||||
├── ISearchEngine.cs
|
||||
├── SearchResult.cs
|
||||
├── SearchOptions.cs
|
||||
├── SearchEngineCapabilities.cs
|
||||
├── SearchMatchKind.cs
|
||||
├── MatchSpan.cs
|
||||
│
|
||||
├── FuzzSearch/
|
||||
│ ├── FuzzSearchEngine.cs
|
||||
│ ├── StringMatcher.cs (existing)
|
||||
│ ├── MatchOption.cs (existing)
|
||||
│ ├── MatchResult.cs (existing)
|
||||
│ └── SearchPrecisionScore.cs (existing)
|
||||
│
|
||||
├── SemanticSearch/
|
||||
│ ├── SemanticSearchEngine.cs
|
||||
│ ├── SemanticSearchCapabilities.cs
|
||||
│ └── SemanticSearchAdapter.cs (adapts ISearchable to Windows API)
|
||||
│
|
||||
└── CompositeSearch/
|
||||
└── CompositeSearchEngine.cs
|
||||
```
|
||||
|
||||
## Consumer Usage (Settings.UI)
|
||||
|
||||
### SettingEntry 实现 ISearchable
|
||||
|
||||
```csharp
|
||||
// Settings.UI.Library/SettingEntry.cs
|
||||
public struct SettingEntry : ISearchable
|
||||
{
|
||||
// Existing properties...
|
||||
|
||||
// ISearchable implementation
|
||||
public string Id => ElementUid ?? $"{PageTypeName}|{ElementName}";
|
||||
public string SearchableText => Header ?? string.Empty;
|
||||
public string? SecondarySearchableText => Description;
|
||||
}
|
||||
```
|
||||
|
||||
### SettingsSearchService
|
||||
|
||||
```csharp
|
||||
// Settings.UI/Services/SettingsSearchService.cs
|
||||
public sealed class SettingsSearchService : IDisposable
|
||||
{
|
||||
private readonly ISearchEngine<SettingEntry> _engine;
|
||||
|
||||
public SettingsSearchService(ISearchEngine<SettingEntry> engine)
|
||||
{
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(IEnumerable<SettingEntry> entries)
|
||||
{
|
||||
await _engine.InitializeAsync();
|
||||
await _engine.IndexBatchAsync(entries);
|
||||
}
|
||||
|
||||
public async Task<List<SettingEntry>> SearchAsync(string query, CancellationToken ct = default)
|
||||
{
|
||||
var results = await _engine.SearchAsync(query, cancellationToken: ct);
|
||||
return results.Select(r => r.Item).ToList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Startup Configuration
|
||||
|
||||
```csharp
|
||||
// Option 1: Fuzzy only (default, immediate)
|
||||
var engine = new FuzzSearchEngine<SettingEntry>();
|
||||
|
||||
// Option 2: Semantic only (requires Windows AI)
|
||||
var engine = new SemanticSearchAdapter<SettingEntry>("PowerToysSettings");
|
||||
|
||||
// Option 3: Composite (best of both worlds)
|
||||
var engine = new CompositeSearchEngine<SettingEntry>(
|
||||
primary: new SemanticSearchAdapter<SettingEntry>("PowerToysSettings"),
|
||||
fallback: new FuzzSearchEngine<SettingEntry>(),
|
||||
strategy: CombineStrategy.PrimaryWithFallback
|
||||
);
|
||||
|
||||
var searchService = new SettingsSearchService(engine);
|
||||
await searchService.InitializeAsync(settingEntries);
|
||||
```
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Core Abstractions
|
||||
1. 创建 `ISearchable`, `ISearchEngine<T>`, `SearchResult<T>` 等核心接口
|
||||
2. 保持现有 FuzzSearch 代码不变
|
||||
|
||||
### Phase 2: FuzzSearchEngine<T>
|
||||
1. 创建泛型 `FuzzSearchEngine<T>` 实现
|
||||
2. 内部复用现有 `StringMatcher`
|
||||
|
||||
### Phase 3: SemanticSearchEngine
|
||||
1. 完善现有 `SemanticSearchEngine` 实现
|
||||
2. 创建 `SemanticSearchAdapter<T>` 桥接泛型接口
|
||||
|
||||
### Phase 4: Settings.UI Migration
|
||||
1. `SettingEntry` 实现 `ISearchable`
|
||||
2. 创建 `SettingsSearchService`
|
||||
3. 迁移 `SearchIndexService` 到新架构
|
||||
4. 保持 API 兼容,逐步废弃旧方法
|
||||
|
||||
### Phase 5: CompositeSearchEngine (Optional)
|
||||
1. 实现组合引擎
|
||||
2. 支持 Fuzzy + Semantic 混合搜索
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **是否需要支持图片搜索?** 当前 SemanticSearchEngine 支持 `IndexImage`,但 `ISearchable` 只有文本。如果需要图片,可能需要 `IImageSearchable` 扩展。
|
||||
|
||||
2. **结果去重策略?** CompositeEngine 合并结果时,同一个 Item 可能被多个引擎匹配,如何去重和合并分数?
|
||||
|
||||
3. **异步 vs 同步?** FuzzSearch 完全可以同步执行,但接口统一用 `Task` 是否合适?考虑提供同步重载?
|
||||
|
||||
4. **索引更新策略?** 当 Settings 内容变化时(例如用户切换语言),如何高效更新索引?
|
||||
|
||||
---
|
||||
|
||||
*Document Version: 1.0*
|
||||
*Last Updated: 2026-01-21*
|
||||
@@ -1119,35 +1119,6 @@ LExit:
|
||||
return WcaFinalize(er);
|
||||
}
|
||||
|
||||
UINT __stdcall RestoreBuiltInNewContextMenuCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
hr = WcaInitialize(hInstall, "RestoreBuiltInNewContextMenuCA");
|
||||
|
||||
constexpr wchar_t built_in_new_registry_path[] = LR"(Software\Classes\Directory\Background\ShellEx\ContextMenuHandlers\New)";
|
||||
|
||||
HKEY key{};
|
||||
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER,
|
||||
built_in_new_registry_path,
|
||||
0,
|
||||
KEY_ALL_ACCESS,
|
||||
&key) != ERROR_SUCCESS)
|
||||
{
|
||||
return WcaFinalize(ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
if (RegDeleteValueW(key, nullptr) != ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(key);
|
||||
return WcaFinalize(ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
RegCloseKey(key);
|
||||
|
||||
return WcaFinalize(ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
UINT __stdcall TelemetryLogInstallSuccessCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
@@ -1594,6 +1565,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
||||
L"PowerToys.PowerRename.exe",
|
||||
L"PowerToys.ImageResizer.exe",
|
||||
L"PowerToys.LightSwitchService.exe",
|
||||
L"PowerToys.PowerDisplay.exe",
|
||||
L"PowerToys.GcodeThumbnailProvider.exe",
|
||||
L"PowerToys.BgcodeThumbnailProvider.exe",
|
||||
L"PowerToys.PdfThumbnailProvider.exe",
|
||||
|
||||
@@ -7,7 +7,6 @@ EXPORTS
|
||||
ApplyModulesRegistryChangeSetsCA
|
||||
DetectPrevInstallPathCA
|
||||
RemoveScheduledTasksCA
|
||||
RestoreBuiltInNewContextMenuCA
|
||||
TelemetryLogInstallSuccessCA
|
||||
TelemetryLogInstallCancelCA
|
||||
TelemetryLogInstallFailCA
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props" Condition="Exists('..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props')" />
|
||||
<Import Project="..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props" Condition="Exists('..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props')" />
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\wix.props" Condition="Exists('..\wix.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{B3A354B0-1E54-4B55-A962-FB5AF9330C19}</ProjectGuid>
|
||||
@@ -88,7 +88,7 @@
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>inc;..\..\src\;..\..\src\common\Telemetry;telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions>/await /Zc:twoPhase- /Wv:18 %(AdditionalOptions)</AdditionalOptions>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
@@ -165,14 +165,14 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.DUtil.5.0.2\build\WixToolset.DUtil.props'))" />
|
||||
<Error Condition="!Exists('..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WixToolset.WcaUtil.5.0.2\build\WixToolset.WcaUtil.props'))" />
|
||||
</Target>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="WixToolset.DUtil" version="5.0.2" targetFramework="native" />
|
||||
<package id="WixToolset.WcaUtil" version="5.0.2" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -22,16 +22,6 @@
|
||||
|
||||
<ComponentGroup Id="DscResourcesComponentGroup">
|
||||
<ComponentRef Id="PowerToysDSCReference" />
|
||||
<?if $(var.PerUser) = "false" ?>
|
||||
<Component Id="SecureDSCModulesFolder" Guid="7D2F4E57-CCB2-4F89-9B8B-62E9B3CC4E12" Directory="DSCModulesReferenceFolder" Bitness="always64">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="SecureDSCModulesFolder" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<CreateFolder>
|
||||
<PermissionEx Sddl="D:PAI(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GRGX;;;BU)(A;OICIIO;GA;;;CO)" />
|
||||
</CreateFolder>
|
||||
</Component>
|
||||
<?endif?>
|
||||
<Component Id="RemoveDSCModulesFolder" Guid="A3C77D92-4E97-4C1A-9F2E-8B3C5D6E7F80" Directory="DSCModulesReferenceFolder">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveDSCModulesFolder" Value="" KeyPath="yes" />
|
||||
|
||||
@@ -2,29 +2,7 @@
|
||||
|
||||
<?include $(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<?define KeyboardManagerAssetsFiles=?>
|
||||
<?define KeyboardManagerAssetsWinUI3Files=?>
|
||||
<?define KeyboardManagerAssetsFilesPath=$(var.BinDir)\Assets\KeyboardManager\?>
|
||||
<?define KeyboardManagerAssetsWinUI3FilesPath=$(var.BinDir)\WinUI3Apps\Assets\KeyboardManagerEditor\?>
|
||||
|
||||
<Fragment>
|
||||
<DirectoryRef Id="BaseApplicationsAssetsFolder">
|
||||
<Directory Id="KeyboardManagerAssetsInstallFolder" Name="KeyboardManager" />
|
||||
</DirectoryRef>
|
||||
<DirectoryRef Id="WinUI3AppsAssetsFolder">
|
||||
<Directory Id="KeyboardManagerAssetsWinUI3InstallFolder" Name="KeyboardManagerEditor" />
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="KeyboardManagerAssetsInstallFolder" FileSource="$(var.KeyboardManagerAssetsFilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--KeyboardManagerAssetsFiles_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="KeyboardManagerAssetsWinUI3InstallFolder" FileSource="$(var.KeyboardManagerAssetsWinUI3FilesPath)">
|
||||
<!-- Generated by generateFileComponents.ps1 -->
|
||||
<!--KeyboardManagerAssetsWinUI3Files_Component_Def-->
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Directory Id="KeyboardManagerEditorInstallFolder" Name="KeyboardManagerEditor" />
|
||||
<Directory Id="KeyboardManagerEngineInstallFolder" Name="KeyboardManagerEngine" />
|
||||
@@ -66,8 +44,6 @@
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="RemoveKeyboardManagerFolder" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<RemoveFolder Id="RemoveFolderKeyboardManagerAssetsInstallFolder" Directory="KeyboardManagerAssetsInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderKeyboardManagerAssetsWinUI3InstallFolder" Directory="KeyboardManagerAssetsWinUI3InstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderKeyboardManagerEditorFolder" Directory="KeyboardManagerEditorInstallFolder" On="uninstall" />
|
||||
<RemoveFolder Id="RemoveFolderKeyboardManagerEngineFolder" Directory="KeyboardManagerEngineInstallFolder" On="uninstall" />
|
||||
</Component>
|
||||
|
||||
@@ -47,6 +47,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
call move /Y ..\..\..\NewPlus.wxs.bk ..\..\..\NewPlus.wxs
|
||||
call move /Y ..\..\..\Peek.wxs.bk ..\..\..\Peek.wxs
|
||||
call move /Y ..\..\..\PowerRename.wxs.bk ..\..\..\PowerRename.wxs
|
||||
call move /Y ..\..\..\PowerDisplay.wxs.bk ..\..\..\PowerDisplay.wxs
|
||||
call move /Y ..\..\..\Product.wxs.bk ..\..\..\Product.wxs
|
||||
call move /Y ..\..\..\RegistryPreview.wxs.bk ..\..\..\RegistryPreview.wxs
|
||||
call move /Y ..\..\..\Resources.wxs.bk ..\..\..\Resources.wxs
|
||||
@@ -123,6 +124,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
<Compile Include="KeyboardManager.wxs" />
|
||||
<Compile Include="Peek.wxs" />
|
||||
<Compile Include="PowerRename.wxs" />
|
||||
<Compile Include="PowerDisplay.wxs" />
|
||||
<Compile Include="DscResources.wxs" />
|
||||
<Compile Include="RegistryPreview.wxs" />
|
||||
<Compile Include="Run.wxs" />
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<ComponentGroupRef Id="LightSwitchComponentGroup" />
|
||||
<ComponentGroupRef Id="PeekComponentGroup" />
|
||||
<ComponentGroupRef Id="PowerRenameComponentGroup" />
|
||||
<ComponentGroupRef Id="PowerDisplayComponentGroup" />
|
||||
<ComponentGroupRef Id="RegistryPreviewComponentGroup" />
|
||||
<ComponentGroupRef Id="RunComponentGroup" />
|
||||
<ComponentGroupRef Id="SettingsComponentGroup" />
|
||||
@@ -161,9 +162,6 @@
|
||||
<!-- Clean Video Conference Mute registry keys that might be around from previous installations. We've deprecated this utility since then. -->
|
||||
<Custom Action="CleanVideoConferenceRegistry" Before="InstallFinalize" Condition="NOT Installed" />
|
||||
|
||||
<!-- Restore built-in "New" context menu in case user disabled it via New+ -->
|
||||
<Custom Action="RestoreBuiltInNewContextMenu" Before="RemoveFiles" Condition="Installed AND (REMOVE="ALL")" />
|
||||
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<CustomAction Id="SetLaunchPowerToysParam" Property="LaunchPowerToys" Value="[INSTALLFOLDER]" />
|
||||
@@ -265,8 +263,6 @@
|
||||
|
||||
<CustomAction Id="SetBundleInstallLocation" Return="ignore" Impersonate="no" Execute="deferred" DllEntry="SetBundleInstallLocationCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<CustomAction Id="RestoreBuiltInNewContextMenu" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="RestoreBuiltInNewContextMenuCA" BinaryRef="PTCustomActions" />
|
||||
|
||||
<!-- Close 'PowerToys.exe' before uninstall-->
|
||||
<Property Id="MSIRESTARTMANAGERCONTROL" Value="DisableShutdown" />
|
||||
<Property Id="MSIFASTINSTALL" Value="DisableShutdown" />
|
||||
|
||||
@@ -172,16 +172,14 @@ Generate-FileComponents -fileListName "HostsAssetsFiles" -wxsFilePath $PSScriptR
|
||||
Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFilePath $PSScriptRoot\ImageResizer.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ImageResizer"
|
||||
Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs
|
||||
|
||||
#KeyboardManager
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\KeyboardManager"
|
||||
Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsWinUI3Files -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManagerEditor"
|
||||
Generate-FileComponents -fileListName "KeyboardManagerAssetsFiles" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
|
||||
Generate-FileComponents -fileListName "KeyboardManagerAssetsWinUI3Files" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs
|
||||
|
||||
# Light Switch Service
|
||||
Generate-FileList -fileDepsJson "" -fileListName LightSwitchFiles -wxsFilePath $PSScriptRoot\LightSwitch.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\LightSwitchService"
|
||||
Generate-FileComponents -fileListName "LightSwitchFiles" -wxsFilePath $PSScriptRoot\LightSwitch.wxs
|
||||
|
||||
#PowerDisplay
|
||||
Generate-FileList -fileDepsJson "" -fileListName PowerDisplayAssetsFiles -wxsFilePath $PSScriptRoot\PowerDisplay.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\PowerDisplay"
|
||||
Generate-FileComponents -fileListName "PowerDisplayAssetsFiles" -wxsFilePath $PSScriptRoot\PowerDisplay.wxs
|
||||
|
||||
#New+
|
||||
Generate-FileList -fileDepsJson "" -fileListName NewPlusAssetsFiles -wxsFilePath $PSScriptRoot\NewPlus.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\NewPlus"
|
||||
Generate-FileComponents -fileListName "NewPlusAssetsFiles" -wxsFilePath $PSScriptRoot\NewPlus.wxs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h actionRunner.base.rc actionRunner.rc" />
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h actionRunner.base.rc actionRunner.rc" />
|
||||
</Target>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
@@ -11,10 +10,11 @@
|
||||
<RootNamespace>actionRunner</RootNamespace>
|
||||
<ProjectName>PowerToys.ActionRunner</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)deps\expected.props" />
|
||||
<Import Project="..\..\deps\expected.props" />
|
||||
<PropertyGroup>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
@@ -59,17 +59,17 @@
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
<Import Project="..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -4,9 +4,8 @@
|
||||
<Import Project=".\Common.Dotnet.PrepareGeneratedFolder.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<CoreTargetFramework>net9.0</CoreTargetFramework>
|
||||
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
|
||||
<TargetFramework>$(CoreTargetFramework)-windows10.0.26100.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
|
||||
@@ -7,13 +7,4 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
In CI, the main build runs `/t:Build;Test` across the full solution.
|
||||
Fuzz test projects are built for OneFuzz ingestion, but should not be
|
||||
executed as regular MSTest tests in this pass.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(TF_BUILD)' != ''">
|
||||
<TestingPlatformDisableCustomTestTarget>true</TestingPlatformDisableCustomTestTarget>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19000.0" MaxVersionTested="10.0.26226.0" />
|
||||
<PackageDependency Name="Microsoft.WindowsAppRuntime.2.0-experimental4" MinVersion="0.738.2207.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
@@ -80,6 +81,7 @@
|
||||
<Extensions>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
|
||||
<com:ExeServer Executable="Microsoft.CmdPal.Ext.PowerToys.exe" Arguments="-RegisterProcessAsComServer" DisplayName="PowerToys Command Palette Extension">
|
||||
<com:Class Id="7EC02C7D-8F98-4A2E-9F23-B58C2C2F2B17" DisplayName="PowerToys Command Palette Extension" />
|
||||
</com:ExeServer>
|
||||
@@ -105,5 +107,15 @@
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
<Application Id="PowerToys.SettingsSearchEvaluation" Executable="SettingsSearchEvaluation.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
DisplayName="PowerToys.SettingsSearchEvaluation"
|
||||
Description="PowerToys Settings search evaluator"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Images\Square150x150Logo.png"
|
||||
Square44x44Logo="Images\Square44x44Logo.png"
|
||||
AppListEntry="none">
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
|
||||
@@ -320,6 +320,19 @@ try {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Include resources.pri in the sparse MSIX if available to enable MRT in packaged mode.
|
||||
# Prefer a prebuilt resources.pri in output root; fall back to Settings pri.
|
||||
$resourcesPriSource = Join-Path $outDir "resources.pri"
|
||||
if (-not (Test-Path $resourcesPriSource)) {
|
||||
$resourcesPriSource = Join-Path $outDir "WinUI3Apps\\PowerToys.Settings.pri"
|
||||
}
|
||||
if (Test-Path $resourcesPriSource) {
|
||||
Copy-Item -Path $resourcesPriSource -Destination (Join-Path $stagingDir "resources.pri") -Force -ErrorAction SilentlyContinue
|
||||
Write-BuildLog "Including resources.pri from: $resourcesPriSource" -Level Info
|
||||
} else {
|
||||
Write-BuildLog "resources.pri not found; strings may be missing in sparse identity." -Level Warning
|
||||
}
|
||||
|
||||
# Ensure publisher matches the dev certificate for local builds
|
||||
$manifestStagingPath = Join-Path $stagingDir 'AppxManifest.xml'
|
||||
|
||||
@@ -117,4 +117,4 @@
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -57,7 +57,7 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
|
||||
auto state = UpdateState::read();
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
const auto new_version_info = 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 = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
|
||||
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
|
||||
if (!downloaded_installer)
|
||||
{
|
||||
Logger::error("Couldn't download new installer");
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h PowerToys.Update.base.rc PowerToys.Update.rc" />
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h PowerToys.Update.base.rc PowerToys.Update.rc" />
|
||||
</Target>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
@@ -11,10 +10,11 @@
|
||||
<RootNamespace>Update</RootNamespace>
|
||||
<ProjectName>PowerToys.Update</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)deps\expected.props" />
|
||||
<Import Project="..\..\deps\expected.props" />
|
||||
<PropertyGroup>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
@@ -65,17 +65,17 @@
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(RepoRoot)deps\spdlog.props" />
|
||||
<Import Project="..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -22,13 +22,13 @@ using System.Diagnostics.CodeAnalysis;
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.SpecialRules", "SA0001:XmlCommentAnalysisDisabled", Justification = "Not enabled as we don't want or need XML documentation.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:DocumentationTextMustEndWithAPeriod", Justification = "Not enabled as we don't want or need XML documentation.")]
|
||||
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action<object, SyncStatusEventArgs> does not allow the required notation")]
|
||||
[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Core.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action<object, SyncStatusEventArgs> does not allow the required notation")]
|
||||
|
||||
// Non general suppressions
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is part of the markdown processing", MessageId = "System.Windows.Documents.Run.#ctor(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.Markdown.#ImageInlineEvaluator(System.Text.RegularExpressions.Match)")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1<Microsoft.Templates.Composition.QueryNode>,Microsoft.Templates.Composition.QueryablePropertyDictionary)")]
|
||||
[assembly: SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Resource DictionaryWriter does not implement flush async", Scope = "member", Target = "~M:Microsoft.Templates.PostActions.Catalog.Merge.MergeResourceDictionaryPostAction.ExecuteInternalAsync~System.Threading.Tasks.Task")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer specification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1<Microsoft.Templates.Core.Composition.QueryNode>,Microsoft.Templates.Core.Composition.QueryablePropertyDictionary)")]
|
||||
[assembly: SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Resource DictionaryWriter does not implement flush async", Scope = "member", Target = "~M:Microsoft.Templates.Core.PostActions.Catalog.Merge.MergeResourceDictionaryPostAction.ExecuteInternalAsync~System.Threading.Tasks.Task")]
|
||||
[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Used in a lot of places for meaningful method names")]
|
||||
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Static methods may improve performance but decrease maintainability")]
|
||||
[assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Renaming everything would be a lot of work. It does not do any harm if an EventHandler delegate ends with the suffix EventHandler. Besides this, the Rule causes some false positives.")]
|
||||
@@ -43,10 +43,10 @@ using System.Diagnostics.CodeAnalysis;
|
||||
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel.OnDelete(Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel)")]
|
||||
|
||||
// Localization suppressions
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Locations.JunctionNativeMethods.#CreateJunction(System.String,System.String,System.Boolean)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Locations.JunctionNativeMethods.#DeleteJunction(System.String)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Locations.JunctionNativeMethods.#InternalGetTarget(Microsoft.Win32.SafeHandles.SafeFileHandle)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Locations.JunctionNativeMethods.#OpenReparsePoint(System.String,Microsoft.Templates.Locations.JunctionNativeMethods+EFileAccess)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#CreateJunction(System.String,System.String,System.Boolean)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#DeleteJunction(System.String)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#InternalGetTarget(Microsoft.Win32.SafeHandles.SafeFileHandle)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#OpenReparsePoint(System.String,Microsoft.Templates.Core.Locations.JunctionNativeMethods+EFileAccess)", Justification = "Only used for local generation")]
|
||||
[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Documents.InlineCollection.Add(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Extensions.TextBlockExtensions.#OnSequentialFlowStepChanged(System.Windows.DependencyObject,System.Windows.DependencyPropertyChangedEventArgs)", Justification = "No text here")]
|
||||
[assembly: SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "The user's search term should be compared with culture based rules.", Scope = "type", Target = "~T:Microsoft.PowerToys.Run.Plugin.TimeDate.Components.SearchController")]
|
||||
|
||||
|
||||
27
src/common/AllExperiments/AllExperiments.csproj
Normal file
27
src/common/AllExperiments/AllExperiments.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetName>PowerToys.AllExperiments</TargetName>
|
||||
<MockDirectory>.\Microsoft.VariantAssignment\</MockDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Experimentation is live, forcing inclusion -->
|
||||
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
|
||||
<!-- Newtonsoft.Json is included and a version specified in Directory.Packages.props to avoid a vulnerability from older versions. -->
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Microsoft.VariantAssignment.Client" />
|
||||
<PackageReference Include="Microsoft.VariantAssignment.Contract" />
|
||||
<Compile Remove=".\$(MockDirectory)\Client\*.cs" />
|
||||
<Compile Remove=".\$(MockDirectory)\Contract\*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
214
src/common/AllExperiments/Experiments.cs
Normal file
214
src/common/AllExperiments/Experiments.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.VariantAssignment.Client;
|
||||
using Microsoft.VariantAssignment.Contract;
|
||||
using Windows.System.Profile;
|
||||
|
||||
namespace AllExperiments
|
||||
{
|
||||
// The dependencies required to build this project are only available in the official build pipeline and are internal to Microsoft.
|
||||
// However, this project is not required to build a test version of the application.
|
||||
public class Experiments
|
||||
{
|
||||
public enum ExperimentState
|
||||
{
|
||||
Enabled,
|
||||
Disabled,
|
||||
NotLoaded,
|
||||
}
|
||||
|
||||
#pragma warning disable SA1401 // Need to use LandingPageExperiment as a static property in OobeShellPage.xaml.cs
|
||||
#pragma warning disable CA2211 // Non-constant fields should not be visible
|
||||
public static ExperimentState LandingPageExperiment = ExperimentState.NotLoaded;
|
||||
#pragma warning restore CA2211
|
||||
#pragma warning restore SA1401
|
||||
|
||||
public async Task<bool> EnableLandingPageExperimentAsync()
|
||||
{
|
||||
if (Experiments.LandingPageExperiment != ExperimentState.NotLoaded)
|
||||
{
|
||||
return Experiments.LandingPageExperiment == ExperimentState.Enabled;
|
||||
}
|
||||
|
||||
Experiments varServ = new Experiments();
|
||||
await varServ.VariantAssignmentProvider_Initialize();
|
||||
var landingPageExperiment = varServ.IsExperiment;
|
||||
|
||||
Experiments.LandingPageExperiment = landingPageExperiment ? ExperimentState.Enabled : ExperimentState.Disabled;
|
||||
|
||||
return landingPageExperiment;
|
||||
}
|
||||
|
||||
private async Task VariantAssignmentProvider_Initialize()
|
||||
{
|
||||
IsExperiment = false;
|
||||
string jsonFilePath = CreateFilePath();
|
||||
|
||||
var vaSettings = new VariantAssignmentClientSettings
|
||||
{
|
||||
Endpoint = new Uri("https://default.exp-tas.com/exptas77/a7a397e7-6fbe-4f21-a4e9-3f542e4b000e-exppowertoys/api/v1/tas"),
|
||||
EnableCaching = true,
|
||||
ResponseCacheTime = TimeSpan.FromMinutes(5),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var vaClient = vaSettings.GetTreatmentAssignmentServiceClient();
|
||||
var vaRequest = GetVariantAssignmentRequest();
|
||||
using var variantAssignments = await vaClient.GetVariantAssignmentsAsync(vaRequest).ConfigureAwait(false);
|
||||
|
||||
if (variantAssignments.AssignedVariants.Count != 0)
|
||||
{
|
||||
var dataVersion = variantAssignments.DataVersion;
|
||||
var featureVariables = variantAssignments.GetFeatureVariables();
|
||||
var assignmentContext = variantAssignments.GetAssignmentContext();
|
||||
var featureFlagValue = featureVariables[0].GetStringValue();
|
||||
|
||||
var experimentGroup = string.Empty;
|
||||
string json = File.ReadAllText(jsonFilePath);
|
||||
var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
|
||||
|
||||
if (jsonDictionary != null)
|
||||
{
|
||||
if (!jsonDictionary.TryGetValue("dataversion", out object? value))
|
||||
{
|
||||
value = dataVersion;
|
||||
jsonDictionary.Add("dataversion", value);
|
||||
}
|
||||
|
||||
if (!jsonDictionary.ContainsKey("variantassignment"))
|
||||
{
|
||||
jsonDictionary.Add("variantassignment", featureFlagValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonDataVersion = value.ToString();
|
||||
if (jsonDataVersion != null && int.Parse(jsonDataVersion, CultureInfo.InvariantCulture) < dataVersion)
|
||||
{
|
||||
jsonDictionary["dataversion"] = dataVersion;
|
||||
jsonDictionary["variantassignment"] = featureFlagValue;
|
||||
}
|
||||
}
|
||||
|
||||
experimentGroup = jsonDictionary["variantassignment"].ToString();
|
||||
|
||||
string output = JsonSerializer.Serialize(jsonDictionary);
|
||||
File.WriteAllText(jsonFilePath, output);
|
||||
}
|
||||
|
||||
if (experimentGroup == "alternate" && AssignmentUnit != string.Empty)
|
||||
{
|
||||
IsExperiment = true;
|
||||
}
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new OobeVariantAssignmentEvent() { AssignmentContext = assignmentContext, ClientID = AssignmentUnit });
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
string json = File.ReadAllText(jsonFilePath);
|
||||
var jsonDictionary = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
|
||||
|
||||
if (jsonDictionary != null)
|
||||
{
|
||||
if (jsonDictionary.TryGetValue("variantassignment", out object? value))
|
||||
{
|
||||
if (value.ToString() == "alternate" && AssignmentUnit != string.Empty)
|
||||
{
|
||||
IsExperiment = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonDictionary["variantassignment"] = "current";
|
||||
}
|
||||
}
|
||||
|
||||
string output = JsonSerializer.Serialize(jsonDictionary);
|
||||
File.WriteAllText(jsonFilePath, output);
|
||||
|
||||
Logger.LogError("Error getting to TAS endpoint", ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error getting variant assignments for experiment", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExperiment { get; set; }
|
||||
|
||||
private string? AssignmentUnit { get; set; }
|
||||
|
||||
private VariantAssignmentRequest GetVariantAssignmentRequest()
|
||||
{
|
||||
var jsonFilePath = CreateFilePath();
|
||||
try
|
||||
{
|
||||
if (!File.Exists(jsonFilePath))
|
||||
{
|
||||
AssignmentUnit = Guid.NewGuid().ToString();
|
||||
var data = new Dictionary<string, string>()
|
||||
{
|
||||
["clientid"] = AssignmentUnit,
|
||||
};
|
||||
string jsonData = JsonSerializer.Serialize(data);
|
||||
File.WriteAllText(jsonFilePath, jsonData);
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = File.ReadAllText(jsonFilePath);
|
||||
var jsonDictionary = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(json);
|
||||
if (jsonDictionary != null)
|
||||
{
|
||||
AssignmentUnit = jsonDictionary["clientid"]?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error creating/getting AssignmentUnit", ex);
|
||||
}
|
||||
|
||||
var attrNames = new List<string> { "FlightRing", "c:InstallLanguage" };
|
||||
var attrData = AnalyticsInfo.GetSystemPropertiesAsync(attrNames).AsTask().GetAwaiter().GetResult();
|
||||
|
||||
var flightRing = string.Empty;
|
||||
var installLanguage = string.Empty;
|
||||
|
||||
if (attrData.ContainsKey("FlightRing"))
|
||||
{
|
||||
flightRing = attrData["FlightRing"];
|
||||
}
|
||||
|
||||
if (attrData.ContainsKey("InstallLanguage"))
|
||||
{
|
||||
installLanguage = attrData["InstallLanguage"];
|
||||
}
|
||||
|
||||
return new VariantAssignmentRequest
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
{ "installLanguage", installLanguage },
|
||||
{ "flightRing", flightRing },
|
||||
{ "clientid", AssignmentUnit },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private string CreateFilePath()
|
||||
{
|
||||
var exeDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var settingsPath = @"Microsoft\PowerToys\experimentation.json";
|
||||
var filePath = Path.Combine(exeDir, settingsPath);
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/common/AllExperiments/Logger.cs
Normal file
81
src/common/AllExperiments/Logger.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace AllExperiments
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||
private static readonly IPath Path = FileSystem.Path;
|
||||
private static readonly IDirectory Directory = FileSystem.Directory;
|
||||
|
||||
private static readonly string ApplicationLogPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\Settings Logs\\Experimentation");
|
||||
|
||||
static Logger()
|
||||
{
|
||||
if (!Directory.Exists(ApplicationLogPath))
|
||||
{
|
||||
Directory.CreateDirectory(ApplicationLogPath);
|
||||
}
|
||||
|
||||
// Using InvariantCulture since this is used for a log file name
|
||||
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, "INFO");
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, "ERROR");
|
||||
#if DEBUG
|
||||
Debugger.Break();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception e)
|
||||
{
|
||||
Log(
|
||||
message + Environment.NewLine +
|
||||
e?.Message + Environment.NewLine +
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
e?.InnerException?.Message + Environment.NewLine +
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
e?.StackTrace,
|
||||
"ERROR");
|
||||
#if DEBUG
|
||||
Debugger.Break();
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||
Trace.Indent();
|
||||
Trace.WriteLine(GetCallerInfo());
|
||||
Trace.WriteLine(message);
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
StackTrace stackTrace = new StackTrace();
|
||||
|
||||
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||
var className = methodName?.DeclaringType?.Name;
|
||||
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VariantAssignment.Contract;
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Client
|
||||
{
|
||||
#pragma warning disable SA1200 // Using directives should be placed correctly
|
||||
using TreatmentAssignmentServiceClient = VariantAssignmentServiceClient<TreatmentAssignmentServiceResponse>;
|
||||
#pragma warning restore SA1200 // Using directives should be placed correctly
|
||||
|
||||
public static class VariantAssignmentClientExtensionMethods
|
||||
{
|
||||
public static IVariantAssignmentProvider GetTreatmentAssignmentServiceClient(this VariantAssignmentClientSettings settings)
|
||||
{
|
||||
return new TreatmentAssignmentServiceClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.VariantAssignment.Contract;
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Client
|
||||
{
|
||||
internal sealed partial class VariantAssignmentServiceClient<TServerResponse> : IVariantAssignmentProvider, IDisposable
|
||||
where TServerResponse : VariantAssignmentServiceResponse
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
return Task.FromResult(EmptyVariantAssignmentResponse.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public class EmptyVariantAssignmentResponse : IVariantAssignmentResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton instance of <see cref="EmptyVariantAssignmentResponse"/>.
|
||||
/// </summary>
|
||||
public static readonly IVariantAssignmentResponse Instance = new EmptyVariantAssignmentResponse();
|
||||
|
||||
public EmptyVariantAssignmentResponse()
|
||||
{
|
||||
}
|
||||
|
||||
public long DataVersion => 0;
|
||||
|
||||
public string Thumbprint => string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<IAssignedVariant> AssignedVariants => Array.Empty<IAssignedVariant>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path) => null;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix) => Array.Empty<IFeatureVariable>();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
string IVariantAssignmentResponse.GetAssignmentContext()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IReadOnlyList<IFeatureVariable> IVariantAssignmentResponse.GetFeatureVariables()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,10 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace KeyboardManagerEditorUI.Helpers
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public enum KeyInputMode
|
||||
public interface IAssignedVariant
|
||||
{
|
||||
OriginalKeys,
|
||||
RemappedKeys,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public interface IFeatureVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the variable's value as a string.
|
||||
/// </summary>
|
||||
/// <returns>String value of the variable.</returns>
|
||||
string GetStringValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public interface IVariantAssignmentProvider : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes variant assignments based on <paramref name="request"/> data.
|
||||
/// </summary>
|
||||
/// <param name="request">Variant assignment parameters.</param>
|
||||
/// <param name="ct">Propagates notification that operations should be canceled.</param>
|
||||
/// <returns>An awaitable task that returns a <see cref="IVariantAssignmentResponse"/>.</returns>
|
||||
Task<IVariantAssignmentResponse> GetVariantAssignmentsAsync(IVariantAssignmentRequest request, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public interface IVariantAssignmentRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets inputs used for evaluating filters, assignment units, etc.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<(string Key, string Value)> Parameters { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
/// <summary>
|
||||
/// Snapshot of variant assignments.
|
||||
/// </summary>
|
||||
public interface IVariantAssignmentResponse : IDisposable
|
||||
{
|
||||
///// <summary>
|
||||
///// Gets the serial number of variant assignment configuration snapshot used when assigning variants.
|
||||
///// </summary>
|
||||
long DataVersion { get; }
|
||||
|
||||
///// <summary>
|
||||
///// Get a hash of the response suitable for caching.
|
||||
///// </summary>
|
||||
// string Thumbprint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the variants assigned based on request parameters and a variant configuration snapshot.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets feature variables assigned by variants in this response.
|
||||
/// </summary>
|
||||
/// <param name="prefix">(Optional) Filter feature variables where <see cref="IFeatureVariable.KeySegments"/> contains the <paramref name="prefix"/>.</param>
|
||||
/// <returns>Range of matching feature variables.</returns>
|
||||
IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix);
|
||||
|
||||
// this actually part of the interface but gets the job done
|
||||
IReadOnlyList<IFeatureVariable> GetFeatureVariables();
|
||||
|
||||
// this actually part of the interface but gets the job done
|
||||
string GetAssignmentContext();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single feature variable assigned by variants in this response.
|
||||
/// </summary>
|
||||
/// <param name="path">Exact feature variable path.</param>
|
||||
/// <returns>Matching feature variable or null.</returns>
|
||||
IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
internal sealed class TreatmentAssignmentServiceResponse : VariantAssignmentServiceResponse
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for variant assignment service client.
|
||||
/// </summary>
|
||||
public class VariantAssignmentClientSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the variant assignment service endpoint URL.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Uri? Endpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether gets or sets a value whether client side request caching should be enabled.
|
||||
/// </summary>
|
||||
public bool EnableCaching { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum time a cached variant assignment response may be used without re-validating.
|
||||
/// </summary>
|
||||
public TimeSpan ResponseCacheTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Specialized;
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
public class VariantAssignmentRequest : IVariantAssignmentRequest
|
||||
{
|
||||
private NameValueCollection _parameters = new NameValueCollection();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets mutable <see cref="IVariantAssignmentRequest.Parameters"/>.
|
||||
/// </summary>
|
||||
public NameValueCollection Parameters { get => _parameters; set => _parameters = value; }
|
||||
|
||||
IReadOnlyCollection<(string Key, string Value)> IVariantAssignmentRequest.Parameters => (IReadOnlyCollection<(string Key, string Value)>)_parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// The goal of this class is to just mock out the Microsoft.VariantAssignment close source objects
|
||||
namespace Microsoft.VariantAssignment.Contract
|
||||
{
|
||||
/// <summary>
|
||||
/// Mutable implementation of <see cref="IVariantAssignmentResponse"/> for (de)serialization.
|
||||
/// </summary>
|
||||
internal class VariantAssignmentServiceResponse : IVariantAssignmentResponse, IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual long DataVersion { get; set; }
|
||||
|
||||
public virtual IReadOnlyCollection<IAssignedVariant> AssignedVariants { get; set; } = Array.Empty<IAssignedVariant>();
|
||||
|
||||
public IFeatureVariable GetFeatureVariable(IReadOnlyList<string> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IReadOnlyList<IFeatureVariable> GetFeatureVariables(IReadOnlyList<string> prefix)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IFeatureVariable> GetFeatureVariables()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetAssignmentContext()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<AdditionalIncludeDirectories>$(RepoRoot)src\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
@@ -36,12 +36,12 @@
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user