Compare commits

..

134 Commits

Author SHA1 Message Date
Niels Laute
8fcad1f491 Fix dev setup .vsconfig component IDs for Visual Studio 2026
The winget configuration files now provision Visual Studio 2026 (channel 18.Release), but .vsconfig still listed Visual Studio 2022-era component IDs that were renamed or removed in VS 2026. The VSComponents DSC resource silently skipped them, so the Windows 11 SDK (26100) and WindowsAppSDK C# support never installed, breaking the build (cppwinrt / WindowsAppSDK failures).

- Windows10SDK.22621/26100 -> Windows11SDK.22621/26100 (renamed in VS 2026)
- ComponentGroup.WindowsAppSDK.Cs -> WindowsAppSdkSupport.CSharp; added WindowsAppSdkSupport.Cpp
- Removed Windows10SDK.19041 (only a min-version floor) and Windows10SDK.20348 (unused, removed from VS 2026)
- readme: correct '22621' SDK label from Windows 10 to Windows 11

Fixes #48778

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 13:08:22 +02:00
Copilot
5f1b496bf2 Shortcut Guide: Rename Win+Q to "Open Click to Do" for Copilot+ PCs (#48439)
Win+Q and Win+S were both labeled "Open search" in Shortcut Guide. On
Copilot+ PCs, Win+Q launches Click to Do — not Search — so the duplicate
label was misleading.

## Changes

- `+WindowsNT.Shell.en-US.yml`: Renamed Win+Q entry from `Open search` →
`Open Click to Do` with description `On Copilot+ PCs`; Win+S retains
`Open search`

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

The Windows key shortcuts manifest had two entries with identical names
(`Open search`) mapped to Win+Q and Win+S respectively. On Copilot+ PCs,
Win+Q opens Click to Do, not Search. The Win+Q entry is corrected to
`Open Click to Do` with a `On Copilot+ PCs` description to scope its
applicability.

## Validation Steps Performed

- Visually reviewed the YAML diff to confirm only the Win+Q entry name
and description changed; Win+S entry is unchanged.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-24 10:44:07 +00:00
Ingo Kodba
a2218b3c3b [KeyboardManager] Fix sticky Ctrl caused by stale AltGr flag (#46672)
## Summary

Fixes #46693

- The `static bool isAltRightKeyInvoked` flag in
`HandleShortcutRemapEvent` is set when AltGr (RAlt+LCtrl) is pressed,
but only reset inside Case 1's else branch — which requires a shortcut
to be actively invoked (`isShortcutInvoked == true`)
- If the user presses and releases AltGr when **no shortcut is active**,
the flag stays `true` permanently across all future calls
- Once stale, the flag blocks modifier key restoration and state resets
at 13 locations throughout the function (lines 646, 670, 791, 800, 866,
880, 889, 907, 927, 978, 996), and causes LCtrl key-up events to `break`
out of the handler loop (line 620-622) instead of being properly
processed
- This makes LCtrl permanently stuck for any shortcut remap that uses
LCtrl as a modifier

## Fix

Reset `isAltRightKeyInvoked` when `VK_RMENU` (Right Alt) is released,
regardless of whether a shortcut is currently invoked. This is added as
an `else if` right after the existing flag-set condition.

## Test plan

- [ ] Configure a shortcut remap using LCtrl (e.g. LCtrl+Y → Backspace)
- [ ] Press and release AltGr without triggering any shortcut
- [ ] Use the LCtrl shortcut — verify Ctrl does not become stuck
- [ ] Verify AltGr-based shortcuts still work correctly
- [ ] Verify normal AltGr character input (e.g. AltGr+Q for @ on German
layout) is unaffected

---------

Co-authored-by: fluffyspace <fluffyspace@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-24 10:42:54 +00:00
Clint Rutkas
47335149f3 [Settings] Extract HandleNavigationFailure and test all null permutations (#48410)
## Summary

Follow-up to #48409. That PR rewrote
`ShellViewModel.Frame_NavigationFailed` to set `e.Handled = true` and
log instead of re-throwing, but the unit tests it added only covered the
`GetPageDisplayName` formatting helper. The actual contract that matters
- "this handler must never throw, regardless of which fields on
`NavigationFailedEventArgs` happen to be null" - was not directly
testable because `NavigationFailedEventArgs` is a sealed WinRT type that
cannot be constructed from MSTest.

## Change

Tiny refactor: split the failure-handling logic out of
`Frame_NavigationFailed` into a pure static
`HandleNavigationFailure(Type sourcePageType, Exception exception)`. The
WinUI-shaped handler now just sets `Handled = true` and delegates.

This makes the "must not throw" invariant testable in isolation - no
WinUI Frame, no Microsoft.UI.Xaml.Navigation types needed.

## Tests

Added four new cases under `ShellViewModelTests`, exercising all four
`(SourcePageType, Exception)` null permutations:

| `sourcePageType` | `exception` |
| - | - |
| null | null |
| typeof(...) | null |
| null | new Exception(...) |
| typeof(...) | new Exception(...) |

Each test simply calls the helper and relies on MSTest's default "if it
throws, the test fails" behavior. Any future change that re-introduces
an unguarded dereference of `e.SourcePageType` or `e.Exception` will
turn the corresponding test red.

## Validation

All six tests pass:

```
Passed GetPageDisplayName_ReturnsFullName_ForKnownType
Passed GetPageDisplayName_ReturnsPlaceholder_ForNullType
Passed HandleNavigationFailure_DoesNotThrow_ForNullInputs
Passed HandleNavigationFailure_DoesNotThrow_ForNullException
Passed HandleNavigationFailure_DoesNotThrow_ForNullPageType
Passed HandleNavigationFailure_DoesNotThrow_ForBothInputsPresent
Total tests: 6, Passed: 6
```

`PowerToys.Settings.csproj` and `Settings.UI.UnitTests.csproj` both
build clean (Release|x64).

## Note

This PR is stacked on top of #48409 (same branch lineage). If #48409 is
merged first this PR will rebase cleanly to a small diff on
`ShellViewModel.cs` + the additional test cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 10:02:08 +02:00
dependabot[bot]
5c63486dcb build(deps): bump actions/checkout from 6 to 7 (#48743)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to
7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>block checking out fork pr for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
<li>getting ready for checkout v7 release by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2464">actions/checkout#2464</a></li>
<li>update error wording by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2467">actions/checkout#2467</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.3...v7.0.0">https://github.com/actions/checkout/compare/v6.0.3...v7.0.0</a></p>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
<h2>v6.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Add orchestration_id to git user-agent when ACTIONS_ORCHESTRATION_ID
is set by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2355">actions/checkout#2355</a></li>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6.0.1...v6.0.2">https://github.com/actions/checkout/compare/v6.0.1...v6.0.2</a></p>
<h2>v6.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update all references from v5 and v4 to v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2314">actions/checkout#2314</a></li>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
<li>Clarify v6 README by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2328">actions/checkout#2328</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v6...v6.0.1">https://github.com/actions/checkout/compare/v6...v6.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the
minor-actions-dependencies group across 1 directory by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2458">actions/checkout#2458</a></li>
<li>Bump flatted from 3.3.1 to 3.4.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2460">actions/checkout#2460</a></li>
<li>Bump js-yaml from 4.1.0 to 4.2.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2461">actions/checkout#2461</a></li>
<li>Bump <code>@​actions/core</code> and
<code>@​actions/tool-cache</code> and Remove uuid by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2459">actions/checkout#2459</a></li>
<li>upgrade module to esm and update dependencies by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2463">actions/checkout#2463</a></li>
<li>Bump the minor-npm-dependencies group across 1 directory with 3
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/checkout/pull/2462">actions/checkout#2462</a></li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9c091bb21b"><code>9c091bb</code></a>
update error wording (<a
href="https://redirect.github.com/actions/checkout/issues/2467">#2467</a>)</li>
<li><a
href="1044a6dea9"><code>1044a6d</code></a>
getting ready for checkout v7 release (<a
href="https://redirect.github.com/actions/checkout/issues/2464">#2464</a>)</li>
<li><a
href="f0282184c7"><code>f028218</code></a>
Bump the minor-npm-dependencies group across 1 directory with 3 updates
(<a
href="https://redirect.github.com/actions/checkout/issues/2462">#2462</a>)</li>
<li><a
href="d914b262ff"><code>d914b26</code></a>
upgrade module to esm and update dependencies (<a
href="https://redirect.github.com/actions/checkout/issues/2463">#2463</a>)</li>
<li><a
href="537c7ef99c"><code>537c7ef</code></a>
Bump <code>@​actions/core</code> and <code>@​actions/tool-cache</code>
and Remove uuid (<a
href="https://redirect.github.com/actions/checkout/issues/2459">#2459</a>)</li>
<li><a
href="130a169078"><code>130a169</code></a>
Bump js-yaml from 4.1.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2461">#2461</a>)</li>
<li><a
href="7d09575332"><code>7d09575</code></a>
Bump flatted from 3.3.1 to 3.4.2 (<a
href="https://redirect.github.com/actions/checkout/issues/2460">#2460</a>)</li>
<li><a
href="0f9f3aa320"><code>0f9f3aa</code></a>
Bump actions/publish-immutable-action (<a
href="https://redirect.github.com/actions/checkout/issues/2458">#2458</a>)</li>
<li><a
href="f9e715a95f"><code>f9e715a</code></a>
block checking out fork pr for pull_request_target and workflow_run (<a
href="https://redirect.github.com/actions/checkout/issues/2454">#2454</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v6...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 09:51:55 +02:00
Gordon Lam
bf6dbd8865 feat(Advanced Paste): Add setting to show/hide AI paste section (#45242)
## Summary of the Pull Request

Adds a new "Show AI paste section" setting to Advanced Paste that allows
users to hide the AI paste input box from the Advanced Paste window.
This addresses user requests for a cleaner UI when the AI paste feature
is not needed or desired.

## PR Checklist

- [x] Closes: #32967
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

## Detailed Description of the Pull Request / Additional comments

This PR implements a new boolean setting `ShowAIPaste` that controls the
visibility of the AI paste input box (PromptBox) in the Advanced Paste
window.

### Changes Made

**Settings UI Library:**
- `src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs` -
Added `ShowAIPaste` property with JSON serialization

**Settings UI:**
- `src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs` -
Added view model property with change notification
-
`src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml`
- Added checkbox setting in the UI Behavior section
- `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw` - Added
localized strings for header and description

**Advanced Paste Module:**
- `src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs` -
Added interface property
- `src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs` -
Implemented setting loading from properties
-
`src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs`
- Added `ShowAIPasteSection` property combining user setting with GPO
policy
-
`src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml`
- Bound PromptBox visibility to new property

**Tests:**
-
`src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs`
- Added mock property
-
`src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json`
- Updated test settings file

### Behavior
- Default value is `true` (AI paste section visible) to maintain
backward compatibility
- The setting respects GPO policy - if AI is disabled by GPO, the
section will be hidden regardless of user preference
- Setting is persisted in the Advanced Paste settings JSON file

## Validation Steps Performed

- [x] Verified setting appears in Settings UI under Advanced Paste →
UI Behavior
- [x] Verified toggling the setting hides/shows the AI paste input box
in the Advanced Paste window
- [x] Verified setting persists after closing and reopening Settings
- [x] Verified default value is `true` (visible) for new installations
- [x] Verified existing unit tests pass with updated mock

Fixes #32967

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-06-24 15:43:23 +08:00
Deryl Fabiensyah
3c942ae356 Quick Accent: Add ¡ and ¿ to ! and ? quick accent menu (#20618) (#48599)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR addresses issue #20618 by adding the inverted exclamation mark
(**¡**) to the **!** key (`VK_1`) and the inverted question mark (**¿**)
to the **?** key (`VK_SLASH_`) for both Spanish (`SP`) and Catalan
(`CA`) character mappings in Quick Accent.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

Adds the inverted punctuation marks `¡` (inverted exclamation mark) and
`¿` (inverted question mark) to the character mappings of Spanish (`SP`)
and Catalan (`CA`) languages in `CharacterMappings.cs`.
- `LetterKey.VK_1` (`!`) now maps to `¡`
- `LetterKey.VK_SLASH_` (`?`) now maps to `¿`

This enables quick accent popups when pressing these respective keys,
aligning with the expected behavior for these languages.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

1. Verified character mapping changes build successfully.
2. Verified that the PowerToys CI pipeline checks completed
successfully.
2026-06-24 12:07:10 +08:00
Mario Hewardt
be4c3a1afa ZoomIt: add WebP/JPG screenshot encoder (#48818)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Add webp and jpg support for screenshots.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-23 15:48:34 -07:00
Clint Rutkas
968a7ac4b6 [Peek] Stop fail-fast in AppWindow.Closing path; reset cached preview-handler factories on release (#48564)
## Summary

Harden Peek's `AppWindow.Closing` path so a stale cached preview-handler
factory can't fail-fast the Peek process. Also clean up the matching
path in RegistryPreview.

## Background

Spotted while reading through Peek's `MainWindow` teardown sequence and
the `ShellPreviewHandlerPreviewer` cache for an unrelated review of how
Peek manages out-of-process preview-handler lifetimes.

The Peek `MainWindow` subscribes to `AppWindow.Closing`. The handler
doesn't actually close the window — it sets `args.Cancel = true` and
calls `Uninitialize()`, which in turn calls
`ShellPreviewHandlerPreviewer.ReleaseHandlerFactories()`.

`ReleaseHandlerFactories()` looked like this:

```csharp
public static void ReleaseHandlerFactories()
{
    foreach (var factory in HandlerFactories.Values)
    {
        try { Marshal.FinalReleaseComObject(factory); } catch { }
    }
}
```

Two problems:

1. The static `HandlerFactories` dictionary is never cleared. After
`FinalReleaseComObject`, the entries still point at separated RCWs. A
subsequent activation that races with this cleanup (or a second close in
the same process) can pick up the dead RCW from the cache.
2. The cached factory had `LockServer(true)` called on it when it was
first cached, but the matching `LockServer(false)` was never paired.

Any managed exception that escapes a WinRT event callback is projected
back to CFlat as a failed HRESULT and the CsWinRT dispatcher fail-fasts
the process. So a single `InvalidComObjectException` (HRESULT
0x80131527) thrown out of `Uninitialize()` is enough to terminate Peek.

## Changes

* **`ShellPreviewHandlerPreviewer.ReleaseHandlerFactories`** — snapshot
then clear the dictionary up front so that a subsequent call (or a
concurrent `LoadPreviewAsync`) can't pick up a stale RCW. Call
`LockServer(false)` before `FinalReleaseComObject` to mirror the
cache-time `LockServer(true)`. Both COM calls remain individually
wrapped because the RCW may already be unreachable during process
teardown.
* **`Peek.UI/MainWindow.xaml.cs` — `AppWindow_Closing`** — wrap the body
in try/catch + `Logger.LogError`. Any future exception in
`Uninitialize()` (or its callees) will now log instead of fail-fasting
the process.
* **`RegistryPreview/MainWindow.Events.cs` — `AppWindow_Closing`** —
same defensive try/catch, plus null-guard `jsonWindowPlacement` before
`SetNamedValue`. The placement dictionary can legitimately be null on
first run or after a corrupt placement file; previously that would NRE →
fail-fast.

## Risk

Low. The `ReleaseHandlerFactories` change matches the documented
`LockServer`/`FinalReleaseComObject` pairing and only widens the
lifetime window of the cache by `Clear()`-ing earlier; nothing in Peek
calls this method outside of teardown. The two try/catch wrappers
strictly add defense — the success path is unchanged.

## Validation

Spot-built locally; this repo's `dotnet restore` runtime-pack issue
(unrelated to this PR — same NU1102 pattern that's affecting other open
PRs) prevents a full `Build.cmd` here. The C++ side of Peek is
untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---

ADO:
https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/58765809/

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Boliang Zhang <122517415+LegendaryBlair@users.noreply.github.com>
2026-06-23 21:32:02 +08:00
Clint Rutkas
dd26d86580 [FancyZones] Fix stuck drag state and swallowed keys when a window is destroyed mid-drag (#48569)
## Summary
Fixes a class of "stuck drag" bugs in FancyZones where closing or
destroying a window **while it is being dragged** leaves FancyZones in a
half-dragging state — zone overlays stay on screen and subsequent
keystrokes (notably number keys) are swallowed or misrouted.

## What this changes
- **Subscribe to and dispatch `EVENT_OBJECT_DESTROY`.** `FancyZonesApp`
never subscribed to the destroy event, and the consumer's
`WM_PRIV_WINDOWDESTROYED` branch could therefore never fire. The event
is now registered and routed through `HandleWinHookEvent`.
- **Abort the drag (without snapping) when the dragged window is
destroyed.** On `WM_PRIV_WINDOWDESTROYED`, if the destroyed HWND is the
one being dragged, call the new `WindowMouseSnap::Abort()` (tears down
overlays/highlights/transparency) instead of `MoveSizeEnd()`, which
would try to snap the now-dead HWND and corrupt zone state. Dragging
state is then disabled.
- **Always clear dragging state in `MoveSizeEnd()`**, even when the
snapper was already null, so the state can't get stranded.
- **Require Win+Ctrl+Alt to switch layouts while dragging.** Previously
any digit switched layouts while `dragging` was true; if drag state was
stuck this "stole" number keys from the focused app. This is the
root-cause fix for the number-key-stealing symptom.
- **Only swallow the bare Shift key during a drag**, not `Shift+<other>`
combos, so real keystrokes are no longer eaten by an in-progress drag.

## Testing
- Builds Release x64 (FancyZones) clean against current `main`.
- Manually verified drag → close window mid-drag no longer leaves
overlays up or steals number keys. (FancyZones has no unit-test harness
for this path.)

This is one of a small set of related "stuck key / stuck state"
hardening fixes; each stands alone.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Muyuan Li <116717757+MuyuanMS@users.noreply.github.com>
2026-06-23 15:09:26 +02:00
Clint Rutkas
4771f15b6c [Runner] Harden centralized keyboard hook against stuck/ghost keys (#48570)
## Summary
Hardens the runner's centralized keyboard hook against stuck and "ghost"
key activations — cases where a hotkey action fires after the key was
already released, or a pending timer fires after the hook was torn down.

## What this changes
- **`vkCodePressed` is now `std::atomic<DWORD>`.** It is read/written
from the low-level hook callback and from the timer/teardown paths; the
plain `DWORD` was a data race.
- **Revalidate the key is still physically held before firing a held-key
timer.** The pressed-key timer callback now checks
`GetAsyncKeyState(virtualKey) & 0x8000` before invoking the action,
preventing ghost activations after the user has let go.
- **Tag the injected dummy `0xFF` key-up** with
`CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG` so the hook does not
reprocess its own synthetic event.
- **`Stop()` kills all pending pressed-key timers and resets
`vkCodePressed` before unhooking**, so a timer can't fire a callback
into a half-removed hook.

## Testing
- Builds Release x64 (runner / `PowerToys.exe`) clean against current
`main`.

This is one of a small set of related "stuck key" hardening fixes; each
stands alone.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 13:54:19 +02:00
Copilot
ed93fb585a Fix ZoomIt module description to eliminate Chinese translation repetition bug (#47370)
The ZoomIt module description contained `"snip screenshots"` — both
words map to the same Chinese term (截图), causing the translation system
to emit the garbled `"截图截图截图"` in the Chinese UI.

## Changes

- **`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`**:
Changed `"to snip screenshots to the clipboard or to a file"` → `"to
copy screenshots to the clipboard or to a file"` in both
`ZoomIt.ModuleDescription` and `Oobe_ZoomIt.Description`
- `copy` has an unambiguous, distinct Chinese translation (`复制`) with no
overlap with `截图`
- Consistent with the existing `ZoomIt_SnipGroup.Description` which
already uses `"Copy a selected area of the screen to the clipboard or to
a file"`

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

Before (zh-CN rendered):
> 还可以使用 Zoomlt 将屏幕**截图截图截图**剪贴板或文件中

After (expected zh-CN):
> 还可以使用 ZoomIt 将截图**复制到**剪贴板或文件中

Root cause: `snip` (Windows snipping action) and `screenshots` are both
translated to `截图` by the localization pipeline, producing triple
repetition. Replacing `snip` with `copy` — already used in the adjacent
Snip group description — resolves the collision without changing the
functional meaning.

## Validation Steps Performed

- Verified both string keys (`ZoomIt.ModuleDescription`,
`Oobe_ZoomIt.Description`) updated in `en-us/Resources.resw`
- Confirmed no other occurrences of `"snip screenshots"` remain in the
file

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-06-23 10:00:27 +00:00
Niels Laute
c3bec3935a Docs cleanup (#48813)
## Summary

Removes outdated/unused documentation and asset files:

- **`doc/unofficialInstallMethods.md`** — removed.
- **`doc/planning/`** — removed entire folder (`awake.md`,
`FancyZonesBacklog.md`, `PowerToysBacklog.md`,
`ShortcutGuideBacklog.md`).
- **`doc/images/icons/Video Conference Mute.png`** — removed (unused
module icon, not referenced anywhere in the repo).

Verified there are no remaining references to any of the removed files
across tracked sources (checked literal and `%20`-encoded paths).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 09:19:27 +00:00
Gordon Lam
334d1c8054 Validate cached installer filename from UpdateState.json in the updater (#48741)
## Summary of the Pull Request

`PowerToys.Update` reads `downloadedInstallerFilename` from the
persisted `UpdateState.json` and combines it with the `Updates` folder
to locate the installer to run. If that cached state is stale,
corrupted, or otherwise unexpected, the stored value could contain path
separators or an absolute path, which would make the updater look for
the installer outside the `Updates` folder.

This PR validates that the cached value is a plain filename and that the
resolved path stays inside the `Updates` folder before using it;
otherwise the update is treated as unavailable. The normal update flow
(a bare asset filename produced by the download step) is unaffected.

## PR Checklist

- [ ] **Communication:** I've discussed this with core contributors
already.
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** No end-user-facing strings added
- [x] **Dev docs:** N/A
- [x] **New binaries:** None

## Detailed Description of the Pull Request / Additional comments

- The filename check is factored into an inline helper
`updating::IsSafeDownloadedInstallerFilename` in
`src/common/updating/updateLifecycle.h` (next to the other inline update
helpers) so it can be unit-tested without a project reference.
- `ObtainInstaller` in `src/Update/PowerToys.Update.cpp` now calls that
helper and additionally confirms, via `weakly_canonical`, that the
resolved installer path's parent is the `Updates` directory.
- No behavior change for normal installer filenames, so the regular
update flow does not regress.

## Validation Steps Performed

- Added `IsSafeDownloadedInstallerFilenameTests` to `Updating.UnitTests`
covering normal filenames, empty values, parent-directory components,
nested path components, and absolute/drive-relative/UNC paths.
- Built `Updating.UnitTests` (Debug x64): 0 warnings, 0 errors.
- Ran the full `Updating.UnitTests` suite: 36/36 passed (30 existing + 6
new).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 15:01:25 +08:00
Niels Laute
5c16c97db5 [Settings] Fix FancyZones page scroll bounce at Excluded apps (#47937)
## Summary

Fixes #25353 — the FancyZones Settings page bounces / jumps when the
user scrolls down to the bottom (the **Excluded apps** expander).
Several closed dupes report the same behavior: #31500, #34259, #30164,
#45173, #32099, #32361.

## Root cause

The Excluded-apps `TextBox` (lines 283-305 of `FancyZonesPage.xaml`)
forced its own inner `ScrollViewer` to be permanently enabled with a
visible scrollbar, nested inside the page-level `ScrollViewer` provided
by `SettingsPageControl`:

```xml
ScrollViewer.IsVerticalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
```

The expander is `IsExpanded="True"` and sits at the very bottom of the
page, so when you wheel-scroll down the page the pointer ends up over
the TextBox. The TextBox''s inner scroller intercepts the wheel events;
when it has nothing left to scroll the event bubbles back to the outer
page scroller, which tries to over-scroll past content end. The result
is the bounce/jump visible in the videos on the issue.

The unbounded `MinHeight="160"` (with no `MaxHeight`) made it worse,
because the box can grow as users type, triggering layout re-measures
during scrolling.

## Fix

Minimal change to the same TextBox:

- Drop `ScrollViewer.IsVerticalRailEnabled` and
`ScrollViewer.VerticalScrollMode` — `AcceptsReturn="True"` already wires
up the inner scroller correctly.
- Change `ScrollViewer.VerticalScrollBarVisibility` from `Visible` to
`Auto`, so the inner scroller only competes with the outer one when
there is actual overflow to scroll.
- Add `MaxHeight="320"` so the box cannot keep growing as the user
types.

## Validation

- Builds clean (`tools\build\build.cmd` from
`src\settings-ui\Settings.UI`, arm64 Debug, exit 0, 0 errors).
- The same nested-scroller pattern exists on 9 other Settings pages
(AlwaysOnTop, GrabAndMove, MouseUtils, MouseWithoutBorders, PowerAccent,
PowerLauncher, ShortcutGuide). Keeping this PR atomic to the page called
out in #25353; happy to follow up with a sibling PR for those if
reviewers want them in the same pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 14:35:13 +08:00
Boliang Zhang
a255ece641 [File Locksmith] Fix crash (0xc000027b) when a listed process's image file no longer exists (#48719)
## Summary of the Pull Request

`PidToIconConverter.Convert` called `Icon.ExtractAssociatedIcon(path)`
without a guard. When a running process's image path is non-empty but
the file no longer exists on disk, the call throws
`FileNotFoundException`. The converter runs per-row while the process
`ListView` virtualizes, so the exception reached
`App_UnhandledException` (which only logs and never sets `e.Handled =
true`) and WinUI 3 fast-failed the whole app (`0xc000027b`).

This is routinely triggered by self-updating software that deletes its
old versioned directory while the old process keeps running — e.g.
Windows Defender (`SenseAPZ`, `MsMpEng`, `MpDefenderCoreService` under
versioned `...\Platform\<ver>\` / `...\DataCollection\<ver>\` paths).
Right-clicking a drive root enumerates every process and reliably
includes such a stale-path process, so the crash is easy to hit in
normal use.

The fix wraps the icon extraction in a try/catch and falls through to
the existing placeholder `BitmapImage`, logging a warning — mirroring
the exception handling already present in `MainViewModel.WatchProcess`.

## PR Checklist

- [x] Closes: #48693
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass — no existing test harness
covers this WinUI converter path; validated manually (steps below)
- [x] **Localization:** All end-user-facing strings can be localized —
n/a; the only new string is a developer log warning, not end-user UI
- [x] **Dev docs:** Added/updated — n/a
- [x] **New binaries:** Added on the required places — n/a; no new
binaries

## Detailed Description of the Pull Request / Additional comments


`src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToIconConverter.cs`:

```csharp
if (!string.IsNullOrEmpty(y))
{
    try
    {
        icon = Icon.ExtractAssociatedIcon(y);
    }
    catch (Exception ex)
    {
        // The process image path can be non-empty but no longer exist on disk
        // (e.g. self-updating software that deletes its old versioned directory while
        // the old process is still running). ExtractAssociatedIcon then throws and,
        // because this converter runs per-row during ListView virtualization, the
        // exception would otherwise reach App_UnhandledException and fast-fail the app.
        // Fall through to the placeholder icon instead of crashing.
        Logger.LogWarning($"Couldn't extract the icon for '{y}'. {ex}");
    }
}
```

(+ `using ManagedCommon;` — already a project reference; `Logger` is
used identically in `App.xaml.cs`.)

Scope note: this keeps to the targeted converter guard. I deliberately
did **not** also blanket-set `e.Handled = true` in
`App_UnhandledException`, since that would mask unrelated genuine
crashes; the converter guard fully addresses this crash.

## Validation Steps Performed

1. Reproduced on the installed 0.100 build: right-click `C:\` → **Unlock
with File Locksmith** → scroll the list → crash (`0xc000027b`, faulting
module `Microsoft.UI.Xaml.dll`; the FileLocksmith log shows an unhandled
`FileNotFoundException` for `...\SenseAPZ.exe` in
`MainPage...ProcessBindings` / `PidToIconConverter`). Reproduced twice.
2. Built `FileLocksmithUI` (x64/Release) with the fix — 0 warnings / 0
errors.
3. Ran the produced exe on `C:\` and scrolled ~40× over the same list
(which includes the stale `SenseAPZ` row):
   - No crash; the process stayed alive and responsive.
- The log shows the new warning firing for the exact `...\SenseAPZ.exe`
path, confirming the catch branch executed on the previously-fatal row.
   - 0 unhandled exceptions in the session.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 14:33:06 +08:00
Clint Rutkas
106c970c8c Fix full rebuild: upgrade CalculatorEngineCommon to C++20 coroutines (#48790)
Remove _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS and upgrade
LanguageStandard from stdcpp17 to stdcpp20 so that <coroutine> is used
instead of the removed <experimental/coroutine> header on VS 2026.

<img width="946" height="348" alt="image"
src="https://github.com/user-attachments/assets/53392fd8-1a81-4852-9913-d84575f2f3e1"
/>

was part of https://github.com/microsoft/PowerToys/pull/48102 prior

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 00:16:59 +01:00
Mike Griese
ff8c1dbf85 Dock: Add a teachingtip to docks while pinning (#48726)
When you're pinning something to the dock with the dialog, I have _no_
idea what display is 1,2,3,4. That's meaningless to me.

This attempts to resolve that by adding a teachingtip to each dock while
pinning. When we open that pin to dock dialog, we'll display the
teachingtips, and dismiss them when the dialog is closed.

That way folks have at least some indication of where each display is
when pinning.
2026-06-22 16:54:55 -05:00
Michael Jolley
cd7465e22a CmdPal: Refactor TopLevelCommandManager to use IExtensionService implementations (#48417)
## Summary

The `TopLevelCommandManager` handles **alot.** And by "alot" I mean too
much. In preparation for future types of extensions, this PR attempts to
pull out the methods for loading/managing extensions to individual
`IExtensionService`s.

These `ExtensionService`s will be injected the `TopLevelCommandManager`
via dependency injection. It will iterate through them asking them to
load their extensions. During that process, they will surface
`ICommandProvider`s back to the `TopLevelCommandManager` for display in
Command Palette.

Currently, there are two types of `IExtensionService`s:
`BuiltInExtensionService` and `WinRTExtensionService`.

- The `BuiltInExtensionService` receives all `ICommandProvider`s
registered with DI.
- The `WinRTExtensionService` manages out-of-process WinRT AppExtension
providers.

## Additional Changes

- **Circular DI dependency resolved**: `BuiltInsCommandProvider` no
longer depends on `IRootPageService`.
Previously, it required the `IRootPageService` solely for the dock
command to open Command Palette (using the RootPage as its command.)
Now, it uses a new `GoHomeDockCommand` which returns
`CommandResult.GoHome()` with the existing `onBeforeShowConfirmation`
callback to show the palette at the dock button position.
  
 Also, fixes #48643

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 16:51:12 -05:00
Niels Laute
502dc40aa4 [Shortcut Guide] Prevent overlay crash on section navigation (#48448) (#48481)
## Summary of the Pull Request

Shortcut Guide overlay crashes and closes when navigating between
sidebar sections (e.g. clicking PowerToys, then clicking the Windows
icon to come back). Repro and crash logs in #48448. Crash logs in #48441
show the same propagation path plus a follow-up access violation in
coreclr, indicating exceptions that escape local catches.

This PR fixes the immediate navigation race and adds broader crash
hardening so future exceptions on the UI/background threads are logged
instead of tearing down the overlay.

### Navigation-race fix (commit 1)

Root cause: `WindowSelector_SelectionChanged` calls
`App.TaskBarWindow.Activate()` and then `SetWindowPosition()`
synchronously. `Activate()` runs a reentrant `Window_Activated` →
`BringToFront` → `TaskbarWindow.Activated` chain that can leave
`App.TaskBarWindow.AppWindow` momentarily null, so `SetWindowPosition`
throws `NullReferenceException`.

Because the initial `SelectedItem = MenuItems[0]` is set from
`SetNavItems`, the NRE bubbles up into `InitializeNavItemsAsync`'s catch
block — which sets `_closeType = "InitializationFailed"` and closes the
window. That matches both user-visible symptoms: the overlay "flashes
and disappears" (#48441) on open and "closes when clicking the Windows
icon" (#48448).

Edits in
`src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs`:

- **`SetWindowPosition`**: null-guard `App.TaskBarWindow?.AppWindow` and
skip the taskbar-overlap height adjustment when it is not currently
observable. Wrap the body in `try`/`catch` with `Logger.LogError` so any
future positioning hiccup keeps the previous layout instead of taking
down the overlay.
- **`WindowSelector_SelectionChanged`**: null-guard
`App.TaskBarWindow?.Hide()` / `Activate()` and wrap the body in
`try`/`catch`. This breaks the propagation path that lets a navigation
exception reach `InitializeNavItemsAsync`'s "fatal init failure" branch.

### Crash hardening (commit 2)

Additional defensive changes to cover other unguarded paths surfaced
while reviewing #48441:

- **`App.xaml.cs`**: register `App.UnhandledException`,
`AppDomain.CurrentDomain.UnhandledException`, and
`TaskScheduler.UnobservedTaskException` so a stray exception (e.g. an IO
failure during a fire-and-forget UI handler, or a background `Task`
fault) gets logged instead of tearing the process down with an access
violation in coreclr. Mirrors what Peek, AdvancedPaste, and CmdPal
already do.
- **`App.OnLaunched`**: wrap the launch sequence in `try`/`catch` and
exit cleanly with an error log on failure (`LoadData` / `MainWindow` /
`TaskbarWindow` ctors and `Activate` are all reachable failure points).
- **`App.LoadData`**: broaden the `Pinned.json` deserialize catch to
also handle `IOException` / `UnauthorizedAccessException`, and guard the
round-trip `SaveSettings` call as best-effort with a warning log.
- **`PinnedShortcutsHelper.Save`**: catch `IOException` /
`UnauthorizedAccessException` / `JsonException` and log; `Pin` / `Unpin`
runs from a synchronous UI handler so an unguarded `File.WriteAllText`
would tear down the overlay on any disk hiccup.
- **`TaskbarWindow.UpdateTasklistButtons`**: move the `AppWindow.Move`
calls inside the existing `try` block, null-guard
`App.MainWindow?.AppWindow` up front, and wrap the whole body so the
method (which runs from the ctor and from `Activated`) cannot tear the
overlay down when `MainWindow` is in a transient state.

## PR Checklist

- [x] Closes: #48448
- [x] Likely also fixes: #48441
- [x] **Communication:** Defensive fix to known crash paths; no API
change.
- [ ] **Tests:** No automated tests added — the failures are reentrancy
/ timing races that are hard to deterministically trigger in CI.
Validated with a synthetic repro (see below).
- [x] **Localization:** N/A — only logger strings.
- [x] **Dev docs:** N/A
- [x] **New binaries:** N/A

## Detailed Description of the Pull Request / Additional comments

The fix is intentionally defensive (rather than restructuring the
reentrant activation flow) because the legacy taskbar UIA enumeration
(`TasklistPositions.GetButtons`) on Windows 10 is what most reliably
widens the race window and is out of scope to redesign for a hotfix.

## Validation Steps Performed

- Build: `tools\build\build.cmd` from
`src\modules\ShortcutGuide\ShortcutGuide.Ui` — exit 0, errors log empty
for both commits.
- Synthetic repro: temporarily injected `throw new
NullReferenceException()` at the top of `SetWindowPosition` to exercise
the exact propagation path the reporter's log shows (`SetWindowPosition`
→ `SelectionChanged` → `set_SelectedItem` →
`InitializeNavItemsAsync.catch` → `Close("InitializationFailed")`).
- Before fix: overlay flashes and closes, log shows `Failed to
initialize navigation items.` with the NRE.
- After fix: overlay stays open, page navigates, log shows `Failed to
set Shortcut Guide window position; keeping previous layout.` from the
new `catch`.
- Did not reproduce the live race on a Win11 dev box; the reporter's
repro is Windows 10 19045 / Microsoft Store install where the legacy
taskbar UIA timing makes the reentrant chain more likely to expose the
null.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 11:00:25 +02:00
Gordon Lam
b5bd626d70 Scope autorun scheduled task permissions to its owner (#48739)
## Summary

The per-user **"Autorun for &lt;user&gt;"** logon scheduled task
(created in `src/runner/auto_start_helper.cpp`) was registered with a
security descriptor that granted **full access to Everyone**
(`D:(A;;FA;;;WD)`). This PR scopes the task's permissions to only the
accounts that actually need them.

## Changes

- `src/runner/auto_start_helper.cpp`
- Added a small `get_auto_start_task_sddl()` helper that builds the
task's security descriptor granting full control to **Local System**,
the **Administrators** group, and **the user the task belongs to** (SID
read from the current process token).
- Use it when calling `RegisterTaskDefinition` instead of the previous
Everyone descriptor.
  - Added `#include <sddl.h>` for `ConvertSidToStringSidW`.

## Behavior / compatibility

- No change to the run-at-startup or run-elevated flows. The decision
logic in `general_settings.cpp` is untouched — only the descriptor
string passed to `RegisterTaskDefinition` changed.
- The task owner can still fully manage (enable / disable / delete) its
own task in both elevated and non-elevated runner modes, because a
user's token SID is identical across elevation.
- The Task Scheduler service (SYSTEM) retains full control, so the task
still runs at logon as before.

## Validation

- Built `src/runner/runner.vcxproj` (x64 / Debug): **0 errors, 0
warnings**.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 16:59:06 +08:00
Gordon Lam
ee7d4a1938 [EnvironmentVariables] Apply profile variables through dedicated user-scope helpers (#48740)
## Summary of the Pull Request

Profiles in the Environment Variables utility override **User**
variables only, as described in
[`doc/devdocs/modules/environmentvariables.md`](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/modules/environmentvariables.md).
This change makes the implementation apply that documented behavior
consistently: every registry write performed while applying, unapplying,
or editing a profile now goes through dedicated user-scope helpers
instead of the generic `ParentType`-based helpers. It also removes some
dead code along the way.

## PR Checklist

- [ ] Closes: N/A (code consistency / cleanup)
- [x] **Tests:** No automated test project exists for this module;
validated via build + manual steps (see below)
- [x] **Localization:** No end-user-facing strings changed
- [x] **Dev docs:** Existing docs already describe the user-scoped
profile behavior; no change needed
- [x] **New binaries:** None

## Detailed Description of the Pull Request / Additional comments

- Added `SetProfileVariableWithoutNotify` /
`UnsetProfileVariableWithoutNotify` to `EnvironmentVariablesHelper`.
They centralize writing profile-driven variables to the current-user
environment, reusing the existing
`SetEnvironmentVariableFromRegistryWithoutNotify` primitive.
- Updated `ProfileVariablesSet.Apply` / `UnapplyVariable` and the
profile branch of `Variable.Update` to use these helpers.
- Removed an unused local (`var applyToSystem =
variable.ApplyToSystem;`) in `ProfileVariablesSet.Apply`.
- Removed the now-unused `SetVariableWithoutNotify` /
`UnsetVariableWithoutNotify` helpers. The notify variants (`SetVariable`
/ `UnsetVariable`) are unchanged and still back the default User/System
set editing.
- Net change: 3 files, +18 / −33.

## Validation Steps Performed

- Built `EnvironmentVariablesUILib` (Debug, x64): **succeeded, 0
warnings / 0 errors**.
- Manual: created, enabled, disabled, and edited profiles; confirmed
profile variables apply, back up the overridden values, and restore them
on disable exactly as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 16:57:20 +08:00
Gordon Lam
a9e1f0f20e [MouseWithoutBorders] Use per-connection random salt and IV for encrypted streams (#48742)
## Summary

Mouse Without Borders encrypts the socket streams it uses to talk
between machines. Previously the PBKDF2 salt and the AES-CBC IV were
both derived from a single fixed constant, so every connection reused
identical key-derivation material.

This change generates a fresh random salt and IV for each connection and
exchanges them in-band as a small cleartext header ahead of the cipher
text. The AES key is now derived from the per-connection salt.

## Changes

- `Encryption.cs`: generate a random per-connection salt + IV,
write/read them as a cleartext header in
`GetEncryptedStream`/`GetDecryptedStream`, and derive the key from that
salt.
- Removed the now-unused fixed-IV helper (`GenLegalIV`), the `InitialIV`
constant, and the derived-key cache (a per-connection salt makes caching
pointless).
- The header exchange reuses the same tolerant socket-close handling as
the existing random first-block handshake, so the exception behavior
seen by callers (`SocketStuff`, `Clipboard`) is unchanged. Call sites
are untouched.

## Compatibility

This changes the on-the-wire format, so all paired machines must run
this version. Mouse Without Borders already requires the same version on
every machine (it surfaces *"make sure you run the same version in all
machines"* on a handshake mismatch). Existing keys and settings are
preserved - no re-pairing is needed once every machine is updated.

## Validation

- Built `MouseWithoutBorders` App and UnitTests (Release | x64) - clean
(exit 0).
- Added `EncryptionTests` covering encrypt -> decrypt round-tripping and
per-connection uniqueness (salt/IV/header differ on every connection).
All 3 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 16:37:32 +08:00
Gordon Lam
1ce0509fc7 Fix GITHUB_TOKEN leak in github-artifacts MCP helper (#48782)
## Summary

Hardens the local `github-artifacts` MCP helper
(`tools/mcp/github-artifacts/server.js`) so the `GITHUB_TOKEN` bearer
token is only ever sent to allowlisted GitHub API hosts. Previously the
same token was attached when downloading image and ZIP URLs extracted
from issue/PR Markdown, which could forward the token to any host
referenced in untrusted issue content.

This is repository MCP dev-tooling, not the shipped PowerToys runtime.

## What changed

- **New `auth.js`** centralizes token handling:
  - `AUTH_ALLOWED_HOSTS = { api.github.com }`
- `shouldSendGitHubToken(url)` — true only for HTTPS + allowlisted host
- `headersForUrl(url, token, extra)` — attaches `Authorization` only
when the host is allowlisted
- **`server.js`** routes all three request sites (`fetchJson`,
`downloadBytes`, `downloadZipBytes`) through `headersForUrl`. Image/ZIP
URLs from issue content are now fetched **without** `Authorization`.
Removed the ZIP dual-attempt auth retry that previously sent the token
to untrusted hosts.
- **New `test-token-allowlist.js`** regression test (wired into `npm
test` / `npm run test:unit`).

## Verification

- Unit test: 13/13 pass — token attached for `api.github.com`, withheld
from arbitrary hosts, loopback, look-alike hostnames
(`api.github.com.attacker.com`), non-HTTPS, and
`*.githubusercontent.com`.
- Cross-origin redirects: confirmed Node's `fetch` (undici) strips
`Authorization` when a redirect leaves the origin.
- Functionality preserved (real public issues, real token): issue #25595
images (5/5 downloaded) and issue #39476 `PowerToysReport_*.zip`
(downloaded + extracted) both succeed without the token — GitHub serves
this content via public/signed URLs.

## Note

For private-repo attachments that would require an `Authorization`
header on a non-API host, the token is intentionally not sent (the
recommended tradeoff). PowerToys is public, so the helper's normal use
is unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 16:37:03 +08:00
Jiří Polášek
b848c70071 Solution: Restore Microsoft.Plugin.Folder.UnitTests as a project in the solution (#46897)
## Summary of the Pull Request

This PR adds `Microsoft.Plugin.Folder.UnitTests` to SLNX as a project
instead of a plain file.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-22 13:52:32 +08:00
Clint Rutkas
9f7a726a7c Remove unused UPDATE_STAGE2 constants from UpdateUtils.h (#46974)
Remove `UPDATE_STAGE2_RESTART_PT` and `UPDATE_STAGE2_DONT_START_PT`
constants from `UpdateUtils.h`. Never referenced anywhere in the
codebase.

Fixes #46968. Found during multi-agent code review of #46889.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-22 13:49:13 +08:00
moooyo
32ad98a0dd [PowerDisplay] Detect built-in panel when driven by the discrete GPU (#48637)
## Summary of the Pull Request

On dual-GPU laptops, Power Display stopped detecting the built-in panel
(and adjusting its brightness) when the **discrete GPU** drives the
display — it showed "can't detect the display". This fixes that by
classifying displays by **capability** (does WMI brightness work on it?)
instead of by the nominal `OutputTechnology` value, which the discrete
GPU misreports for the internal panel.

## PR Checklist

- [x] Closes: #48587
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized (no
new user-facing strings added)
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

### Root cause

On a hybrid / MUX laptop, when the **discrete GPU** drives the built-in
eDP panel, `QueryDisplayConfig` reports the panel's
`DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY` as `DISPLAYPORT_EXTERNAL` (`10`)
instead of the `INTERNAL` flag (`0x80000000`) it reports under the
integrated GPU. It is the *same physical panel* (same EDID) — only the
reported connector type changes with the active GPU.

PR #47740 introduced a strict classifier: `OutputTechnology` →
internal/external, then **internal → WMI-only, external → DDC/CI-only,
with no fallback**. So under the discrete GPU the built-in panel was
classified *external* and sent to DDC/CI only — but a laptop eDP panel
does not speak DDC/CI, so it was dropped and Power Display reported it
couldn't detect any monitor. (`WmiMonitorBrightness` still exposes that
panel regardless of which GPU drives it, so the panel was actually
controllable — it just never got routed to WMI.)

### Fix: classify by capability, not by nominal output technology

- **`MonitorManager`** now runs **WMI discovery first** over the full
`QueryDisplayConfig` inventory. Every display `WmiMonitorBrightness`
exposes is treated as internal (WMI-controlled); whatever WMI does
**not** claim is routed to DDC/CI. The `OutputTechnology`-based
classifier is gone.
- **`WmiController`** matches the system-wide `WmiMonitorBrightness`
results against the full inventory by `Monitor.Id`. The persisted
`Monitor.Id` is still taken from the matched `DevicePath`
(byte-identical to the DDC route and to prior releases), so saved
brightness/per-monitor settings survive upgrades.
- New **`MonitorIdentity.FromInstanceName`** reduces a WMI
`InstanceName` to the same canonical `Monitor.Id` as `FromDevicePath`;
the separate `PnpHardwareKey` helper is removed.
- **Deleted** `DisplayClassifier` and `MonitorDisplayInfo.IsInternal`
(net ~150 fewer lines).

### Accepted trade-off

A monitor that exposes **both** `WmiMonitorBrightness` **and** DDC/CI is
now controlled via WMI only and won't get DDC-only features (contrast /
volume / input source / color temperature / power). This is uncommon
(typical laptop panels are WMI-only; typical external monitors are
DDC-only) and is a deliberate decision: it removes the entire class of
`OutputTechnology` misclassification bugs while keeping the performance
win of not DDC-probing internal panels.

## Validation Steps Performed

- Built the Power Display app (`PowerDisplay.csproj`) and
`PowerDisplay.Lib.UnitTests` (x64 / Debug) with MSBuild — both succeed,
including after merging latest `main` (Windows App SDK 2.2.0).
- Ran the unit test suite: **128/128 pass**, including new
`FromInstanceName` tests — the `FromInstanceName == FromDevicePath`
equivalence invariant and a concrete #48587 regression case (the BOE
panel reported as `OutputTechnology=10`).
- Traced the fix against the reporter's diagnostic logs: the panel that
previously went `OutputTechnology=10 → External → DDC → dropped` is now
claimed by WMI and controllable.
- Reviewed the diff for regressions (Monitor.Id persistence, monitor
blacklist, mirror mode, dual-internal-panel devices, external-only
desktops).

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:37:32 +08:00
Dave Rayment
2ffc248792 [Build] Increase sleep time in InteropTests to attempt to reduce CI failures (#48156)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Increase sleep duration while waiting for IPC setup in the interop unit
test.

A number of PRs have had failures recently even though they do not touch
interop, likely due to this sleep being too short. An existing comment
points to the addition of the delay being because of this, and
resource-constrained build agents/environments could need a longer
duration.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
See comments on  #47211 and #48106.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
N/A
2026-06-22 12:55:26 +08:00
Bryce Cindrich
cabb71108a fix(shortcut-guide): use <N> token for literal digit keys in manifests (#48757)
## Summary of the Pull Request

Converts the literal-digit shortcut keys in the bundled Shortcut Guide
manifests to the `<N>` special-key convention, so they render as the
correct number.

Per the manifest spec, a bare number in `Keys:` is a virtual-key code. A
literal digit key authored as a bare number is therefore misread (VK `9`
is Tab, VK `1` is the left mouse button, VK `0` is undefined) and
renders incorrectly. The fix authors these as the `<N>` token (for
example `"<9>"`), which `KeyVisual` strips to display the digit.

This is a data-only change: **91 literal-digit keys across 14
manifests** become `<N>` tokens. No code or doc changes; the renderer
and converter already support `<N>`, and the convention is documented in
the spec.

Follow-up to #48461, which introduced and documented the `<N>`
convention (per @noraa-junker's request for a separate PR to fix the
remaining manifests). Together with #48461 this resolves the rendering
reported in #48460.

Files touched (all under
`src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Manifests/`):
Adobe.Illustrator (18), Adobe.Photoshop (17), SlackTechnologies.Slack
(13), Adobe.InDesign (11), JetBrains.IntelliJIDEA.Community (11),
BlenderFoundation.Blender (3), Figma.Figma (3), Google.Chrome (3),
Microsoft.Edge (3), Microsoft.VisualStudioCode (3), Mozilla.Firefox (3),
+WindowsNT.Notepad (1), Adobe.AfterEffects (1), GIMP.GIMP (1).

## PR Checklist

- [ ] **Closes:** N/A (follow-up to #48461; contributes to #48460)
- [ ] **Communication:** discussed in #48461; @noraa-junker requested
this separate PR.
- [ ] **Tests:** N/A for data; the converter and `<N>` convention are
unit-tested in #48461. Validated here by deserializing every manifest
with YamlDotNet (see below).
- [x] **Localization:** unchanged; these are per-language manifest
files.
- [ ] **Dev docs:** N/A (the `<N>` convention is documented in the spec
via #48461).
- [ ] **New binaries:** N/A.
- [ ] **Documentation updated:** N/A.

## Detailed Description of the Pull Request / Additional comments

Each change wraps a single bare digit in angle brackets, for example:

```yaml
          Keys:
-            - 9
+            - "<9>"
```

Quoted tokens (`"<9>"`) are used to match the dominant special-token
style already in the manifests (`"<Enter>"`, `"<Down>"`, etc.).

Note (out of scope): `Adobe.Photoshop.en-US.yml` has a shortcut with an
empty `Name: ""` (around line 799). That is a pre-existing data issue
unrelated to digit rendering; the digit is still converted, and the
empty name is left as-is.

## Validation Steps Performed

- Confirmed the diff touches only the 14 manifests, 91 insertions and 91
deletions, with each changed line being a digit wrapped as `"<N>"` (no
whitespace, indentation, or encoding churn).
- Confirmed zero bare-digit `Keys` entries remain and exactly 91 new
`"<N>"` tokens exist.
- Deserialized all 32 manifests with YamlDotNet (the same library the
app uses at runtime): 0 parse errors.
- Rendering behavior for `<N>` is already verified in #48461 (the
renderer strips the brackets to show the digit).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:40:40 +08:00
Bryce Cindrich
a0e53de825 feat(shortcut-guide): add Postman manifest and fix numbered-key display (#48461)
## Summary of the Pull Request

Adds a Shortcut Guide manifest for **Postman** and fixes a rendering bug
where single-digit keys in manifests displayed incorrectly.

- **Fix numbered-key rendering** —
`src/modules/ShortcutGuide/ShortcutGuide.Ui/Converters/ShortcutDescriptionToKeysConverter.cs`:
a single digit (`0`–`9`) in a manifest's `Keys` was treated as a Windows
virtual-key code instead of the literal digit. Since VK `1` is the left
mouse button, VK `9` is Tab, and VK `0` is undefined, shortcuts such as
`Ctrl+0` (reset zoom) and `Ctrl+9` (last tab) rendered as
blank/incorrect glyphs. Single digits are now rendered as the literal
character.
- **Add Postman shortcuts** —
`src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Manifests/Postman.Postman.en-US.yml`:
new manifest for `Postman.exe` covering Tabs, Sidebar, Request,
Interface, Window and modals, and Console. Auto-included via the
existing `Manifests/*.yml` glob in `ShortcutGuide.Ui.csproj`.
- **Show tab-number ranges** — Edge, Chrome, Firefox, and Postman
manifests: the "switch to a specific tab" entry used the literal key
`1`, which (after the fix above) read as `Ctrl + 1`. It now uses a `1 -
8` range so the keycap conveys "any tab number 1 through 8". The
separate "last tab" (`9`) and "reset zoom" (`0`) entries remain literal
single keys.
- **Add unit tests** — new `ShortcutGuide.UnitTests` (MSTest) project
covering `ShortcutDescriptionToKeysConverter.GetKeysList`, including the
single-digit regression.

## PR Checklist

- [x] Closes: #48460
- [ ] **Communication:** I've discussed this with core contributors
already. <!-- Filed #48460; the v0.100 announcement invites app-shortcut
contributions via PR. -->
- [x] **Tests:** Added/updated and all pass <!-- New
ShortcutGuide.UnitTests (MSTest); 8 tests pass locally via
vstest.console. -->
- [x] **Localization:** All end-user-facing strings can be localized
<!-- Shortcut names live in per-language manifest files (`*.en-US.yml`);
other locales fall back to en-US, consistent with existing manifests.
-->
- [ ] **Dev docs:** Added/updated <!-- N/A: no behavior requiring
dev-doc changes. -->
- [ ] **New binaries:** Added on the required places <!-- N/A: the new
manifest is a data asset under an already-shipped, globbed folder. The
new test project is auto-discovered by the existing `**\*UnitTest*.dll`
VSTest glob, so no CI pipeline change is required. -->
- [ ] **Documentation updated:** <!-- N/A -->

## Detailed Description of the Pull Request / Additional comments

The Shortcut Guide displays per-app shortcuts from YAML manifests,
matched to the foreground window via `WindowFilter`. Keys are converted
to keycaps by `ShortcutDescriptionToKeysConverter`. Numeric key strings
were unconditionally parsed as virtual-key codes, so literal-digit
shortcuts rendered wrong. The fix adds a `>= 0 and <= 9` case that emits
the digit character as-is; non-digit numeric codes (arrows, etc.) are
unchanged.

The new Postman manifest exercises this with `Ctrl+0` / `Ctrl+9`. The
browser/Postman "specific tab" entries were updated from the literal `1`
to the `1 - 8` range string, rendered verbatim by `KeyVisual` (the same
path used by the existing `Number (1-9)` key in the Windows Explorer
manifest).

A new `ShortcutGuide.UnitTests` (MSTest) project covers the converter:
single digits render literally (regression test), modifier ordering,
non-numeric passthrough (e.g. `1 - 8`), and arrow-key VK mapping.

## Validation Steps Performed

Built and ran locally (x64 Debug):

- Built `ShortcutGuideModuleInterface`, `ShortcutGuide.Ui`, and
`ShortcutGuide.IndexYmlGenerator`; launched the Debug `PowerToys.exe`.
- Triggered Shortcut Guide (`Win+Shift+/`) with **Postman** focused: the
Postman section renders with all categories, and `Ctrl+1` / `Ctrl+9` /
`Ctrl+0` display correctly (previously blank/incorrect).
- Verified the "specific tab" entry renders as `Ctrl + 1 - 8` in
**Edge**, **Chrome**, **Firefox**, and **Postman**.
- Built `ShortcutGuide.UnitTests` and ran via `vstest.console.exe`:
**8/8 tests pass**.

<img width="845" height="1432" alt="PowerToys Shortcut Guide Running
Postman"
src="https://github.com/user-attachments/assets/6359617e-3e2c-48b0-8005-b3684594ec94"
/>

Co-Authored-By: Claude Opus 4.8

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:40:29 +08:00
Dave Rayment
eab305334b [ColorPicker] Fix the main window UI appearing in the zoomed-in view (#48762)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
This uses `SetWindowAffinity()` with the `WDA_EXCLUDEFROMCAPTURE`
constant on the main Color Picker window to ensure the ZoomWindow bitmap
creation excludes the corner of the picker UI. The ability to capture
the picker window is restored immediately after, so it's still visible
in Snipping Tool, Remote Desktop and other tools.

The fix uses a new helper with simple `Include()` / `Exclude()` methods
as an adapter to the native code. This may potentially be included in
Common if other utilities needed it.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
1. Repro'd the original issue on the current Color Picker release:
<img width="262" height="263" alt="image"
src="https://github.com/user-attachments/assets/bc12b2f8-b67f-4b56-a803-57ab2fe0fa17"
/>

2. Confirmed that the fix worked and normal window affinity was restored
(otherwise I would not have been able to snip this):
<img width="262" height="262" alt="image"
src="https://github.com/user-attachments/assets/bc336ad0-4114-49fa-8440-b78466de363f"
/>

3. Repeated zooming in and out over a normal session.
2026-06-22 04:00:11 +00:00
Avirup
c41ac6df87 Add Pitjantjatjara language support to Quick Accent (#48561)
Closes #45025

## [QuickAccent] Add Pitjantjatjara / Yankunytjatjara Language Support

### Summary
This pull request adds support for Pitjantjatjara/Yankunytjatjara 
to the Quick Accent feature. Pitjantjatjara/Yankunytjatjara is an
Australian Aboriginal language
spoken in the Western and South Australian deserts.

### What's New
- Users can now access Pitjantjatjara/Yankunytjatjara retroflex
consonant characters (ḻ, ṉ, ṟ, ṯ)
  through Quick Accent
- Language is available in the Quick Accent settings dropdown menu
- Full localization support for UI display

### Why This Matters
Pitjantjatjara/Yankunytjatjara uses unique retroflex consonants that are
essential for:
- **Correct pronunciation:** Retroflex sounds are phonetically distinct
from regular consonants
- **Written accuracy:** Proper character representation in educational
materials
- **Cultural preservation:** Supporting Aboriginal language
documentation and teaching
- **Accessibility:** Users without specialized keyboards can now type
these characters easily

### Character Mappings
The implementation adds 4 retroflex consonants:
- **L → ḻ** (Retroflex lateral approximant)
- **N → ṉ** (Retroflex nasal)
- **R → ṟ** (Retroflex approximant)
- **T → ṯ** (Retroflex stop)

### Files Changed
1. **Language.cs** - Added `PJT` enum value
2. **CharacterMappings.cs** - Added language entry with character
mappings and display order
3. **Resources.resw** - Added localized UI string
4. **expect.txt** - Added language name to spell-check whitelist

### Testing
-  Language appears in Quick Accent settings dropdown
-  Retroflex characters appear when holding L/N/R/T + activation key
-  No build errors or warnings
2026-06-22 03:58:43 +00:00
Eymard Silva
e1b1a8d7ed Fix VS Code Workspaces shared storage lookup (#47505)
## Summary

Fixes VS Code Workspaces recent entries discovery after VS Code 1.118
moved `history.recentlyOpenedPathsList` to the shared application
storage database.

The plugin now probes both the legacy `User/globalStorage/state.vscdb`
database and the new shared storage database for VS Code Stable,
Insiders, Exploration, VSCodium, VSCodium Insiders, and portable
installs.

It also deduplicates results that may appear in both locations.

Fixes #47445

## Validation

- Reviewed the change against the issue's documented VS Code 1.118
storage paths.
- Attempted local focused build from
`src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces`
with `tools/build/build.cmd -Platform x64 -Configuration Debug`.
- Build was blocked by local environment/tooling issues unrelated to
this C# change: missing/invalid VC tooling/Windows SDK.
2026-06-21 22:20:24 +08:00
Matthew Gan
19c2c28dd9 Added the Philippine peso symbol to Quick Accent (#48444)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
The PR modifies the `CharacterMappings.cs` file by adding the Philippine
peso symbol to the symbols for the "P" key under the Currency group.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
No validation steps were performed as I assume this would not create any
breaking changes.
2026-06-20 17:37:56 +00:00
Clint Rutkas
459bd56fb6 Grab and Move: tight warning-gold overlay border + Always On Top 4px default (#48474)
## Summary

Refines the **Grab and Move** drag/resize overlay so it matches the
polish of **Always on Top (AoT)**, and lowers the AoT default border
thickness. Created at the request of @crutkas.

Two related border-refinement changes, kept in one PR because the Grab
and Move "double layer" is designed around AoT's border.



https://github.com/user-attachments/assets/0b605f92-60bd-44a0-a540-70e6d425146a



### 1. Always on Top - default border thickness 15 -> 4
The default highlight border was `15px`, which is visually heavy.
Dropped to `4px` for a tighter, Fluent-style frame.
- `src/modules/alwaysontop/AlwaysOnTop/Settings.h` (C++ default)
- `src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs`
(`DefaultFrameThickness`)
- Existing users keep their configured value; only fresh installs /
"reset" pick up `4`. Slider range (1-30) is unchanged.

### 2. Grab and Move - tight, warning-gold overlay (fill + border)
Previously the overlay was a full translucent **white wash** sized to
`GetWindowRect`, which includes the invisible resize-border / shadow
margins (~7px) - so it sat *off* the visible window. It now hugs the
visible frame, mirroring AoT:

- **Keeps the translucent white wash** over the visible window (the
familiar "grabbed" feedback) and adds a tight **warning-gold border on
top**. Both hug the visible frame and are rounded to match the window
corners.
- **Tight geometry:** anchored to `DWMWA_EXTENDED_FRAME_BOUNDS` (inset
by the invisible-border margins) instead of `GetWindowRect`.
- **Corner detection:** matches the window's corner radius via
`DWMWA_WINDOW_CORNER_PREFERENCE` (same mapping AoT uses); border
thickness and radius scale with the target window DPI.
- **Distinct accent:** Fluent **warning gold `#FFB900`** - the literal
equivalent of WinUI
[`SystemFillColorCaution`](https://learn.microsoft.com/en-us/windows/apps/design/style/color)
(used as a `ThemeResource` for warnings across the Settings UI; a Win32
layered window can't resolve a `ThemeResource`, so a literal is
required). Keeps Grab and Move visually distinct from AoT's accent-blue.
- **Double layer, for free:** the Grab and Move border is drawn just
**inside** the visible edge, while AoT draws its border just **outside**
the visible edge. The two naturally stack into a clean double layer, so
Grab and Move stays a constant **4px** with no AoT detection / window
enumeration.

Rendering keeps the existing GDI + `UpdateLayeredWindow` per-pixel-alpha
path and adds **GDI+** (a Windows system library - no new third-party
dependency) for the antialiased, rounded fill and border. Frame metrics
are computed **once per drag/resize** (never in the mouse-move hot
path). The optional geometry label is unchanged.

## Before / After
| | Before | After |
|---|---|---|
| Grab and Move overlay | Full white wash, offset from the window edge |
Same wash, now tight to the visible frame + gold border, corner-matched
|
| AoT default border | 15px | 4px |
| AoT + Grab and Move together | white wash over AoT border | GM gold
inside the edge + AoT accent outside it = double layer |

## Validation
- Builds clean (exit 0, 0 warnings/errors) for **x64 Debug**:
`GrabAndMove`, `AlwaysOnTop`, and `Settings.UI.Library` (Code Analysis /
C26451 clean).
- Smoke-tested live by running the standalone module exes: tight gold
border + wash on Alt-drag / Alt-right-drag, AoT 4px border, and the
inside/outside double layer on a pinned window.
- WARNING: still **draft** pending broader visual validation (border
tightness across DPIs, the exact gold, rounded vs square corners, AoT
z-order during fast drags - AoT renders from a separate process and
follows on a ~100ms timer). Screenshots to be added.

## Follow-up (not in this PR)
AoT and Grab and Move remain **separate** overlay systems (AoT:
persistent per-window Direct2D border; Grab and Move: transient
GDI/`UpdateLayeredWindow` overlay). They can't share one runtime window,
but the frame-geometry + corner-detection + DPI helpers are worth
extracting into `src/common` (seeded by AoT's
`WindowCornersUtil`/`ScalingUtils`). Tracked separately to keep this PR
atomic (`src/common` is an ABI-careful area).

## Notes
- No IPC/JSON schema changes; no new settings.
- No new third-party dependencies (GDI+ is a system library).

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-20 05:06:39 -07:00
Gleb Khmyznikov
e3a1ee2e5b [DOCS] docmd links to source files normalization (#48754)
This pull request adds a custom `docmd` plugin to improve how
documentation links to source files are handled, ensuring that
repo-root-relative links work both when editing locally and on the
published site. The main changes include introducing the new plugin,
updating configuration to use it, and documenting its behavior.

**Plugin integration and configuration:**

* Added a new local plugin `github-source-links` in `docmd-plugins/`,
which rewrites repo-root-relative links (e.g., `/src/.../Foo.cpp`) in
Markdown files to absolute GitHub blob URLs during the documentation
build process, ensuring links remain functional on the published site.
[[1]](diffhunk://#diff-c2c746e6974a6cfdd229031c2977f2bb0dca37c6d5f598ed45dc0d9f0b74c7caR1-R52)
[[2]](diffhunk://#diff-a97f3ce59c97313aacd716b9874b445d162c45ee2e6ef9fd2db59fe17235e1cfR1-R8)
* Updated `docmd.config.json` to register the new plugin under the
`plugins` key, enabling it for documentation builds.
* Updated `package.json` to include the plugin as a dependency,
referencing the local plugin directory.

**Documentation updates:**

* Updated `README.md` to document the new `docmd-plugins/` folder,
explain the purpose of repo-root-relative links, and describe how the
plugin rewrites these links for the published site.
2026-06-19 17:09:20 -05:00
Gleb Khmyznikov
d2aa24786d [DOCS] Add auto-generated github page for dev docs (#48752)
This pull request introduces a new, automated workflow for building and
publishing the developer documentation website using
[docmd](https://docmd.io/). The static site is now generated from
`doc/devdocs`, built in the `doc/devdocs-website` folder, and deployed
to GitHub Pages via a GitHub Actions workflow. The build output is not
committed to the repository but is instead published as an artifact.
Supporting configuration files, documentation, and `.gitignore` entries
are also added to streamline local development and CI/CD.

**Automated build and deployment:**

* Added `.github/workflows/regenerate-devdocs-website.yml` to build the
static site with docmd and deploy it to GitHub Pages automatically on
changes to `doc/devdocs` or `doc/devdocs-website`, or via manual
trigger.

**Project setup and configuration:**

* Added `doc/devdocs-website/package.json` to define the Node.js
project, pin the docmd version, and provide scripts for local
development and builds.
* Added `doc/devdocs-website/docmd.config.json` to configure docmd (site
title, source, output directory, base path).
* Added `doc/devdocs-website/.npmrc` to disable lockfile generation,
ensuring fresh dependency installs each build.

**Documentation and housekeeping:**

* Added `doc/devdocs-website/README.md` with instructions for editing,
building, and publishing the docs website.
* Added `doc/devdocs-website/.gitignore` to exclude the generated
`site/` output from version control.
2026-06-19 15:59:12 -05:00
Michael Jolley
d31d0ab168 Update labeling action to support pull requests (#48733)
This pull request updates the `auto-labeler.yml` GitHub Actions workflow
to improve reliability and maintain compatibility. The most important
changes are an upgrade to the `actions/github-script` version and
improved error handling when applying labels, ensuring the workflow does
not fail due to restricted permissions.

**Dependency upgrade:**
* Upgraded `actions/github-script` from version 7 to version 9 in the
`Apply area labels with AI` step, ensuring continued support and access
to the latest features and security updates.

**Error handling improvements:**
* Added a `try/catch` block around the label application logic to
gracefully handle cases where the workflow lacks permission to write
labels (e.g., due to restricted integration tokens), logging a message
and skipping the operation instead of failing the entire workflow.
2026-06-18 19:13:01 -05:00
Mike Griese
6dcda8a6aa CmdPal: Only list available docks when pinning (#48723)
This is a totally minor nitpick.

I don't have the dock enabled on all my displays. But the current "pin
to dock" dialog lets me pin it to a display I don't have the dock on.
When that happens, it effectively results in _nothing_ happening. Not
great.

This PR mitigates that situation, but only listing the enabled docks
when pinning.
2026-06-18 19:12:45 -05:00
ABHIJEET KALE
0d12a62abb [CmdPal][Dock] Fix performance meter showing '???' after restart (#48682)
This PR fixes Issue #48680 where the CmdPal dock performance meter shows
'???' after restart.

**Root cause**: The PerformanceWidgetsPage initially returns items with
a placeholder title ('???') because ContentData hasn't loaded yet. The
DataManager timer starts when the dock subscribes to ItemsChanged, and
the Updated event fires 1 second later with real data. However, the
Updated event handlers were updating ListItem.Title directly without
calling RaiseItemsChanged() on the page, so the dock was never notified
that the items had changed and continued to display the stale '???'
title.

**Fix**: Added a RaiseItemsChanged() call to each of the 5 Updated event
handlers (CPU, Memory, Network, GPU, Battery) after the item titles are
updated. This causes the dock to re-call GetItems() and refresh the
displayed titles.

Fixes #48680
2026-06-18 17:48:30 -05:00
Michael Jolley
486015d400 Add permissions for PR labeling (#48731)
This pull request makes minor adjustments to the
`.github/workflows/auto-labeler.yml` GitHub Actions workflow, focusing
on permissions and event triggers.

Workflow configuration updates:

* Added `pull-requests: write` permission to the workflow to ensure it
has the necessary access for managing pull requests.
* Removed the `edited` event from the list of triggers for
`pull_request_target`, so the workflow will no longer run when a pull
request is edited.
2026-06-18 17:47:42 -05:00
William
2125d739a3 Changed sleep icon to hibernation icon in Command Palette (#48689)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Changed the icon from the sleep icon to the hibernate icon, which fixes
the issue in #48535

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #48535
## Detailed Description of the Pull Request / Additional comments
The icon was previously the sleep icon, and I have changed it to the
hibernate icon.

## Validation Steps Performed
I have visually checked that the icon is now updated to the correct
icon.
2026-06-18 14:12:26 -05:00
Mike Griese
fb0f4292eb CmdPal: fix initializing the Run history in AOT builds (#48463)
Regressed in one of the last two releases.

The problem was that `history.ToImmutableList()` ran on the projected
`IVector<string>`. `ImmutableList.CreateRange` checks for
`IReadOnlyCollection<string>`, and resolving that interface on a WinRT
object requires a helper type that AOT can't generate.

So we have to just _not do that_.

Closes #48445
2026-06-18 12:55:59 -05:00
Niels Laute
a294e73bb6 [CmdPal] Animated notification (#48176)
## Summary of the Pull Request

Gives the CmdPal toast notification a glow-up: it now slides in/out with
a nice fade, has acrylic + a soft shadow, and stops fighting with
`SizeToContent`.

Along the way, the toast guts got refactored into a couple of reusable
bits that live in `PowerToys.Common.UI.Controls` so other PowerToys
utilities can grab them for their own transient overlays:

- **`TransparentWindow`** — a `WindowEx`-derived host that strips the
native frame, hides from taskbar/Alt-Tab, uses `TransparentTintBackdrop`
for transparency, and runs show/hide implicit animations on its content.
Supply your own animations via `ShowAnimations` / `HideAnimations`, or
take the defaults (fade + slide).
- **`TransparentCard`** — a templated `ContentControl` with acrylic
(`AlwaysActiveDesktopAcrylicBackdrop`), rounded corners, border, and
shadow. Drop whatever XAML you want inside.
- **`AlwaysActiveDesktopAcrylicBackdrop`** — small `SystemBackdrop`
wrapper so the acrylic doesn''t go grey when the window isn''t focused
(transient overlays are never focused).

CmdPal''s `ToastWindow` is now basically a 16-line wrapper: derives from
`TransparentWindow`, drops a bound `TextBlock` inside, and handles its
own 2.5s auto-hide timer + bottom-center positioning.



https://github.com/user-attachments/assets/3a62080c-22f0-480c-ac4d-028bcc32f07d



## PR Checklist

- [x] Closes: #40886
- [x] **Communication:** I''ve discussed this with core contributors
already.
- [ ] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

A couple of design notes worth calling out:

- The window is sized slightly bigger than the visible card (~24px
breathing room on each side) so the shadow and slide animation have room
to render without clipping. That buffer area is transparent but NOT
click-through — kept it small on purpose. We explored `SetWindowRgn` and
`EnableWindow` tricks to make it click-through too, but neither plays
nicely with WinUI 3''s DesktopWindowXamlSource. Small transparent frame
is the pragmatic compromise.
- Animations use the Toolkit''s implicit `ShowAnimations` /
`HideAnimations` so there''s zero animation code in
`ToastWindow.xaml.cs`.
- `TransparentCard.xaml` is registered in `Themes/Generic.xaml` —
required for templated controls in a library project;
`<GenerateLibraryLayout>` alone doesn''t auto-merge per-control xaml.

## Validation Steps Performed

- Built clean (arm64 Debug).
- Triggered a CmdPal toast manually: fades + slides in, hangs for 2.5s,
fades + slides out.
- Acrylic stays active when the window isn''t focused (toasts are never
focused).
- Shadow renders fully without clipping.

---------

Co-authored-by: niels9001 <niels9001@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 12:53:51 -05:00
Anton Kesy
f87b14d5b9 Fix grammar and typos (#47539)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Fixes some typos and grammar mistakes inside docs, strings, and
comments.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

None

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-06-18 12:36:52 -05:00
Michael Jolley
e8fee90f49 Enhance labeling action to support pull requests (#48729)
This pull request updates the auto-labeling workflow to support both
issues and pull requests, improves clarity in logs and prompts, and
renames the workflow file for broader applicability. The changes enhance
automation for triaging by enabling area label assignment to new or
updated pull requests in addition to issues.

**Expanded triage coverage and workflow improvements:**

* The workflow now triggers on both issue and pull request events
(`opened`, `reopened`, `edited`, `synchronize`), allowing automatic area
labeling for pull requests as well as issues.
(`.github/workflows/auto-labeler.yml`,
[.github/workflows/auto-labeler.ymlL1-R7](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL1-R7))
* The concurrency group logic has been updated to handle pull request
events separately from issues, ensuring that rapid updates to a PR or
issue are managed correctly. (`.github/workflows/auto-labeler.yml`,
[.github/workflows/auto-labeler.ymlR24-R26](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eR24-R26))

**User experience and clarity enhancements:**

* Console logs and prompt messages now refer generically to "item" or
distinguish between "Issue" and "Pull request" as appropriate, improving
clarity in workflow output and AI prompts.
(`.github/workflows/auto-labeler.yml`,
[[1]](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL41-R44)
[[2]](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL58-R79)
[[3]](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL139-R146)
[[4]](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL212-R217)
* The system prompt for the AI labeling assistant has been updated to
clarify that both issues and pull requests should be classified and
labeled. (`.github/workflows/auto-labeler.yml`,
[.github/workflows/auto-labeler.ymlL127-R133](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL127-R133))

**File naming:**

* The workflow file has been renamed from
`.github/workflows/auto-label-issues.yml` to
`.github/workflows/auto-labeler.yml` to reflect its broader scope.
(`.github/workflows/auto-labeler.yml`,
[.github/workflows/auto-labeler.ymlL1-R7](diffhunk://#diff-f874b1d773361dc46f2496bc3ce97ee3441e91257188b27a2bd83693c3b8a82eL1-R7))
2026-06-18 12:05:33 -05:00
Copilot
bdc60b6415 CmdPal: Include basic SettingsManager in extension template (#46028)
Adds a `SettingsManager` scaffold to the CmdPal extension template so
new extensions have a ready-to-use settings pattern out of the box.

## Summary of the Pull Request

Most extensions need configuration. The template previously had no
settings infrastructure, leaving developers to discover the pattern from
built-in extensions. This adds a wired-up `SettingsManager` following
the same conventions used across all built-in extensions.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

**`Helpers/SettingsManager.cs`** (new)
- Extends `JsonSettingsManager` (already bundled in
`Microsoft.CommandPalette.Extensions`)
- Uses `Utilities.BaseSettingsPath()` for correct local-state path
(packaged and unpackaged)
- Includes commented-out `ToggleSetting` example illustrating the full
add/register/expose cycle
- Auto-saves via `Settings.SettingsChanged` event

**`TemplateCmdPalExtensionCommandsProvider.cs`** (updated)
- Instantiates `SettingsManager` as a field
- Sets `Settings = _settingsManager.Settings` to advertise settings to
CmdPal
- Exposes `_settingsManager.Settings.SettingsPage` in `MoreCommands` for
in-app settings access

```csharp
// Helpers/SettingsManager.cs
internal sealed partial class SettingsManager : JsonSettingsManager
{
    private static readonly string _namespace = "templatecmdpalextension";
    private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";

    // TODO: Add your settings here. For example:
    // private readonly ToggleSetting _myToggle = new(
    //     Namespaced(nameof(MyToggle)), "My toggle setting", "Description", false);
    // public bool MyToggle => _myToggle.Value;

    public SettingsManager()
    {
        FilePath = SettingsJsonPath();
        // Settings.Add(_myToggle);
        LoadSettings();
        Settings.SettingsChanged += (_, _) => SaveSettings();
    }
}

// TemplateCmdPalExtensionCommandsProvider.cs
private readonly SettingsManager _settingsManager = new();

public TemplateCmdPalExtensionCommandsProvider()
{
    // ...
    _commands = [
        new CommandItem(new TemplateCmdPalExtensionPage())
        {
            Title = DisplayName,
            MoreCommands = [new CommandContextItem(_settingsManager.Settings.SettingsPage)],
        },
    ];
    Settings = _settingsManager.Settings;
}
```

The template wizard replaces `"TemplateCmdPalExtension"` throughout all
files, so `BaseSettingsPath` and the namespace string will update
automatically when scaffolding a new extension.

## Validation Steps Performed

- Verified `SettingsManager` pattern matches existing built-in
extensions (`ClipboardHistory`, `System`, `Registry`)
- No new package references required — `JsonSettingsManager`,
`ToggleSetting`, and `Utilities` are all included in the existing
`Microsoft.CommandPalette.Extensions` NuGet package

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>CmdPal: Include basic SettingsManager in
Template</issue_title>
> <issue_description>### Description of the new feature / enhancement
> 
> Include basic `SettingsManager` in template.
> 
> ### Scenario when this would be used?
> 
> I believe most extensions will require some form of configuration, so
adding basic `SettingsManager` would be helpful.
> 
> ### Supporting information
> 
> _No response_</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes microsoft/PowerToys#39274

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
2026-06-18 16:33:11 +00:00
ABHIJEET KALE
8dc4598786 [CmdPal][System] Add Chinese (ZH) translation guidance for Sleep vs Hibernate (#48534) (#48653)
## Summary

Fixes incorrect Chinese translation where both Sleep and Hibernate
commands showed '休眠' (hibernation) instead of using distinct terms: '睡眠'
for Sleep and '休眠' for Hibernate.

## Changes

Added \<comment>\ elements with \ZH:\ guidance to 6 resource entries in
\Microsoft.CmdPal.Ext.System\\Properties\\Resources.resx\:

| Resource Key | Current Comment | Added ZH Guidance |
|---|---|---|
| \Microsoft_plugin_command_name_hibernate\ | *(none)* | translate as
'休眠' |
| \Microsoft_plugin_sys_hibernate\ | Existing English comment | use 休眠
not 睡眠 |
| \Microsoft_plugin_sys_hibernate_confirmation\ | Existing English
comment | use 休眠 not 睡眠 |
| \Microsoft_plugin_command_name_sleep\ | *(none)* | translate as '睡眠',
do NOT use '休眠' |
| \Microsoft_plugin_sys_sleep\ | Existing English comment | use 睡眠 not
休眠 |
| \Microsoft_plugin_sys_sleep_confirmation\ | Existing English comment |
use 睡眠 not 休眠 |

## Why comments only?

Chinese translations are managed by the internal CDPX localization
pipeline. Adding \<comment>\ elements to the English \.resx\ file is the
standard way to guide translators for the next localization pass. See
similar fix in PR #48649 for Japanese translation guidance.
2026-06-18 12:26:32 +02:00
ABHIJEET KALE
fa8fbb60a1 [CmdPal][Localization] Add JA translation guidance comments for Download and Battery strings (#48649)
## Summary

Adds translation guidance comments to English .resx resource strings
that have incorrect Japanese translations in the CDPX localization
pipeline.

## Problem

- **Download/Downloading** — translated as 受け取り (implies physical
package receipt) instead of the correct IT term 受信
- **Battery** (laptop/tablet context) — translated as 電池 (dry cell)
instead of バッテリー (built-in battery)

## Fix

Added comment elements to 9 resource entries across 3 files with the
prefix JA: to guide the localization team:

| File | Strings | Comment |
|------|---------|---------|
| Microsoft.CmdPal.Ext.WinGet\Properties\Resources.resx |
winget_downloading, winget_download_progress | JA: translate as 受信 not
受け取り |
| Microsoft.CmdPal.UI.ViewModels\Properties\Resources.resx |
winget_operation_status_downloading,
winget_operation_status_downloading_percent,
gallery_item_winget_action_downloading_with_progress,
gallery_item_winget_action_downloading | JA: translate as 受信 not 受け取り |
| Microsoft.CmdPal.Ext.WindowsSettings\Properties\Resources.resx |
BatterySaver, BatterySaverSettings, BatteryUse | JA: translate as バッテリー
not 電池 |

## Background

As documented in doc/devdocs/development/localization.md, localized
.resx files are generated at build time by the CDPX pipeline from .lcl
files managed by the internal localization team. The comment elements in
English .resx files are surfaced to translators and are the correct
mechanism for providing translation guidance. The actual .lcl file fixes
must be applied by the internal team.

Closes #48598
2026-06-18 12:25:40 +02:00
Clint Rutkas
ab4947579b [QuickAccess] Suppress unhandled XAML exceptions in flyout host (#48457)
## Summary

Adds the two missing top-level exception handlers in the QuickAccess
(Preview) flyout host so that an unhandled XAML exception during launch
or page navigation no longer FailFasts `PowerToys.QuickAccess.exe`.

Spotted while reading through `App.OnLaunched` and `ShellPage` for an
unrelated review of the flyout startup path — none of the existing
handlers exist yet, so any throw during `MainWindow` construction,
`ShellHost.Initialize`, or `ContentFrame.Navigate(typeof(LaunchPage) |
typeof(AppsListPage), …)` bubbles all the way out to the Windows App SDK
runtime and is stowed as a XAML failure. Compare with
`src\settings-ui\Settings.UI\SettingsXAML\App.xaml.cs`, which already
wires `UnhandledException += App_UnhandledException`.

## Changes

**`src\settings-ui\QuickAccess.UI\QuickAccessXAML\App.xaml.cs`**

- Hook `Application.UnhandledException` in the constructor. The handler
logs the exception via `ManagedCommon.Logger.LogError` (same logger
Settings uses) and sets `e.Handled = true`. QuickAccess is a transient
launcher flyout owned by the runner, so swallowing a stray XAML error
and keeping the host alive for the next summon is the correct trade-off
— the failure is still recorded for diagnostics.
- Wrap the body of `OnLaunched` in a try/catch. If `MainWindow` (which
sets up window chrome, listener threads, the IPC coordinator, and the
XAML shell) fails to construct, log the exception and call `Exit()`
cleanly rather than letting the throw escape into the Windows App SDK
launch path.


**`src\settings-ui\QuickAccess.UI\QuickAccessXAML\Flyout\ShellPage.xaml.cs`**

- Subscribe to `ContentFrame.NavigationFailed` after
`InitializeComponent`. A page constructor or XAML-load failure in
`LaunchPage` / `AppsListPage` would otherwise bubble out of the `Frame`
and crash the launcher. The handler logs the failure
(`SourcePageType.FullName` + the exception) and marks it handled so the
next summon retries navigation.

No production behaviour changes when things work — only the failure
paths are different. No public API surface changes.

## Why both handlers, not just one

- `Application.UnhandledException` does not fire for
`Frame.NavigationFailed`. The Frame raises its own event first and, if
no handler runs or `e.Handled` is left `false`, then it rethrows on the
dispatcher.
- Conversely, `Frame.NavigationFailed` only fires for navigation
failures — not for an exception thrown directly in `OnLaunched` before
any navigation happens.

The two events are complementary, so both need a handler to fully cover
the launch + navigation paths.

## Testing

- The local NuGet feed on my dev box currently can't restore
`Microsoft.NETCore.App.Runtime.win-x64 = 10.0.9` (the feed only has
`11.0.0-preview.1.26104.118`), which fails the project restore for every
WinUI project including this one. That's the same environment issue I
called out on #48414 — pipeline restore uses a different feed and is
fine.
- All three patterns added here are copy-paste analogues of code that
already exists in `Settings.UI` (`App.xaml.cs:96, 106-109`,
`ShellViewModel.cs:86, 136`), so namespace and signature drift risk is
minimal. The only behavioural difference is `e.Handled = true`, which is
the actual goal of this PR.

## Risk

- Low. Two new event handlers and one try/catch. No behaviour change on
the success path.
- Worst-case regression is that a real, repeatable XAML failure becomes
silent in the runner's eyes (no process crash) instead of loud — but
it's logged via `Logger.LogError` so the user can still find the trace
in `%LOCALAPPDATA%\Microsoft\PowerToys\Logs\`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---

ADO:
https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/61258633/

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 12:20:38 +02:00
moooyo
d221f84d8f [Skills] Update WinUI3 migration skills to add more migration mapping item (#47043)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-18 12:17:49 +02:00
Tucker Burns
14152672c5 Add defensive error handling for Settings navigation and search (#46688)
## Summary

Addresses the **root cause chain** behind the NullReferenceException
crash in Settings navigation. Crash dump analysis with WinDbg revealed
that `Frame.Navigate()` can throw native WinUI ABI exceptions that
propagate unhandled through multiple code paths.

### Changes (3 files)

**NavigationService.cs** — Defensive try-catch for all
`Frame.Navigate()` call sites:
- `Navigate()`: Wrap in try-catch, log via `Logger.LogError()`, return
`false` on failure
- `EnsurePageIsSelected()`: Add matching try-catch for consistency (also
calls `Frame.Navigate()` unprotected)
- Protects **all** navigation in the Settings app

**ShellViewModel.cs** — Fix the crash-causing `Frame_NavigationFailed`
handler:
- Replace `throw e.Exception` with `e.Handled = true` +
`Logger.LogError()`
- The original code threw `NullReferenceException` when `e.Exception`
was null (native WinUI errors don't always marshal to managed Exception
objects)
- Mark the event as handled to prevent framework error propagation

**ShellPage.xaml.cs** — Protect `async void SearchBox_QuerySubmitted`:
- Wrap entire method body in try-catch
- `async void` methods that throw after `await` produce unhandled
exceptions that crash the process
- Covers both search indexing (`Task.Run`) and navigation failures

### Crash Dump Evidence

Analysis of `PowerToys.Settings_2025_03_26_48636.dmp` with WinDbg:
- 4 stowed exception records found at `0x000001c60d3b9100`
- Exception chain: `SearchBox_QuerySubmitted` → `Frame.Navigate()` fails
at native ABI → `NavigationFailed` fires with null Exception → `throw
null` → `NullReferenceException` → `FailFastWithStowedExceptions`
- Root failure in `Microsoft.UI.Xaml.dll!DirectUI::Frame::Navigate`

### Review Notes
- `catch (Exception)` pattern matches 92.5% of Settings.UI codebase
convention
- `Logger.LogError()` with `using ManagedCommon` matches standard import
pattern
- No security concerns: logged page type names are not sensitive data
- Build verified locally (38/38 applicable tests pass; 111 COM-dependent
tests fail identically on main)

Co-authored-by: Tucker Burns <tuck.burns@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-18 11:15:52 +02:00
Matheus Mol
5688441127 [KBM] Fix modifier key remapped to non-modifier delivering WM_SYSKEYDOWN (#47192)
## Summary of the Pull Request

When a modifier key (Ctrl/Alt/Shift) is remapped to a non-modifier key
using
Keyboard Manager, the injected key event is delivered to applications as
WM_SYSKEYDOWN instead of WM_KEYDOWN. This causes unexpected behavior —
for
example, remapping Left Alt to Backspace results in whole words being
deleted
instead of single characters, because applications interpret
WM_SYSKEYDOWN +
VK_BACK as Alt+Backspace.

The fix resets the modifier state with a suppress-flag key-up event
before
injecting the target key, consistent with the existing approach used for
the
Caps Lock remapping scenario.

## PR Checklist

- [ ] Closes: #47191
- [ ] Communication: I've discussed this with core contributors already.
If the work hasn't been agreed, this work might be rejected
- [x] Tests: Added/updated and all pass
- [ ] Localization: All end-user-facing strings can be localized
- [ ] Dev docs: Added/updated
- [ ] New binaries: Added on the required places

## Detailed Description of the Pull Request / Additional comments

The root cause is that SendInput is called inside the low-level keyboard
hook
callback before the original modifier event is suppressed. At that point
the
modifier state is still active, so the OS delivers the injected key as
WM_SYSKEYDOWN (system key with Alt context) rather than WM_KEYDOWN.

This is the same mechanism that was already fixed for the Ctrl/Alt/Shift
↔
Caps Lock case. This PR extends the fix to cover modifier → non-modifier
remaps.

## Validation Steps Performed

1. Remapped Left Alt → Backspace in Keyboard Manager
2. Opened a text editor, typed text, pressed the remapped key
3. Confirmed single characters are deleted instead of whole words
4. All 93 unit tests pass (KeyboardManager.Engine.UnitTests)
2026-06-18 16:33:56 +08:00
Mario Hewardt
d9216f0fc7 ZoomIt - Fix race condition in audio init (#48685)
It was a race condition.
2026-06-16 15:57:13 -05:00
moooyo
8c9bd8ba64 Ignore superpowers-generated docs directory (#48633)
## Summary

Add `docs/superpowers/` to `.gitignore` so all locally generated
superpowers artifacts (specs, design docs, and plans) are not committed.

These files are produced by local AI tooling and are workspace-local
only; they should not land in the repository.

## Change

```gitignore
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
docs/superpowers/
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:58:39 +00:00
Gordon Lam
aaee51b90e Upgrade Windows App SDK to 2.2.0 stable (#48546)
## Summary

Upgrades the centrally-managed Windows App SDK package versions to the
**2.2.0 stable** umbrella released on NuGet.

| Package | Before | After |
|---|---|---|
| `Microsoft.WindowsAppSDK` | 2.0.1 | **2.2.0** |
| `Microsoft.WindowsAppSDK.Foundation` | 2.0.20 | **2.1.0** |
| `Microsoft.WindowsAppSDK.AI` | 2.0.185 | **2.2.3** |
| `Microsoft.WindowsAppSDK.Runtime` | 2.0.1 | **2.2.0** |

Foundation/AI/Runtime versions match the dependency graph declared by
`Microsoft.WindowsAppSDK` `2.2.0`'s own nuspec (`Foundation=2.1.0`,
`AI=2.2.3`, `Runtime=[2.2.0]`), so transitive resolution is exact and no
version-conflict warnings are introduced.

Also bumps the CmdPal `ExtensionTemplate` sample's local
`Directory.Packages.props` so the template stays in sync with the main
repo.

## Files changed

- `Directory.Packages.props`
-
`src/modules/cmdpal/ExtensionTemplate/TemplateCmdPalExtension/Directory.Packages.props`

## Validation

Ran `tools/build/build-essentials.cmd` on a clean `origin/main`
worktree:

- `msbuild PowerToys.slnx /t:restore /p:RestorePackagesConfig=true` —
**Build succeeded, 0 warnings, 0 errors** (00:02:50)
- `src/runner/runner.vcxproj` (x64 Debug) — **Build succeeded, 0
warnings, 0 errors** (00:04:15)
- `src/settings-ui/Settings.UI/PowerToys.Settings.csproj` (x64 Debug) —
**Build succeeded, 0 warnings, 0 errors** (00:03:14)

Full module test suite has **not** been run yet — this PR only certifies
the build-essentials baseline. CI will exercise the wider build/test
matrix.

## Notes

- This is an atomic packaging-only change. No source code touched, no
behavior changes.
- If WinAppSDK 2.2.0 surfaces any runtime regression downstream, it can
be reverted as a single commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 15:53:33 +08:00
moooyo
ff3c1f9252 [PowerDisplay] Allow waking a monitor from standby via power-state On (#48628)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Power Display could put a monitor to sleep but never wake it back up.
Selecting **On** in the per-monitor power-state list was a hard-coded
no-op, so the DDC/CI wake command (VCP `0xD6` = `0x01`) was never sent.
This removes that guard so selecting **On** wakes the display, and
cleans up the dead code/comment left behind by the original
one-directional design.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

`MonitorViewModel.HandlePowerStateSelectionChanged` early-returned when
the selected power state was **On** (`0x01`), so `SetPowerStateAsync`
was never called for On and the wake write never reached the monitor. As
a result Power Display's power control was one-directional: it could
send Standby/Suspend/Off but could never turn a monitor back on.

The guard dates back to the very first power-state commit and was paired
with a single-monitor assumption — *"the monitor must be on to see the
UI"*, so On was treated as the always-current state and skipped. A later
change made the selection reflect the monitor's real power state (so a
monitor in the list can legitimately be asleep), and multi-monitor
support means the flyout can be shown on monitor A while the user wants
to wake monitor B. Those changes invalidated the assumption, but the
action-side guard survived a subsequent refactor.

The lower layers already do the right thing:
`MonitorManager.SetPowerStateAsync` →
`DdcCiController.SetPowerStateAsync` → `SetVcpFeatureAsync(monitor,
0xD6, value)` passes the value through unchanged, so the fix is purely
removing the UI-layer guard. DDC/CI stays reachable while the panel is
in Standby/Suspend/Off(DPM), so writing `0x01` turns it back on (this is
the same mechanism Twinkle Tray uses). `Off (Hard)` / `0x05` may still
require a physical wake on some monitors, since that state can cut the
DDC command channel.

Cleanup included in this PR:
- Removed the now-unused `PowerStateItem.PowerStateOn` constant (its
only consumer was the deleted guard).
- Removed the dead `SetPowerState` `[RelayCommand]` (the generated
`SetPowerStateCommand` had zero references — the XAML wires
`SelectionChanged`, not a command).
- Updated the `SetPowerStateAsync` doc comment from the one-directional
framing to a neutral bidirectional description.

Net change: 2 files, +5 / −24.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

- **Build:** `MSBuild PowerDisplay.csproj -p:Platform=x64
-p:Configuration=Debug` (CoreCompile) — **0 errors / 0 warnings**.
- **Static check:** repo-wide grep confirms no remaining references to
`PowerStateOn` or `SetPowerStateCommand`; the power-state ListView binds
only `ItemsSource` + `SelectionChanged` (no `SelectedItem` binding), so
opening the flyout cannot spuriously re-fire a selection.
- **Manual (requires a DDC/CI monitor):** enable *Power state control*
for a monitor → open its flyout and select **Standby** or **Off (DPM)**
(screen blanks) → reopen the flyout and select **On** → the display
wakes.

No automated test was added: with the guard removed the handler is an
unconditional pass-through (identical in shape to
`HandleInputSourceSelectionChanged`), and it is an `async void` WinUI
event handler over real DDC/CI hardware, which is outside the
`PowerDisplay.Lib` unit-test seam.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 06:13:12 +00:00
thetsaw
99ee955dfb Fix DiskAnalyzer plugin link and author (#48618)
Updated the DiskAnalyzer plugin link and author information to point to
the ValleySoft organization repository instead of my personal account.

## Summary of the Pull Request

Documentation-only change. Updates the DiskAnalyzer entry in
`thirdPartyRunPlugins.md`:
- Plugin URL: `thetsaw/PowerToys.Plugin` →
`valley-soft/powertoys-diskanalyzer`
- Author: `thetsaw` → `ValleySoft`

## PR Checklist

- [ ] Closes: N/A
- [x] **Communication:** This is a minor doc-only update to correct a
repo link — no prior discussion needed
- [x] **Tests:** N/A — documentation change only
- [x] **Localization:** N/A — no user-facing strings changed
- [x] **Dev docs:** Updated `doc/thirdPartyRunPlugins.md`
- [ ] **New binaries:** N/A

## Detailed Description of the Pull Request / Additional comments

The DiskAnalyzer PowerToys Run plugin was originally submitted under my
personal GitHub account (`thetsaw`). The project has since been moved to
the official **ValleySoft** organization at:
https://github.com/valley-soft/powertoys-diskanalyzer

## Validation Steps Performed

Verified all URLs are live and resolve correctly:
- https://github.com/valley-soft/powertoys-diskanalyzer 
- https://github.com/valley-soft 
2026-06-15 04:10:13 +00:00
Gordon Lam
4d01062f76 Fix check-spelling: exclude ZoomIt rnnoise third-party tree and dedupe excludes (#48548)
## Summary

The `Check Spelling` workflow has been failing on PRs against `main`
(e.g. #48546) due to issues introduced by the recent ZoomIt webcam-blur
/ noise-cancellation change (#48266) plus two pre-existing duplicate
entries in `.github/actions/spell-check/excludes.txt`.

## What the bot reported

| Severity | Type | Location |
|---|---|---|
|  | `forbidden-pattern` (Should be `a`) |
`src/modules/ZoomIt/ZoomIt/rnnoise/kiss_fft.h:79` — third-party kiss_fft
header contains `an fft` |
| ⚠️ | `large-file` (~30 MB) |
`src/modules/ZoomIt/ZoomIt/rnnoise/rnnoise_data_little.c` |
| ⚠️ | `binary-file` |
`src/modules/ZoomIt/ZoomIt/selfie_segmentation.onnx` |
| ⚠️ | `duplicate-pattern` ×2 | `excludes.txt` lines 115/116 duplicate
lines 108/109 (`FuzzyMatcher{Comparison,Diacritics}Tests.cs`) |

## Fix

`.github/actions/spell-check/excludes.txt`:

- **Drop 2 duplicate** `FuzzyMatcher*Tests.cs` lines.
- **Add 2 new exclusions** for the new third-party ZoomIt assets:
- `^src/modules/ZoomIt/ZoomIt/rnnoise/` — entire third-party
rnnoise/kiss_fft tree (covers both the `an fft` forbidden-pattern in
`kiss_fft.h` and the 30 MB `rnnoise_data_little.c` large-file).
- `^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$` — the ML model
binary.

Net change: `-2` duplicates, `+2` new exclusions → file count unchanged
at 148 lines.

## Notes

- Third-party content under `rnnoise/` should not be spell-checked; this
matches how other vendored/third-party trees in the repo are handled
(e.g. `src/common/CalculatorEngineCommon/exprtk.hpp`,
`src/common/sysinternals/Eula/`).
- No source code changes; pure config.
- Unblocks #48546 and any other PR currently failing `Check Spelling` on
`main`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 09:25:03 +08:00
Knyrps
d7d1e543ae [CmdPal][TimeDate] Open notification center when clicking the clock dock band (#48514)
## Summary

Clicking the clock dock band in the CmdPal Dock now opens the Windows
notification center (Action Center). A separate bell-icon-only dock band
is also exposed for users who prefer a dedicated notification center
shortcut.

Closes #46327

## Detail

- **Clock band left-click**: replaced the previous `NoOpCommand` on
`NowDockBand` with `OpenUrlCommand("ms-actioncenter:")`, dismissing the
Dock on invoke. The `ms-actioncenter:` URI is the correct shell
mechanism - `SendInput` Win+N was tested but dropped because it requires
foreground focus, which the Dock holds at click time.
- **Notification center band**: new `NotificationCenterDockBand`
(`ListItem`) in `TimeDateCommandsProvider.cs`, with a bell icon
(`\uEA8F`, Segoe Fluent Icons) and the same `ms-actioncenter:` command.
Exposed as a second `WrappedDockItem` from `GetDockBands()` under the id
`com.microsoft.cmdpal.timedate.notificationCenterBand`. Users can pin it
from the Dock's edit mode.
- **New resource strings**:
`timedate_show_notification_center_command_name` and
`timedate_notification_center_band_title` added to `Resources.resx` /
`Resources.Designer.cs`.
- **VS 2026 C++ build fixes** (pre-existing failures on `HEAD`): added
`_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS` to
`CalculatorEngineCommon.vcxproj`.

## Screenshots

<img width="339" height="991" alt="image"
src="https://github.com/user-attachments/assets/e0ef8c9a-ec1f-40fa-9620-1e83e6aeeb8d"
/>

## How tested

- Built `Microsoft.CmdPal.UI.csproj` (Debug x64) - 0 errors.
- Launched dev `Microsoft.CmdPal.UI.exe`, clicked the clock band -
notification center opened correctly.
- Right-click context menu on the clock band still shows "Copy time" and
"Copy date" unchanged.
- Pinned the notification center band via edit mode - bell icon renders
icon-only, click opens notification center.
2026-06-12 19:23:46 +00:00
Alex Mihaiuc
272b725ff0 Add ZoomIt webcam backgroun (blur) and microphone noise cancellation (#48266)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This change adds the [RNNoise](https://github.com/xiph/rnnoise) filter
for noise cancellation (audio) and the [Google
mediapipe](https://github.com/google-ai-edge/mediapipe/tree/master)
`selfie_segmentation_cpu` model for webcam background detection and
blurring.

It also fixes an issue introduced with
ba68b88ca1 causing the ZoomIt shortcuts to
fail to register in the standalone version.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

The Settings UI has been extended with a Noise cancellation option, a
Background selection for the webcam and a Brightness slider.

The functionality for these is added to ZoomIt itself. Also, restored
the Mono checkbox which was accidentally masked by
b93fd97e80.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Mario Hewardt <marioh@microsoft.com>
2026-06-12 00:12:35 +02:00
Niels Laute
7884f4217a Update readme (#48392)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-11 06:54:58 +00:00
gilnatab
92014c81b9 [PowerDisplay] Add linked brightness control (#48207)
## Summary of the Pull Request

Adds linked brightness control to PowerDisplay so multiple
brightness-capable monitors can be controlled from a single "All
Displays" slider.

This PR:
- Adds a linked brightness mode with one master brightness slider.
- Seeds the master slider from the linked display with the lowest
Windows DISPLAY number, falling back to monitor ID for determinism.
- Persists linked mode enabled/disabled state.
- Persists per-monitor exclusions by monitor ID.
- Keeps individual display cards available under an expandable section
while linked mode is enabled.
- Shows linked-state guidance in the link icon tooltip instead of a
separate info banner.
- Allows excluded displays to keep their own independent brightness
slider.
- Keeps profiles as per-monitor snapshots; applying a profile turns
linked brightness off before applying the profile values.
- Adds unit tests for linked-brightness selection/seed behavior and
settings compatibility.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

**Screenshots**

| State | Light | Dark |
| --- | --- | --- |
| Linked mode off | <img width="519" height="817" alt="image"
src="https://github.com/user-attachments/assets/bdfae94b-b2e2-4ad3-a45c-7925bb9e5dcd"
/> | <img width="520" height="817" alt="image"
src="https://github.com/user-attachments/assets/69290a70-0375-480d-957c-c9e0af43d18e"
/> |
| Linked mode on | <img width="520" height="307" alt="image"
src="https://github.com/user-attachments/assets/a2b3572b-e51f-4bdc-9209-23ad2f96d27a"
/> | <img width="520" height="307" alt="image"
src="https://github.com/user-attachments/assets/8b14b665-b641-4256-a15b-eced82e62728"
/> |
| Linked mode on — individual displays expanded | <img width="520"
height="895" alt="image"
src="https://github.com/user-attachments/assets/0b40e60d-e78a-4814-baf6-00be7e283edd"
/> | <img width="520" height="895" alt="image"
src="https://github.com/user-attachments/assets/4f59bbfa-d6e5-4cb7-af84-cb484f922a7c"
/> |

The first version is intentionally scoped to brightness-only linked
control. Contrast, volume, color temperature, input source, and
LightSwitch-specific behavior remain independent.

Linked brightness is stored as global PowerDisplay settings:
- `linked_levels_active`
- `excluded_from_sync_monitor_ids`

Newly connected brightness-capable monitors are included by default,
because the exclusion list is the explicit exception. Hotplugging a
monitor does not immediately write brightness; linked hardware writes
happen only after the user changes the master slider.

Profiles remain per-monitor snapshots. This PR does not add
profile-level linked brightness configuration. If linked brightness is
active when a profile is applied, linked mode is turned off first, then
the saved per-monitor profile values are applied. That avoids leaving
the master linked slider active while hardware brightness has been
changed independently per monitor.

When linked mode is turned on, the master slider is seeded from the
linked brightness-capable display with the lowest Windows DISPLAY
number, falling back to monitor ID for determinism. Excluded displays
and displays without brightness support are ignored; if no linked target
remains, the master slider stays disabled. The seed only positions the
slider; it is never written to hardware, so the first user gesture is
the first broadcast.

## Validation Steps Performed

- Built `PowerDisplay.Lib.UnitTests` Debug x64:

```powershell
.\tools\build\build.ps1 -Platform x64 -Configuration Debug -Path src\modules\powerdisplay\PowerDisplay.Lib.UnitTests
```

- Ran `PowerDisplay.Lib.UnitTests` with `vstest.console.exe`
- Ran the XAML styling script:

```powershell
.\.pipelines\applyXamlStyling.ps1 -Main
```

- Result: the XAML styling script completed successfully and processed
`src/modules/powerdisplay/PowerDisplay/PowerDisplayXAML/MainWindow.xaml`.
2026-06-10 13:54:40 +08:00
Alex Mihaiuc
d57096af20 Fix broken hotkeys condition (#48401)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This fixes a bug introduced by
`ba68b88ca1617e52647c6dde467c56f53ca2422a` in the hotkey processing
logic.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

A complex condition ended up being incorrectly broken into 2 conditions,
leading to a missed `else` execution. This led to the mishandling of
keyboard shortcuts especially during reassignment in the standalone mode
for ZoomIt.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-09 15:44:08 +02:00
Noraa Junker
f136a4fe04 [Shortcut Guide] Fix foreground window detection (#48386)
## Summary of the Pull Request

This PR fixes Shortcut Guide foreground app detection by resolving app
IDs from the window that was in the foreground before the Shortcut Guide
UI takes focus.

Based on review feedback, it also adds the missing XML `<param>`
documentation for `foregroundWindowHandle` in
`ManifestInterpreter.GetAllCurrentApplicationIds(...)` to satisfy
documentation/style requirements.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

### Functional change
- Capture and reuse the foreground window handle taken before Shortcut
Guide UI activation.
- Use that captured handle for current-application ID resolution instead
of querying foreground window later, improving app-specific shortcut
matching reliability.

### Follow-up feedback fix
- Added missing XML parameter documentation:
-
`src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs`
  - Added `<param name="foregroundWindowHandle">...</param>`

## Validation Steps Performed

- Ran `parallel_validation` (Code Review: no issues; CodeQL: skipped as
trivial doc-only follow-up).
- Attempted local `dotnet build` for `ShortcutGuide.Ui.csproj`; blocked
by transient external package feed/network failure while restoring
`Microsoft.Build.CopyOnWrite/1.0.282`.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-09 20:00:46 +08:00
Niels Laute
87fa204fd6 Disable Shortcut Guide by default on clean installs (#48383)
## Summary

Flips the default for the `Shortcut Guide` entry in `enabledModules`
from `true` to `false`. New PowerToys installs (no prior
`settings.json`) will start with Shortcut Guide disabled, matching how
other newer modules (PowerToys Run, MouseJump, AdvancedPaste,
MouseWithoutBorders, CropAndLock, QuickAccent, TextExtractor,
MousePointerCrosshairs, KeyboardManager) already ship off-by-default in
`EnabledModules.cs`.

## Why

Shortcut Guide has been on-by-default since the early days, but it is an
opt-in style utility (overlay launched on hotkey). It should not be
active for users who never asked for it on a fresh install.

## Changes

- `src/settings-ui/Settings.UI.Library/EnabledModules.cs` -- flip
`shortcutGuide` backing field from `= true` to bare declaration with the
file's `// defaulting to off` comment convention.
- `src/settings-ui/Settings.UI.UnitTests/ViewModelTests/General.cs` --
update the corresponding `AllModulesAreEnabledByDefault` assertion.

## Compatibility

Existing installs are unaffected: their `settings.json` already persists
the user's prior value (`true`), so anyone who has it on today keeps it
on. Only freshly created `EnabledModules` instances pick up the new
default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 17:06:40 +08:00
Niels Laute
6ebfac8eab [Shortcut Guide] Settings footer icon clip fix and vector nav icons (#48390)
## Summary of the Pull Request

Visual polish for the Shortcut Guide UI, follow-up to #48383 and #48384.

- Fixes the Settings footer icon being clipped in the rail at startup.
Root cause: the icon `RowDefinition` in `CustomNavigationViewStyle.xaml`
was `*`, so the rail's `MinHeight` constraint compressed the 20px icon
down. Changing it to `Auto` lets the row size to its content. This also
removes the need for the `FakeSettingsButton` workaround.
- Replaces the laptop `FontIcon` used for the `Windows` nav entry with a
4-square Windows logo rendered as a `PathIcon`.
- Replaces the bitmap PowerToys app icon with a theme-aware vector
`PathIcon` (rounded square frame + menu bar + three dots) so it matches
the rest of the icons.
- Path data for both icons lives as `x:String` resources in `App.xaml`.
- Tunes the rail item `Row 0` height so the icon vertically aligns with
the selection pill center.

<img width="681" height="1405" alt="image"
src="https://github.com/user-attachments/assets/10787087-e32f-4018-b004-3f824648b962"
/>

## PR Checklist

- [x] **Closes:** part of the Shortcut Guide 0.100 polish set
- [x] **Communication:** internal only
- [x] **Tests:** manual verification
- [x] **Localization:** no new strings
- [x] **Dev docs:** N/A

## Validation Steps Performed

- Built locally and verified the Settings footer icon is no longer
clipped on first show.
- Verified the Windows nav entry now uses the Windows logo PathIcon and
renders correctly in light and dark.
- Verified the PowerToys entry now renders as a vector and follows the
theme foreground.
- Verified the selection pill aligns vertically with the icon center.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-09 17:06:17 +08:00
Jessica Dene Earley-Cha
582f3eb5c3 Move from testing to final for telemetry PR detection workflow (#47993)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Transitions the telemetry PR detection workflow from testing phase to
ready status. All components are now fully functional and automatically
triggered on every new PR.

- Skips checks on draft PRs to avoid noise
- Prevents multiple concurrent runs per PR
- Safely requests `@chatasweetie` as reviewer on telemetry changes
- update commit messages

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Carlos Zamora <carlos.zamora@microsoft.com>
2026-06-08 22:56:09 +02:00
Mike Griese
4f14070a1a cmdpal: blindly try to fix dock usage data (#48283)
We have no idea why this event isn't flowing in 0.99. We fixed the other
"new" event, `CmdPal_ExtensionInvoked`, when we merged #47121. But this
event didn't show up.

So, as a blind experiment, we're removing the potentially long payload.
Just to see if something happens.
2026-06-05 11:31:00 +08:00
Shinpache Shimura
76eb6eaac5 Add copy monitor diagnostics button to Power Display (#48209)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

In the powerdisplay tool, Added a new button, that allows to copy the
current display configuration, to allow to share for troubleshooting, or
just sharing.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-05 01:14:09 +08:00
Jessica Dene Earley-Cha
97f2868481 Fix: Narrator announces checkbox labels in CmdPal Extensions > Installed Apps page (#48135)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Fixes labels not associated with controls in Installed Apps pane.
Narrator only announces "space, checkbox, checked/unchecked" for
Extensions > Installed Apps page checkboxes instead of reading the label
too.

this is an a11y internal bug

The Adaptive Cards renderer (`AdaptiveCards.Rendering.WinUI3` v2.x)
renders `Input.Toggle` as a CheckBox whose `Content` is a TextBlock
containing just `" "`. Without an AutomationProperties.Name`, Narrator
reads the CheckBox.Content (a space character), making the settings
inaccessible to screen reader users.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Michael Jolley <mike@baldbeardedbuilder.com>
2026-06-03 07:50:35 +02:00
Pranshu Namdeo
f3d3abc552 Fix Performance Monitor settings file path collision (#48251)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Fixes #48224

The Performance Monitor extension was still storing its settings in the
shared settings.json file. Since Command Palette built-in extensions now
use extension-specific sibling settings files, the extension's settings
could be overwritten when Command Palette personalization settings were
saved.

This change updates SettingsJsonPath() to store Performance Monitor
settings in an extension-specific settings file
(performanceMonitor.settings.json), ensuring the network speed unit
setting persists correctly.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

The Performance Monitor extension had not been migrated to the newer
extension-specific settings file architecture. As a result, its settings
were stored in the shared settings.json file and could be lost when the
Command Palette host rewrote its configuration.

Following @zadjii-msft's guidance in the issue, this PR updates
SettingsManager.cs to store Performance Monitor settings in an
extension-specific settings file:

performanceMonitor.settings.json

instead of the shared:

settings.json

This aligns the extension with the current Command Palette settings
architecture and prevents the network speed unit setting from being
overwritten.


<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

* Changed Network Speed Unit to "Binary bytes per second" in Performance
Monitor settings.
* Verified the setting was successfully saved to the newly created
`performanceMonitor.settings.json` file.
* Modified core Command Palette personalization settings.
* Verified the Performance Monitor setting was preserved and not
overwritten.
* Restarted PowerToys and verified the setting persisted correctly.

**Note to reviewers:** I left the "Tests" box unchecked because this is
a single-line file path configuration change. I did not add an automated
test, but I have thoroughly verified the fix manually as described in
the Validation steps above.
2026-06-02 16:33:17 +00:00
Michael Jolley
0a64561ed5 CmdPal: Allow connecting to arbitrary hostnames in Remote Desktop list page (#48069)
## Summary of the Pull Request

Fixes #48053 — Remote Desktop extension now allows connecting to
arbitrary hostnames when navigated into the extension's list page.

Previously, `RemoteDesktopListPage` only displayed previously saved
connections. This converts it from `ListPage` to `DynamicListPage`,
adding search-driven behavior:

- Typing a valid hostname/IP that **doesn't** match an existing saved
connection shows a **"Connect to {hostname}"** item at the top of the
list
- Typing something that **exactly matches** a saved connection shows
only the normal results (no duplicate arbitrary-host item)
- Typing an invalid string (not a valid hostname/IP) shows no
arbitrary-host item

The hostname validation reuses the same logic as
`FallbackRemoteDesktopItem`: strip the port suffix (e.g. `myhost:3389` →
`myhost`), then check `Uri.CheckHostName` against `IPv4`, `IPv6`, `Dns`.

## PR Checklist
- [x] Closes: #48053
- [ ] **Communication:** I've discussed this with core contributors
already.
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 10:47:25 -05:00
Niels Laute
7f19817182 Rework Power Display warning dialog (#48249)
## Summary of the Pull Request

Rework the confirmation dialog shown when enabling Power Display (and
its potentially-destructive sub-features) so it is shorter, friendlier,
and consistent across all entry points. The five separate prefix-driven
variants are now a single `PowerDisplayWarningDialog` user control
selected via an enum, sharing the title, learn-more link, and
Enable/Cancel buttons. The previous hand-rolled red warning text is
replaced with a Fluent `InfoBar Severity=""Warning""`.

## PR Checklist

- [ ] Closes: #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

### Before
- `DangerousFeatureWarningDialog` took a resource-key prefix string and
probed up to five optional keys per variant (`_WarningHeader`,
`_WarningConfirm`, `_WarningList_Item1/2`, etc.).
- Each of the five flows (EnableModule, ColorTemperature, PowerState,
InputSource, MaxCompatibility) had a slightly different title (some
questions, some statements, mixed `Warning:` prefixes) and a hand-rolled
red `TextBlock` warning header with a `⚠️` emoji and
`SystemFillColorCriticalBrush`.
- ~30 fragmented `PowerDisplay_*_Warning*` resw keys.

### After
- New `PowerDisplayWarningDialog` selected via `PowerDisplayWarningKind`
enum (`EnableModule`, `ColorTemperature`, `PowerState`, `InputSource`,
`MaxCompatibility`).
- Shared chrome lives in the control:
  - Single title `Before you continue` for every variant.
  - `InfoBar Severity=""Warning""` replaces the hand-rolled red header.
- Learn-more `HyperlinkButton` pointing at
`aka.ms/powerToysOverview_PowerDisplay_Note` (URL is a `private const`
so translators don't see it).
  - Consistent Enable / Cancel buttons.
- Per-variant content collapses to one InfoBar message + one body
paragraph in resw (12 keys total, down from ~30). Bullets are inlined as
`• ` + newlines with `xml:space=""preserve""`.
- `PowerDisplayViewModel.ConfirmDangerousFeatureAsync` and
`TryCommitDangerousChangeAsync` now take the enum instead of a magic
string.

### Files
- **Added:** `ViewModels/PowerDisplayWarningKind.cs`,
`SettingsXAML/Views/PowerDisplayWarningDialog.xaml{,.cs}`
- **Removed:**
`SettingsXAML/Views/DangerousFeatureWarningDialog.xaml{,.cs}`
- **Updated:** `Strings/en-us/Resources.resw`,
`ViewModels/PowerDisplayViewModel.cs`,
`SettingsXAML/Views/PowerDisplayPage.xaml.cs`

## Validation Steps Performed

- Built `PowerToys.Settings.csproj` (Debug arm64) — clean.
- Manually exercised the EnableModule and MaxCompatibility flows;
verified the new title, InfoBar, body paragraph, learn-more link, and
Enable/Cancel button behavior.
- Verified `aka.ms/powerToysOverview_PowerDisplay_Note` opens in the
default browser.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 14:57:43 +08:00
Niels Laute
a33fd3c474 [KBM] Enable new editor by default (#48245)
## Summary

Make the new WinUI 3 Keyboard Manager editor the default by flipping
`useNewEditor` from `false` to `true` everywhere a default is supplied.

## Behavior

- **New installs / new `settings.json`** → new editor enabled
- **Upgrades from a version before the `useNewEditor` key existed** →
new editor enabled (the key is missing, so the default kicks in)
- **Users who have explicitly toggled the setting** → unchanged (we only
change the default, not stored values)
- The "Go back to classic" button in the KBM settings page is untouched

## Changes

- `src/settings-ui/Settings.UI.Library/KeyboardManagerProperties.cs` —
`UseNewEditor` property initializer now `true`
- `src/modules/keyboardmanager/dll/dllmain.cpp` — `m_useNewEditor`
member init + `GetNamedBoolean` fallback now `true`; warn-log message
updated to match
-
`src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs`
— both fallback paths in `IsUseNewEditorEnabled` (file missing /
unreadable) now return `true`, so the "Open New Editor" CmdPal command
surfaces by default

## Validation

Built locally on ARM64 Debug (exit code 0):
- `src/modules/keyboardmanager/dll`
- `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys`
- `src/settings-ui/Settings.UI.Library`

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-02 14:57:02 +08:00
Niels Laute
b712fa4d85 Reword Shortcut Guide module and OOBE descriptions (#48248)
## Summary of the Pull Request

Reword the Shortcut Guide module description and OOBE description so
they describe the feature without referring to `your apps`. The module
description now reads as a single sentence covering Windows and the
active app; the OOBE description is shortened to one paragraph and
refers to `various applications` instead of enumerating the bundled
apps.

## PR Checklist

- [ ] Closes: #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

Two strings in
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw` changed:

- `ShortcutGuide.ModuleDescription` — now: `Shows an on-screen overlay
of keyboard shortcuts for Windows and the active application.`
- `Oobe_ShortcutGuide.Description` — collapsed to one sentence
describing Windows + various applications, no longer enumerating bundled
apps or mentioning future additions.

No code or behavioral changes.

## Validation Steps Performed

- Built Settings.UI (Debug arm64) – clean.
- Verified the Shortcut Guide module card in Settings UI shows the new
description and the OOBE Shortcut Guide page shows the new paragraph.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-02 14:39:52 +08:00
Michael Jolley
b66b044210 CmdPal: Synchronize fallback title/subtitle format for consistent scoring (#48085)
## Summary

Fixes #46055 — Standardizes built-in fallback title/subtitle format so
scoring is consistent across all action fallbacks.

## Problem

`MainListPage.cs` scores fallback items by fuzzy-matching the query
against both Title and Subtitle, but with different weights:
- `nameScore = FuzzyScore(query, Title)`
- `descriptionScore = (FuzzyScore(query, Subtitle) - 4) / 2`

Fallbacks that embedded the raw query in Title got artificially higher
scores than those using Subtitle. This made ranking unpredictable.

## Fix

All "action" fallbacks now follow a consistent pattern:
- **Title** = static action description (no query text)
- **Subtitle** = raw query string (unquoted)

"Result" fallbacks (that found a specific matched item) are left
unchanged — they correctly show the matched item name in Title.

## Full Fallback Audit (example query: `notepad`)

| Fallback | Title | Subtitle | Status |
|---|---|---|---|
| WebSearch: Search | "Search the web with Edge" | `Search for notepad`
| **Changed** — was `Search for "notepad"` in subtitle |
| WebSearch: Open URL | "Open in Microsoft Edge" | `Open notepad.com` |
**Changed** — was `Open "notepad.com"` in title |
| Shell: Run | "Run" | `notepad` | Unchanged — already correct |
| Calculator | "3" (result) | `1+2` (query) | Unchanged — intentional
exception |
| Indexer: single result | "notepad.exe" | `C:\Windows\notepad.exe` |
Unchanged — result fallback |
| Indexer: multiple results | "File search" | `Search for notepad in
files` | **Changed** — was `Search for "notepad" in files` in title |
| Windows Settings: single | "Notepad settings" | `Settings > Apps` |
Unchanged — result fallback |
| Windows Settings: multiple | "Search Windows settings..." | `Search
for notepad` | **Changed** — was `Search for "notepad" in Windows
settings` in title |
| Remote Desktop: exact match | "MyPC" (connection) | "Connect to MyPC"
| Unchanged — result fallback |
| Remote Desktop: arbitrary host | "Remote Desktop" | `Connect to
notepad-host` | **Changed** — was `Connect to notepad-host` in title |
| TimeDate | "Monday, May 23" (result) | "Current date" | Unchanged —
result fallback |
| System | "Shut down" (result) | "Shuts down the computer" | Unchanged
— result fallback |
| PowerToys | "Color Picker" (static) | "Pick a color..." | Unchanged —
result fallback |

## Changes

- `FallbackExecuteSearchItem.cs` — Subtitle uses raw query instead of
`"Search for \"{query}\""` format
- `FallbackOpenURLItem.cs` — Title shows browser name (was query),
Subtitle shows raw query (was browser)
- `FallbackOpenFileItem.cs` — Multi-result: Title is static display
name, Subtitle is raw query
- `FallbackWindowsSettingsItem.cs` — Multi-result: Title is static
description, Subtitle is raw query
- `FallbackRemoteDesktopItem.cs` — Arbitrary host: Title is static
"Remote Desktop", Subtitle is raw query

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:57:30 -05:00
Michael Jolley
18919eaa40 CmdPal: Fix dock subtitle visibility in compact mode after async update (#48088)
## Summary

When an extension updates its `Subtitle` property asynchronously after
initial render, the `TextVisibilityStates` visual state group
transitions from `TitleOnly` → `TextVisible`. This transition sets
`SubtitleText.Visibility = Visible`, overriding the `CompactStates`
setter that had hidden it.

## Fix

Added `control.UpdateCompactState()` to `OnTextPropertyChanged` in
`DockItemControl.xaml.cs`. This re-applies the compact state after any
text property change. When `IsCompact` is `false`, `UpdateCompactState`
is a no-op — no behavior change for the non-compact path.

Fixes #47980

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:56:33 -05:00
Michael Jolley
e0854fbaf3 CmdPal: Reorder dock network stats to match Task Manager order (#48098)
## Summary

Fixes #47939

The Performance Monitor dock band displayed network stats as Receive →
Send, but Task Manager shows Send → Receive. This swaps the order to
match Task Manager.

## Changes

- `PerformanceWidgetsPage.cs`: Swap `_networkUpItem` (Send) before
`_networkDownItem` (Receive) in the band items array.

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:55:41 -05:00
Michael Jolley
ba20da1611 CmdPal: Fix hotkey navigation when palette is showing transient dock page (#48089)
## Summary

When a keyboard shortcut opens CmdPal to an extension while the palette
is already showing a dock-launched transient page, `GoHome(false)`
cannot restore the root page — the frame's back stack is empty because
the transient dock page was never pushed on top of root. The user ends
up with only the hotkey-target page in the frame with no way to navigate
back to the main list.

## Root Cause

In `ShellPage.SummonOnUiThread()`, the hotkey-to-page branch called
`GoHome(false)` before sending `ShowWindowMessage`. But when the active
page is a transient dock page, `_currentlyTransient` is still `true` and
the frame back stack is empty, so `GoHome` can't re-establish the root
page as the frame base.

## Fix

Added `ResetToHome()` to `ShellViewModel`, mirroring the pattern already
used in `WindowHiddenMessage` handling:
1. Clears `_currentlyTransient`
2. Calls `_rootPageService.GoHome()` to reset extension state
3. Sends `PerformCommandMessage` for `_rootPage` — navigating
MainListPage into the frame as the base

In `ShellPage.SummonOnUiThread()`, the `GoHome(false)` call in the
`isPage` branch is replaced with `ViewModel.ResetToHome()`. The root
page is then cleanly in the frame before the hotkey target's
`PerformCommandMessage` navigates on top of it.

Fixes #47994

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:29:53 -05:00
Niels Laute
35f2ed839e CmdPal: Reorder Pin to Dock dialog so controls precede the preview (#48250)
## Summary of the Pull Request

Reorder the Pin to Dock dialog content so the configuration controls
(monitor selector, dock section, label options) appear at the top and
the live preview is shown below them. The user now configures the pin
first and sees the resulting preview directly underneath, instead of
staring at the preview and having to scan past it to find the controls.

<img width="515" height="440" alt="image"
src="https://github.com/user-attachments/assets/0d1d0543-2b30-48f5-a1aa-676a165870f5"
/>

## PR Checklist

- [ ] Closes: #xxx
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places

## Detailed Description of the Pull Request / Additional comments

XAML-only change to
`src/modules/cmdpal/Microsoft.CmdPal.UI/Dock/PinToDockDialogContent.xaml`.
The new visual order inside the `ScrollViewer`/`StackPanel` is:

1. Monitor selector (still `Visibility=""Collapsed""` by default; shown
when more than one monitor is available)
2. Dock section `Segmented` (Start / Center / End)
3. Label options (`Show title` / `Show subtitle` checkboxes)
4. Divider `Rectangle`
5. Preview `Border`

No logic, bindings, `x:Name` identifiers, event handlers, or `x:Uid`
keys are changed.

## Validation Steps Performed

- Built `Microsoft.CmdPal.UI` (Debug arm64) — clean.
- Verified the Pin to Dock dialog renders with controls on top and the
preview underneath; segmented selection, label-option checkboxes, and
the multi-monitor combo still behave as before.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 15:43:46 -05:00
Michael Jolley
e20b5b9c51 CmdPal: Fix GPU index out of range crash in PerfMon widget (#48103)
## Summary

Fixes #47821

The GPU Performance Monitor widget crashes with
`IndexOutOfRangeException` on systems where GPU performance counters
fail to enumerate (common on Intel Arc and hybrid GPU configurations).
The dock band shows `???` and opening the flyout causes an error.

## Root Cause

`GPUStats.CreateGPUImageUrl()` accessed `_stats[index]` without bounds
checking. When `GetGPUPerfCounters()` finds no matching counter
instances, `_stats` remains empty but callers still pass index 0.

`GetGPUName()`, `GetGPUUsage()`, and `GetGPUTemperature()` already have
proper guards (`if (_stats.Count <= index) return ...`) — this fix adds
the same pattern to the one remaining unguarded method.

## Changes

- `GPUStats.cs`: Add bounds check to `CreateGPUImageUrl()` — return
empty string if index out of range

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 20:59:57 +02:00
Mike Griese
c6a9ad2ad0 cmdpal: fix the dock window border being visible, redux (#48180)
Addenda to #47187

that fix only works if the _hwnd is already set. Actually it's crazy it
ever worked.

Tested by disconnecting and reconnecting RDP a couple times, which
pretty consistently reproduces the problem.
2026-06-01 11:33:17 +00:00
Muyuan Li
a67fc2d9b7 Fix ShortcutGuide v2 crash when Manifests directory is missing (#48171)
## Summary

Fixes #48170 — ShortcutGuide v2 crashes on launch when the bundled
`Manifests` directory is absent from the install path.

### Root Cause

The `Assets\ShortcutGuide\Manifests\*.yml` files were never reaching the
build output directory during the CI solution-level build (`msbuild
PowerToys.slnx /t:Build -graph`). The `CopyToOutputDirectory` metadata
on `<Content>` items does not reliably copy files to a shared
`OutputPath` in this build configuration. As a result, the WiX installer
generator found no yml files to package, and the installed product was
missing the Manifests directory entirely.

At runtime, `PowerToysShortcutsPopulator.Populate()` threw an unhandled
`FileNotFoundException` causing a crash loop.

### Fix (3 layers)

1. **Code resilience** (`Program.cs`, `PowerToysShortcutsPopulator.cs`):
- Wrap `Populate()` in try/catch so a missing manifest degrades
gracefully instead of crashing
   - Add `File.Exists` guard before `File.ReadAllText`

2. **Build output** (`ShortcutGuide.Ui.csproj`):
- Add explicit `CopyManifestsToOutputDir` MSBuild target
(`AfterTargets="Build"`) that copies yml files to
`$(OutDir)Assets\ShortcutGuide\Manifests\` — same pattern as the
existing `CopyPRIFileToOutputDir` target
- Keep `<Content Include>` with `CopyToOutputDirectory` as a fallback
for publish scenarios

3. **Installer packaging** (`generateAllFileComponents.ps1`,
`ShortcutGuide.wxs`):
   - Add `*.yml` to the file inclusion list
- Add `Generate-FileList` / `Generate-FileComponents` calls for
`ShortcutGuideManifestsFiles`
- Add WiX directory definition and `RemoveFolder` component for the
Manifests directory

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 16:28:08 +08:00
Jessica Dene Earley-Cha
c78f6e52a0 [CmdPal] Toggle "Show details" / "Hide details" with icon in context menu (#48140)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Converts the "Show details" context menu command into a toggle that
switches between "Show details" and "Hide details" with appropriate
icons, and fixes the icon not rendering in the context menu.

Address internal a11y bug.



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-06-01 13:19:38 +08:00
thetsaw
9a55209d13 Add DiskAnalyzer to third-party Run plugins list (#48106)
## Summary of the Pull Request

Adds **DiskAnalyzer** to the General plugins table in
`doc/thirdPartyRunPlugins.md`.

- **Plugin:**
[Community.PowerToys.Run.Plugin.DiskAnalyzer](https://github.com/thetsaw/PowerToys.Plugin)
- - **Author:** thetsaw
- - **Keyword:** `ds`
- - **License:** MIT
- - **Platforms:** x64 and ARM64
### What it does
Scan any folder or drive to find the largest files and subfolders, view
drive usage with visual progress bars, and navigate your filesystem all
from PowerToys Run.

## PR Checklist

- [x] Plugin has been publicly available
- [ ] - [x] MIT licensed
- [ ] - [x] Releases include x64 and ARM64 zips
- [ ] - [x] plugin.json is correctly formatted
- [ ] - [x] README includes install instructions
## Detailed Description

This is a documentation-only change adding one row to the third-party
plugins table. No source code, binaries, or build files are modified.
2026-05-28 17:46:23 +00:00
Copilot
0cb6fe250b Rename Settings UI label from “Shortcut Guide V2” to “Shortcut Guide” (#48151)
## Summary of the Pull Request

This updates PowerToys Settings to remove the obsolete “V2” suffix from
the Shortcut Guide module name. The UI now consistently shows **Shortcut
Guide**.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

- **Settings navigation label**
  - Updated `Shell_ShortcutGuide.Content` to `Shortcut Guide`.
- **Module title**
  - Updated `ShortcutGuide.ModuleTitle` to `Shortcut Guide`.
- **OOBE title**
  - Updated `Oobe_ShortcutGuide.Title` to `Shortcut Guide`.

```xml
<data name="Shell_ShortcutGuide.Content" xml:space="preserve">
  <value>Shortcut Guide</value>
</data>
```

## Validation Steps Performed

- N/A for behavior-level validation in this description (change is
limited to localized display strings).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-28 17:46:17 +08:00
moooyo
c0cb9417ad Remove "NEW" tag from Power Display and Grab and Move (#48174)
## Summary of the Pull Request
Both Power Display and Grab and Move have matured beyond their initial
release phase. This removes the "NEW" `InfoBadge` from their navigation
items in Settings, the two parent navigation groups (Windowing &
Layouts, Input / Output) that surfaced the badge when collapsed, and
clears the `IsNew` flag for Power Display in the OOBE shell.

<img width="1867" height="973" alt="image"
src="https://github.com/user-attachments/assets/533f271c-c70f-414f-a76a-43fd9ffbbd44"
/>
<img width="497" height="575" alt="image"
src="https://github.com/user-attachments/assets/fe1e97c3-c806-4f42-a836-76e042630d61"
/>
<img width="1619" height="1027" alt="image"
src="https://github.com/user-attachments/assets/f5db715b-bc69-4505-803a-18a9b2716280"
/>


## PR Checklist

- [x] Closes: #48153
- [x] **Communication:** Tracked by the linked issue
- [x] **Tests:** Markup-only change; Settings.UI builds clean with WinUI
markup compiler (no XAML errors)
- [x] **Localization:** No end-user-facing strings changed
- [ ] **Dev docs:** N/A
- [ ] **New binaries:** N/A
- [ ] **Documentation updated:** N/A

## Detailed Description of the Pull Request / Additional comments
Files touched:
- `src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml` —
removed four `<InfoBadge Style="{StaticResource NewInfoBadge}" />`
blocks on `GrabAndMoveNavigationItem`, `PowerDisplayNavigationItem`, and
on the two parent group headers `WindowingAndLayoutsNavigationItem` and
`InputOutputNavigationItem` (the parent badges existed only to surface a
NEW child when the group was collapsed; with no NEW children left in
those groups, the parent badges are now stale).
- `src/settings-ui/Settings.UI/OOBE/ViewModel/OobeShellViewModel.cs` —
flipped `(PowerToysModules.PowerDisplay, true)` to
`(PowerToysModules.PowerDisplay, false)`. Grab and Move was already
`false`.

No other modules or strings affected.

## Validation Steps Performed
- Built `src\settings-ui\Settings.UI\PowerToys.Settings.csproj`
(Release|x64) with MSBuild from VS 18 Enterprise;
`PowerToys.Settings.dll` produced with 0 errors related to this change.
WinUI markup compiler would have aborted before producing the DLL if the
XAML had syntax issues.
- Diff inspected: only the five intended deletions/edits, no collateral
changes.
- Visual run-time verification of the Settings navigation pane is
recommended before merge.

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:28:31 +00:00
moooyo
cd5027fa1a [PowerDisplay] Fix false-positive crash detection on cooperative shutdown (#48173)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Cooperative shutdowns of `PowerDisplay.exe` — Runner's `TerminateApp`
NamedPipe message, the `Terminate` named event, tray-quit, Runner-exit
detection, and PowerToys upgrades — all call `Environment.Exit(0)`
immediately. If DDC/CI discovery is mid-flight, that path skips the
`try/finally` that owns `CrashDetectionScope`, leaving `discovery.lock`
on disk. Phase 0 at the next `PowerDisplay.exe` startup then treats this
orphan as evidence of a real crash and auto-disables the module,
surfacing the "PowerDisplay has crashed" InfoBar in Settings UI.

This PR adds an `AppDomain.ProcessExit` safety-net inside
`CrashDetectionScope`. ProcessExit fires for `Environment.Exit` but
**not** for `FailFast` / BSOD / external `TerminateProcess` — exactly
the partition we need: cooperative exit → best-effort delete the lock;
involuntary kill → leave the lock for Phase 0 to detect (original design
intent preserved).

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

### Root cause

`CrashDetectionScope.Begin()` writes `discovery.lock` before DDC/CI
capability fetch and `Dispose()` deletes it when the `using` block
exits. The lock is intentionally designed to survive any code path that
cannot run user-mode cleanup (BSOD, kernel OOM, `TerminateProcess`), so
that the next `PowerDisplay.exe` start can see it and run Phase 0 (write
`crash_detected.flag`, set `enabled.PowerDisplay=false` in global
`settings.json`, signal `AutoDisablePowerDisplayEvent`).

The bug is that several **cooperative** shutdown paths route to
`Environment.Exit(0)` immediately:

| Path | Code |
|---|---|
| Runner's `TerminateApp` NamedPipe | `App.xaml.cs::OnNamedPipeMessage`
→ `Shutdown()` → `Environment.Exit(0)` |
| `Terminate` named event | `App.xaml.cs::OnLaunched` →
`RegisterEvent(..., () => Environment.Exit(0), "Terminate")` |
| Tray-quit | `TrayIconService` callback → `Environment.Exit(0)` |
| Runner-exit detection | `RunnerHelper.WaitForPowerToysRunner` callback
→ `Environment.Exit(0)` |

`Environment.Exit` calls `ExitProcess` under the hood, which terminates
all threads abruptly. Background `Task.WhenAll` doing DDC capability
fetch is killed mid-flight; the `finally` block that calls
`scope.Dispose()` never runs; `discovery.lock` orphans; Phase 0 next
time false-positives.

Concrete repro from logs:
- `15:08:42.510` lock written
- `15:08:42.79` probe monitor #1
- `15:08:46.92` probe monitor #2 (started, not finished — typical probe
takes ~5s)
- `15:08:49.03` `TerminateApp` received → `Environment.Exit(0)` → no
`Dispose` log line
- `15:10:10.03` next startup: Phase 0 sees orphan lock with `pid:17712,
startedAt:2026-05-28T07:08:42Z` → writes `crash_detected.flag` →
auto-disables

### Fix

`CrashDetectionScope.Begin()` now also subscribes to
`AppDomain.CurrentDomain.ProcessExit`. The handler does a best-effort
`File.Delete(_lockPath)` (swallowing exceptions, as required for
ProcessExit handlers). `Dispose()` unsubscribes before deleting. An
`Interlocked.Exchange` guards the race between Dispose and ProcessExit
so only one of the two performs the delete.

ProcessExit's semantics match the cooperative/involuntary partition
exactly:

| Shutdown path | ProcessExit fires? | Behavior after this PR |
|---|---|---|
| `Environment.Exit(code)` (all 4 paths above) | yes | lock deleted by
handler |
| `Environment.FailFast` | no | lock survives → Phase 0 catches it
(correct: explicit FailFast = real failure) |
| BSOD / external `TerminateProcess` / kernel OOM | no | lock survives →
Phase 0 catches it (correct: original design) |
| Discovery completes normally / throws | n/a | `try/finally` calls
`Dispose()` as before; handler unsubscribed first |

### Testability

A new `IProcessExitHook` interface abstracts the subscription so unit
tests can simulate ProcessExit without terminating the test runner.
Production code uses the default `AppDomainProcessExitHook` singleton;
tests inject a fake whose `RaiseExit()` invokes subscribed handlers
synchronously.

### Files touched

-
`src/modules/powerdisplay/PowerDisplay.Lib/Services/IProcessExitHook.cs`
*(new)* — interface + production singleton
-
`src/modules/powerdisplay/PowerDisplay.Lib/Services/CrashDetectionScope.cs`
— subscribe in `Begin`, unsubscribe in `Dispose`, add `OnProcessExit`
handler, expanded class doc
-
`src/modules/powerdisplay/PowerDisplay.Lib.UnitTests/CrashDetectionScopeTests.cs`
*(new)* — 10 unit tests

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

### Automated

10 new unit tests in `CrashDetectionScopeTests`, all passing:

```
Passed Begin_WritesLockFileAtomically
Passed Begin_SubscribesToProcessExit
Passed Dispose_UnsubscribesFromProcessExit
Passed Dispose_DeletesLockFile
Passed ProcessExitFired_BeforeDispose_DeletesLock      (core scenario)
Passed ProcessExitFired_AfterDispose_DoesNothing
Passed Dispose_AfterProcessExit_DoesNotThrow
Passed ProcessExitFired_LockFileMissing_DoesNotThrow
Passed Dispose_IsIdempotent
Passed MultipleScopes_DoNotShareState
```

Full `PowerDisplay.Lib.UnitTests` suite: **129 / 132 passing**. The 3
failures (`DetectOrphanAndDisable_RunsFullSequenceWhenOrphanPresent`,
`DetectOrphanAndDisable_HandlesUnknownVersionAsOrphan`,
`DetectOrphanAndDisable_LeavesLockIntactOnSignalFailure`) are
**pre-existing on `main`** — they fail with `REGDB_E_CLASSNOTREG` from
`Constants.AutoDisablePowerDisplayEvent()` (WinRT activation factory not
COM-registered in the test environment). Verified by stashing this PR's
changes and re-running the same 3 tests on baseline `main` — same
failures, same cause, unrelated to this change.

### Manual

1. Reproduced the original false-positive on `main`:
- Enable PowerDisplay → open Settings UI → quickly toggle PowerDisplay
off
- Observe `discovery.lock` left in
`%LOCALAPPDATA%\Microsoft\PowerToys\PowerDisplay\`
- Re-enable PowerDisplay → Phase 0 writes `crash_detected.flag` →
InfoBar appears
2. Repeated the same steps with this branch:
- Toggling PowerDisplay off cleanly deletes `discovery.lock`
(ProcessExit handler ran)
- Re-enabling PowerDisplay shows no InfoBar, no `crash_detected.flag`
created
3. BSOD path is unchanged (verified by inspecting the conditional logic
— `AppDomain.ProcessExit` does not fire for involuntary terminations;
the lock survives just as before).

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:02:14 +00:00
Copilot
7da62cdb0a Rename OOBE overview Learn link label to “Documentation” (#48155)
## Summary of the Pull Request

Renames the OOBE welcome/overview hyperlink label from **“Documentation
on Microsoft Learn”** to **“Documentation”** for brevity and
consistency.
Scope is limited to the localized string resource used by the OOBE
overview page.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

- **Resource update (OOBE Overview)**
- Updated `Oobe_Overview_DescriptionLinkText.Text` in
`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`.

```xml
<data name="Oobe_Overview_DescriptionLinkText.Text" xml:space="preserve">
  <value>Documentation</value>
</data>
```

## Validation Steps Performed

- Confirmed the OOBE overview string key now resolves to
**“Documentation”**.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-28 15:26:02 +08:00
Eymard Silva
c46083dd8d Handle complex calculator results (#47506)
## Summary of the Pull Request

Return a friendly calculator error when Mages evaluates an expression to
a complex number instead of letting decimal conversion throw.

This fixes the PowerToys Run Calculator result for expressions such as
`sqrt(-1)` by detecting `System.Numerics.Complex` results before decimal
conversion and showing a localized error message instead.

Fixes #43937

## PR Checklist

- [x] Closes: #43937
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [x] **Localization:** All end-user-facing strings can be localized
- [x] **Dev docs:** Added/updated
- [x] **New binaries:** Added on the required places
- [x] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [x] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [x] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [x] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [x] **Documentation updated:** Not required for this bug fix.

## Detailed Description of the Pull Request / Additional comments

The Calculator plugin previously passed complex results from Mages into
`Convert.ToDecimal`, which caused an exception for expressions like
`sqrt(-1)`.

This PR updates the calculator result transformation logic to detect
`System.Numerics.Complex` and return a localized user-facing error
message: `Complex numbers are not supported`.

It also updates calculator query tests to cover both direct keyword and
global query behavior.

## Validation Steps Performed

- Added unit test coverage for `=sqrt(-1)` returning `Complex numbers
are not supported`.
- Added unit test coverage for global query `sqrt(-1)` returning no
result instead of surfacing an unhandled exception.
- Ran `git diff --check`.
- Attempted local build/test with the PowerToys build scripts, but local
validation was blocked by Visual Studio/VC tooling configuration issues
unrelated to this change: `PlatformToolsetVersion` resolves to an empty
value during restore/build.
2026-05-28 15:06:22 +08:00
Mike Griese
65112a7b05 Move CmdPal API spec back to cmdpal/ directory (#48160)
Reverts 0819a62 / #46926

The cmdpal API is literally generated from this spec document. It needs
to live with the rest of the code to work correctly.

Docs for authoring cmdpal extensions are on
https://learn.microsoft.com/en-us/windows/powertoys/command-palette/extension-development,
and we should direct docs commentary there.
2026-05-28 00:05:12 +02:00
Dustin L. Howett
109c63ba33 Remove our dependency on expected-lite (#48159)
This removes our last git submodule dependency!

We were using `expected-lite` in one place, which was being compiled out
_anyway_ in favor of using `std::expected`.
2026-05-27 14:58:27 -07:00
🄂ʏᴇᴅ 🄰ʙᴅᴜʟ 🄰ᴍᴀ🄝 ✧
6be6509c46 Fix project template settings heading (#48148)
## Summary
- Fix a grammar typo in the PowerToy project template README heading.
- Change "Settings Informations" to "Settings Information".

## Validation
- Ran `git diff --check`.
2026-05-27 17:31:03 +00:00
Boliang Zhang
8a7933c0b2 Migrate spdlog from submodule to vcpkg (#48039)
## Summary

Migrate `deps/spdlog` from a git submodule to **vcpkg manifest mode**
with an overlay port pinned to the **exact same commit**
(`gabime/spdlog@616866fc`). Replaces the polyfill shim added in #47910
with a proper port-level patch.

This is the follow-up to PR #47928, which I closed after @zadjii-msft /
@DHowett clarified that the intended direction was a single combined
"move to vcpkg **and** apply a patch file" (one change, not two stepping
stones).

## Guidance honored

Per @zadjii-msft (offline):
-  Convert each submodule to vcpkg **one at a time** — this PR is
**spdlog only**. `deps/expected-lite` stays a submodule (separate PR
next).
-  Atomic commit per dep (multiple commits on the branch for review
traceability; squash on merge gives the requested single commit).
-  **Don't bump the version.** Only variable changed: submodule →
vcpkg. Same commit (`616866fc`, v1.8.5 + 38) the submodule pointed at.

Per @DHowett
([review](https://github.com/microsoft/PowerToys/pull/48039#pullrequestreview-4338835150)):
-  No vcpkg submodule — vswhere-first detection via a Terminal-style
`steps-install-vcpkg.yml` template; three-tier `VcpkgRoot` fallback (env
var → VS-shipped → runtime clone pinned to manifest baseline).

## Design

- **Repo-root manifest**: `vcpkg.json` declares only `spdlog`, with
`builtin-baseline` pinned. `vcpkg-configuration.json` registers
`deps/vcpkg-overlays/` as overlay-ports.
- **Overlay port** `deps/vcpkg-overlays/spdlog/`: `vcpkg_from_github(REF
616866fc...)` with bundled fmt preserved (`-DSPDLOG_FMT_EXTERNAL=OFF`);
the MSVC 14.51 fix from #47910 carried as a proper vcpkg patch on
`include/spdlog/fmt/bundled/format.h`.
- **vcpkg integration is global** (set in `Cpp.Build.props`, imported
via `ForceImportBeforeCppProps` for every `.vcxproj`). An earlier
attempt to make vcpkg per-project-opt-in via `deps/spdlog.props` failed
because ~85 PowerToys `.vcxproj` files import `spdlog.props` AFTER
`Microsoft.Cpp.targets`, by which point `vcpkg.props`' `ClCompile` hook
is dead-on-arrival. The trade-off (every C++ project invokes `vcpkg
install` once at build time, ~0.5 s on cache hits, manifest declares
only spdlog so install set is fixed) is documented in the expanded
`Cpp.Build.props` comment.
- **`deps/spdlog.props`** is now a thin shim that only sets the
historical `SPDLOG_*` preprocessor defines for source-compat.
- **`Cpp.Build.targets`** is a new file imported via
`ForceImportAfterCppTargets` to load `vcpkg.targets` after
`Microsoft.Cpp.targets`. A fail-fast `<Target>` errors with a clear
message if `vcpkg.props` can't be found at the resolved `VcpkgRoot`.
- **Removes** `deps/spdlog-msvc-fix/` polyfill, in-tree wrapper
`src/logging/`, spdlog submodule, the single `<ProjectReference>` in
`logger.vcxproj`, plus 3 `.slnf` refs and 2 `.slnx` refs
(`PowerToys.slnx` + `installer/PowerToysSetup.slnx`), plus 3 hard-coded
`..\deps\spdlog\include` entries in `<AdditionalIncludeDirectories>`.
- **CI**: new reusable `.pipelines/v2/templates/steps-install-vcpkg.yml`
(vswhere-first, manifest-baseline-pinned fallback clone, respects
`useVSPreview`). Gated `Cache@2` for `%LOCALAPPDATA%\vcpkg\archives`
keyed on overlay-port contents. Same vcpkg detection added to
`tools\build\build-essentials.ps1` for local devs.

## Verification

Local build matrix (all 4 configs of `logger.vcxproj` and a
representative late-import consumer):

| Config | Result | Notes |
|--------|--------|-------|
| Release \| x64    |  | vcpkg install ~21 s, `logger.lib` produced |
| Debug \| x64 |  | **Validates patch fixes the actual MSVC 14.51 bug**
(`_ITERATOR_DEBUG_LEVEL > 0` → `_SECURE_SCL`) |
| Release \| ARM64 |  | vcpkg cross-installs `arm64-windows-static`
spdlog in ~16 s |
| Debug \| ARM64 |  | **Previously DISABLED for the in-tree spdlog**
(per `<Build Solution="Debug\|ARM64" Project="false" />` in
`PowerToysSetup.slnx`); this migration FIXES that latent gap |
| FancyZonesLib (Release \| x64) |  | Late-import-pattern consumer;
previously broke in v2 |

Full PowerToys CI (x64 + arm64 + CmdPal SDK + all GitHub Actions checks)
green.

**Consumer audit**: 72 `.vcxproj` files reference `logger.vcxproj`; all
72 also import `deps/spdlog.props`. No transitive-link breakage.

## Out of scope (intentional)

- `deps/expected-lite` migration — next PR per "one-at-a-time" rule.
- Remote vcpkg binary cache (Azure Artifacts NuGet feed). Local pipeline
`Cache@2` works for now, but a remote feed survives across pipelines and
is the long-term answer. Happy to split this into a follow-up.

## Notes for review

- Patch in the overlay port is identical content to PR #47928's patch
but regenerated with LF line endings (vcpkg's `vcpkg_apply_patches` is
strict; no `--ignore-whitespace`).
- Once PowerToys eventually bumps spdlog past v1.14 (which ships fmt
10.2 and drops the affected code path), the overlay port can be deleted
and we can use upstream vcpkg's `spdlog` directly.
- Re. official-release pipelines and terrapin / less-restricted network
isolation: VS-shipped vcpkg is the primary path (no network); the
fallback clone is only exercised when VS doesn't ship vcpkg. Happy to
wire terrapin into the fallback as a follow-up if the official build
template needs it.

Closes the work tracked in #47928 (which was closed unmerged).

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Dustin L. Howett <dustin@howett.net>
2026-05-27 15:45:24 +08:00
Jeremy Sinclair
6f5ea3bb95 [Deps] Upgrade .NET Runtime package versions to 10.0.8 (#48010)
## Summary of the Pull Request

Updates .NET 10 Runtime / Library packages to the latest 10.0.8
servicing release for security fixes.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

Updates the runtime-related package versions in
`Directory.Packages.props` from `10.0.7` to `10.0.8`.

## Validation Steps Performed

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-05-26 14:57:35 +08:00
Michael Clayton
fe985e7eea Ready To Review - [MouseWithoutBorders] - incremental code cleanup / refactor (#44553)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Some focussed refactoring / simplifying / cleanup / delinting on the
Mouse Without Borders codebase (see [#44508 - [Mouse Without Borders] -
de-linting
codebase](https://github.com/microsoft/PowerToys/issues/44508)) now that
the Common class has been broken down.

This PR does some cleaning up on the ```Logger``` class:

* Uplifting coding style (string interpolation, pattern matching,
```var```, etc)
* Rationalising and simplifying code
* Relocating e.g. IO and UI side effects (writing to disk, displaying
dialog boxes) outside of Logger class
* Removing dead code, tightening visibility of existing code
* Added / updated tests to try to cover as much of the refactoring as
possible to prevent regressions

I've split the changes into lots of small commits - it might be easier
to review the individual commits rather than the whole PR in one go.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

### Run manual tests from [Test Checklist
Template](5bc7201ae2/doc/releases/tests-checklist-template.md (mouse-without-borders)):

* Install PowerToys on two PCs in the same local network:
   - [x]     Verify that PowerToys is properly installed on both PCs.
   - [x]     Configure Windows Firewall Rules
- ```netsh advfirewall firewall add rule
name="PowerToys.MouseWithoutBorders - mc" dir=in action=allow
program="C:\src\mc\PowerToys\x64\Debug\PowerToys.exe" enable=yes
remoteip=any profile=any protocol=tcp```
   
 * Setup Connection:
- [x] Open MWB's settings on the first PC and click the "New Key"
button. Verify that a new security key is generated.
- [x] Copy the generated security key and paste it in the corresponding
input field in the settings of MWB on the second PC. Also enter the name
of the first PC in the required field.
- [x] Press "Connect" and verify that the machine layout now includes
two PC tiles, each displaying their respective PC names.
   
 * Verify Connection Status:
- [x] Ensure that the border of the remote PC turns green, indicating a
successful connection.
- [x] Enter an incorrect security key and verify that the border of the
remote PC turns red, indicating a failed connection.
   
 * Test Remote Mouse/Keyboard Control:
- [x] With the PCs connected, test the mouse/keyboard control from one
PC to another. Verify that the mouse/keyboard inputs are correctly
registered on the other PC.
- [ ] Test remote mouse/keyboard control across all four PCs, if
available. Verify that inputs are correctly registered on each connected
PC when the mouse is active there.
     - unable to test - only 2 machines available
   
 * Test Remote Control with Elevated Apps:
- note - the main PowerToys.exe must be running as a **non**-admin for
these tests
- [x] Open an elevated app on one of the PCs. Verify that without "Use
Service" enabled, PowerToys does not control the elevated app.
- [x] Enable "Use Service" in MWB's settings (need to run PowerToys.exe
as admin to enable "Use Service", then restart PowerToys.exe as
non-admin). Verify that PowerToys can now control the elevated app
remotely. Verify that MWB processes are running as LocalSystem, while
the MWB helper process is running non-elevated.
- ```get-process -Name "PowerToys.MouseWithoutBorders*" -IncludeUserName
| format-table Id, ProcessName, UserName```
- [x] Process: ```PowerToys.MouseWithoutBorders.exe``` - running as
```SYSTEM```
- [x] Process: ```PowerToys.MouseWithoutBorders.Helper.exe``` - running
as current user
- ```get-service -Name "PowerToys.*" | ft Status, Name, UserName;
get-ciminstance -Class "Win32_Service" -Filter "Name like 'PowerToys%'"
| ft ProcessId, Name```
- [ ] Service: ```PowerToys.MWB.Service``` - running as ```Local
System```
- [x] Toggle "Use Service" again, verify that each time you do that, the
MWB processes are restarted.
- [ ] Run PowerToys elevated on one of the machines, verify that you can
control elevated apps remotely now on that machine.

* Test Module Enable Status:
- [ ] For all combinations of "Use Service"/"Run PowerToys as admin",
try enabling/disabling MWB module and verify that it's indeed being
toggled using task manager.
   
 * Test Disconnection/Reconnection:
- [ ] Disconnect one of the PCs from network. Verify that the machine
layout updates to reflect the disconnection.
   - [ ]     Do the same, but now by exiting PowerToys.
   - [ ]     Start PowerToys again, verify that the PCs are reconnected.
  
 * Test Various Local Network Conditions:
- [ ] Test MWB performance under various network conditions (e.g., low
bandwidth, high latency). Verify that the tool maintains a stable
connection and functions correctly.

 * Clipboard Sharing:
- [ ] Copy some text on one PC and verify that the same text can be
pasted on another PC.
- [ ] Use the screenshot key and Win+Shift+S to take a screenshot on one
PC and verify that the screenshot can be pasted on another PC.
- [ ] Copy a file in Windows Explorer and verify that the file can be
pasted on another PC. Make sure the file size is below 100MB.
- [ ] Try to copy multiple files and directories and verify that it's
not possible (only the first selected file is being copied).
 
 * Drag and Drop:
- [ ] Drag a file from Windows Explorer on one PC, cross the screen
border onto another PC, and release it there. Verify that the file is
copied to the other PC. Make sure the file size is below 100MB.
- [ ] While dragging the file, verify that a corresponding icon is
displayed under the mouse cursor.
- [ ] Without moving the mouse from one PC to the target PC, press
CTRL+ALT+F1/2/3/4 hotkey to switch to the target PC directly and verify
that file sharing/dropping is not working.

 * Lock and Unlock with "Use Service" Enabled:
   - [ ]     Enable "Use Service" in MWB's settings.
- [ ] Lock a remote PC using Win+L, move the mouse to it remotely, and
try to unlock it. Verify that you can unlock the remote PC.
- [ ] Disable "Use Service" in MWB's settings, lock the remote PC, move
the mouse to it remotely, and try to unlock it. Verify that you can't
unlock the remote PC.

 * Test Settings:
- [ ] Change the rest of available settings on MWB page and verify that
each setting works as described.

### Group Policy Tests

See https://learn.microsoft.com/en-us/windows/powertoys/grouppolicy

- [ ] Install *.admx / *.adml and check settings behave as expected
  - [ ] I'll expand the list of settings here when I get this far :-)
- [ ] HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys
  - [x]     ConfigureEnabledUtilityMouseWithoutBorders
- [x] ```[missing]``` - "Activation -> Enable Mouse Without Borders"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /f```
- [x] ```0``` - "Activation -> Enable Mouse Without Borders" set to
"off" and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Activation -> Enable Mouse Without Borders" set to "on"
and disabled, with GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
ConfigureEnabledUtilityMouseWithoutBorders /t REG_DWORD /d 1 /f```
  - [ ] MwbClipboardSharingEnabled
  - [ ] MwbFileTransferEnabled
  - [ ] MwbUseOriginalUserInterface
  - [ ] MwbDisallowBlockingScreensaver
  - [ ] MwbSameSubnetOnly
  - [ ] MwbValidateRemoteIp
  - [x]     MwbDisableUserDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /f```
- [x] ```0``` - "Advanced Settings -> IP address mapping" enabled, with
GPO warning hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 0 /f```
- [x] ```1``` - "Advanced Settings -> IP address mapping" disabled, with
GPO warning visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbDisableUserDefinedIpMappingRules /t REG_DWORD /d 1 /f```
  - [x]     MwbPolicyDefinedIpMappingRules
- [x] ```[missing]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning and GPO values hidden
- ```reg delete HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /f```
- [x] ```[empty value]``` - "Advanced Settings -> IP address mapping"
enabled, with GPO warning hidden and GPO values hidden
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "" /f```
- [x] ```[non-empty value]``` - "Advanced Settings -> IP address
mapping" enabled, with GPO warning visible and GPO values visible
- ```reg add HKEY_LOCAL_MACHINE\SOFTWARE\Policies\PowerToys /v
MwbPolicyDefinedIpMappingRules /t REG_MULTI_SZ /d "aaa 10.0.0.1\0bbb
10.0.0.2" /f```
2026-05-26 14:54:15 +08:00
Niels Laute
b3bf154fa5 [General] Update issue tracker's duplicate resolution message (#47981)
Reopens the change from #46743 (which appears to be broken) on a fresh
branch.

Original author: @daverayment

## Summary

GitHub newcomers can be confused by the current duplicate resolution
message, as it doesn't clearly point to the original referenced issue -
see
https://github.com/microsoft/PowerToys/issues/46347#issuecomment-4103681050.
They may not realise that the #12345 in the duplicate comment is the
relevant link.

This small wording update to the duplicate resolution message tightens
up wording slightly and includes reference to the prior `/dup #nnn`
comment so newcomers don't miss it.

### Before
> Hi! We've identified this issue as a duplicate of another one that
already exists on this Issue Tracker. This specific instance is being
closed in favor of tracking the concern over on the referenced thread.
Thanks for your report!

### After
> We've identified this issue as a duplicate of an existing one and are
closing this thread so discussion stays in one place.<br/><br/>Please
see the comment above for the link to the original tracking issue, and
feel free to subscribe there for updates.

## Validation Steps Performed
N/A - bot reply text change only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 13:44:34 +08:00
Niels Laute
ab553f9930 [CmdPal][PerfMon] Use distinct up/down arrow icons for network Send/Receive (#48118)
## Summary of the Pull Request

On the Command Palette Performance Monitor extension's Network widget
page, the **Send** and **Receive** list items both used the generic
`NetworkIcon` (`\uEC05`), making them visually indistinguishable at a
glance.

This PR gives each direction its own glyph:
- Send → up arrow `\uE74A`
- Receive → down arrow `\uE74B`

The redundant `↑`/`↓` characters are removed from the Send/Receive
subtitles since the icons now carry that meaning.

Before vs after:

<img width="918" height="122" alt="image"
src="https://github.com/user-attachments/assets/4af3a2fc-d5a7-4fb5-98c6-f1889c7e80f2"
/>


## PR Checklist

- [x] **Closes:** N/A (no existing issue found)
- [x] **Communication:** I've discussed this with collaborators
- [x] **Tests:** Manually verified
- [x] **Localization:** Updated en-US resw (other locales still contain
the arrow characters and can be translated/updated by the loc pipeline)

## Detailed Description of the Pull Request / Additional comments

Files changed:
- `Icons.cs` – added `NetworkUpIcon` and `NetworkDownIcon`
- `PerformanceWidgetsPage.cs` – set `Icon` on `_networkUpItem` and
`_networkDownItem`
- `Strings/en-US/Resources.resw` – `Send ↑` → `Send`, `Receive ↓` →
`Receive`

## Validation Steps Performed

Local visual verification in Command Palette.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 06:29:00 +02:00
Gordon Lam
85b9191b7c [AdvancedPaste] Harden ToJsonFromXmlOrCsvAsync against clipboard read failures (#48124)
## Summary

`ToJsonFromXmlOrCsvAsync` in `AdvancedPaste/Helpers/JsonHelper.cs`
documents that it never throws and returns an empty string on any
failure. The clipboard read at the top of the method
(`clipboardData.GetTextAsync()`) was not wrapped, so a transient
clipboard failure could surface as an exception to callers, contrary to
the documented contract.

This PR:

- Wraps `GetTextAsync()` in a try/catch and returns `string.Empty` on
failure, matching the pattern already used by the JSON/XML/CSV parsing
branches further down in the same method.
- Updates the matching unit test to decode input bytes as UTF-8
(`Encoding.UTF8.GetString(input)`) and consume the awaited task via
`GetAwaiter().GetResult()`, for consistency with sibling tests elsewhere
in the solution.

## Validation

- Local build of `AdvancedPaste.sln`. (Note: my machine has a
pre-existing NuGet SDK resolver issue unrelated to this change — the
same baseline fails on `main` for me. CI should be the source of truth.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 10:59:24 +08:00
Marcello Morena
cb174210cb Add CmdPal debugging information (#48108)
## Summary of the Pull Request
Adding information about using the Command Palette Visual Studio
solution filter based on
https://github.com/microsoft/PowerToys/issues/47997#issuecomment-4524045763.

## PR Checklist

- [ ] Closes: #47997 

## Detailed Description of the Pull Request / Additional comments
I found this really useful and couldn't see a reference to it anywhere
else in the devdocs. Not sure if this is the best way to describe the
process, but I think adding this information somewhere in the debugging
doc would really help newcomers to CmdPal development!
2026-05-25 17:32:57 +00:00
moooyo
f175a9c96a [PowerDisplay] Confirm before enabling module; log EdidId in Phase 0 (#48111)
## Summary of the Pull Request

Two crash-correlation aids for the kernel-side DDC/CI BSOD mitigated by
#47734:

1. Log EDID hardware ID (manufacturer + product code, e.g. `DELD1A8`)
during Phase 0 monitor classification, before any DDC/CI capability
fetch enters the BSOD risk window.
2. Show a confirmation dialog before turning the Power Display module on
from the Settings page, so the user understands the BSOD risk before the
first capability fetch runs.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

### 1. Phase 0 EdidId logging

`MonitorIdentity.EdidIdFromDevicePath` parses the EDID hardware ID
segment from a DevicePath of the form
``\?\DISPLAY#DELD1A8#5&abc&0&UID12345#{guid}`` and returns ``DELD1A8``.
The 3-letter PNP manufacturer code + 4-hex product code is identical for
every physical unit of the same model, so it identifies the *model*
without leaking per-unit identifiers.

`MonitorManager` logs the EdidId on the existing Phase 0 classification
line. Phase 0 uses `QueryDisplayConfig`, which reads OS-cached EDID and
cannot BSOD, so this line is guaranteed on disk before the crash-prone
Phase 2 capability fetch starts. If a machine crashes during
enumeration, the recovered log identifies every attached model
(including same-model duplicates), which makes it possible to correlate
crash reports to specific monitor models even when the user can't tell
us which monitor caused the crash.

### 2. Enable-module confirmation dialog

`PowerDisplayViewModel.IsEnabled` setter is refactored to follow the
same two-phase pattern already used by `MaxCompatibilityMode`:

- `false → true` does not commit immediately; it kicks off
`ConfirmAndEnableModuleAsync`, which awaits the existing
`DangerousFeatureWarningDialog` (resource prefix
`PowerDisplay_EnableModule`) and either commits or reverts the
ToggleSwitch via `OnPropertyChanged`.
- `true → false` commits unconditionally — we never block a user who
wants to turn the module off.
- App-startup loads via `InitializeEnabledValue()` /
`RefreshEnabledState()` assign the `_isEnabled` field directly,
bypassing the setter, so the dialog never fires on settings restore or
GPO refresh.
- GPO-configured state still short-circuits before any dialog logic.

The dialog reuses the existing `DangerousFeatureWarningDialog` injected
by `PowerDisplayPage.xaml.cs`. The 5 new `PowerDisplay_EnableModule_*`
strings explain that the BSOD is in Windows (not Power Display), that
Power Display will auto-disable itself after a detected crash, and that
the user has to re-enable + dismiss the warning each time.

## Validation Steps Performed

- Built `src/settings-ui` and `src/modules/powerdisplay` locally.
- Unit tests: added `EdidIdFromDevicePath_*` cases to
`MonitorIdentityTests`, all green.
- Settings UI manual: toggling Power Display ON now shows the warning
dialog. Pressing Cancel reverts the ToggleSwitch visually; pressing
Enable commits and the module starts. Toggling OFF does not prompt.
Restarting Settings UI with PowerDisplay enabled does not prompt.
GPO-disabled state still locks the toggle.
- Log inspection: `MonitorManager` Phase 0 log now shows `EdidId=...`
for each path before any capability fetch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:08:26 +00:00
moooyo
273d735a8b [PowerDisplay] Confirm before enabling module; log EdidId in Phase 0 (#48111)
## Summary of the Pull Request

Two crash-correlation aids for the kernel-side DDC/CI BSOD mitigated by
#47734:

1. Log EDID hardware ID (manufacturer + product code, e.g. `DELD1A8`)
during Phase 0 monitor classification, before any DDC/CI capability
fetch enters the BSOD risk window.
2. Show a confirmation dialog before turning the Power Display module on
from the Settings page, so the user understands the BSOD risk before the
first capability fetch runs.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

### 1. Phase 0 EdidId logging

`MonitorIdentity.EdidIdFromDevicePath` parses the EDID hardware ID
segment from a DevicePath of the form
``\?\DISPLAY#DELD1A8#5&abc&0&UID12345#{guid}`` and returns ``DELD1A8``.
The 3-letter PNP manufacturer code + 4-hex product code is identical for
every physical unit of the same model, so it identifies the *model*
without leaking per-unit identifiers.

`MonitorManager` logs the EdidId on the existing Phase 0 classification
line. Phase 0 uses `QueryDisplayConfig`, which reads OS-cached EDID and
cannot BSOD, so this line is guaranteed on disk before the crash-prone
Phase 2 capability fetch starts. If a machine crashes during
enumeration, the recovered log identifies every attached model
(including same-model duplicates), which makes it possible to correlate
crash reports to specific monitor models even when the user can't tell
us which monitor caused the crash.

### 2. Enable-module confirmation dialog

`PowerDisplayViewModel.IsEnabled` setter is refactored to follow the
same two-phase pattern already used by `MaxCompatibilityMode`:

- `false → true` does not commit immediately; it kicks off
`ConfirmAndEnableModuleAsync`, which awaits the existing
`DangerousFeatureWarningDialog` (resource prefix
`PowerDisplay_EnableModule`) and either commits or reverts the
ToggleSwitch via `OnPropertyChanged`.
- `true → false` commits unconditionally — we never block a user who
wants to turn the module off.
- App-startup loads via `InitializeEnabledValue()` /
`RefreshEnabledState()` assign the `_isEnabled` field directly,
bypassing the setter, so the dialog never fires on settings restore or
GPO refresh.
- GPO-configured state still short-circuits before any dialog logic.

The dialog reuses the existing `DangerousFeatureWarningDialog` injected
by `PowerDisplayPage.xaml.cs`. The 5 new `PowerDisplay_EnableModule_*`
strings explain that the BSOD is in Windows (not Power Display), that
Power Display will auto-disable itself after a detected crash, and that
the user has to re-enable + dismiss the warning each time.

## Validation Steps Performed

- Built `src/settings-ui` and `src/modules/powerdisplay` locally.
- Unit tests: added `EdidIdFromDevicePath_*` cases to
`MonitorIdentityTests`, all green.
- Settings UI manual: toggling Power Display ON now shows the warning
dialog. Pressing Cancel reverts the ToggleSwitch visually; pressing
Enable commits and the module starts. Toggling OFF does not prompt.
Restarting Settings UI with PowerDisplay enabled does not prompt.
GPO-disabled state still locks the toggle.
- Log inspection: `MonitorManager` Phase 0 log now shows `EdidId=...`
for each path before any capability fetch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 09:08:06 +00:00
Copilot
bcf0b685ac [File Locksmith] Fix IPC text-mode file I/O corrupting Unicode paths (#47361)
## Summary of the Pull Request

The File Locksmith IPC layer reads and writes raw UTF-16 (WCHAR) bytes
to `last-run.log`, but all three stream opens were using the default
text mode. On Windows, the CRT translates `0x0A` bytes to `0x0D 0x0A` on
write and collapses `0x0D 0x0A` back to `0x0A` on read. Because each
WCHAR is 2 bytes, any code unit whose little-endian byte pair contains
`0x0A` in the low position (e.g. `U+010A`, `U+0A0D`) is silently
corrupted. The fix opens all three streams in binary mode and adds an
explicit open-failure guard.

```cpp
// IPC.cpp — Writer::start()
// Before
m_stream = std::ofstream(path);
// After
m_stream = std::ofstream(path, std::ios::binary);
// + is_open() guard returning E_FAIL on failure

// NativeMethods.cpp — StartAsElevated() writer
// Before
std::ofstream stream(paths_file());
// After
std::ofstream stream(paths_file(), std::ios::binary);

// NativeMethods.cpp — ReadPathsFromFile() reader
// Before
std::ifstream stream(paths_file());
// After
std::ifstream stream(paths_file(), std::ios::binary);
```

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

Three targeted changes across two files:

1. **`FileLocksmithLib/IPC.cpp` — `Writer::start()`**: switched
`std::ofstream` from text to binary mode and added an `is_open()` check
that returns `E_FAIL` immediately when the file cannot be opened
(previously the try/catch did not catch a silent open failure because
`std::ofstream` does not throw by default).

2. **`FileLocksmithLibInterop/NativeMethods.cpp` —
`StartAsElevated()`**: switched `std::ofstream` from text to binary
mode. This is the elevated-restart writer path; without this fix,
Unicode corruption persisted when File Locksmith relaunched as
administrator.

3. **`FileLocksmithLibInterop/NativeMethods.cpp` —
`ReadPathsFromFile()`**: switched `std::ifstream` from text to binary
mode. This is the symmetric reader-side bug — even with both writers
corrected, the CRT text-mode reader could collapse a `0x0D 0x0A` byte
pair (a valid UTF-16 LE code unit, e.g. U+0A0D GURMUKHI EK ONKAR) into a
single byte, desynchronising the 2-bytes-at-a-time read loop and
corrupting all subsequent path data.

No behaviour change for purely ASCII paths. Paths containing Unicode
code points whose little-endian UTF-16 byte pair spans `0x0D 0x0A` were
silently corrupted in all three code paths before this fix.

## Validation Steps Performed

- Code review: no issues flagged.
- Full diff reviewed: all three stream opens (`ofstream` writer in
`IPC.cpp`, `ofstream` writer in `NativeMethods.cpp`, `ifstream` reader
in `NativeMethods.cpp`) now use `std::ios::binary`, making write and
read paths byte-exact and symmetric.
- Mechanically correct: `std::ios::binary` suppresses Windows CRT
`\n`↔`\r\n` translation; the delimiter `L'\n'` (LE bytes `0x0A 0x00`) is
unambiguous in binary mode and is handled correctly by the existing read
loop.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 16:30:15 +08:00
Boliang Zhang
6a5e320749 [CI] Prune orphan tokens from check-spelling expect.txt (#48110)
## Problem

Since #47119 (`Refresh check-spelling 0.0.26`, merged 2026-04-23)
refreshed the check-spelling tooling and rewrote
`.github/actions/spell-check/expect.txt` (938 lines / 633 deletions),
the check-spelling bot has been leaving a noisy advisory comment on
**every PR**:

> #### These words are not needed and should be removed
> ABlocked AClient AColumn ACR ADate ADifferent AHybrid ALarger
AModifier ANull AOklab APeriod ARandom ARemapped ASingle ASUS bck …

The same ~150-word list is appended verbatim to every PR the bot looks
at (verified against #48058, #48102, #48104 — the list is identical).
These tokens are residual orphans in `expect.txt` from before the 0.0.26
refresh and no longer match anything in source.

## Fix

Removes exactly the 147 orphan tokens that the bot has consistently
flagged as `now absent` from `.github/actions/spell-check/expect.txt`.
The removed tokens are exclusively the ones the bot itself identified.

All uppercase Win32 / DirectWrite identifiers that are still used in
source (`DWRITE`, `LWIN`, `VCENTER`, `VREDRAW`, etc.) are **preserved**.

## Verification

- Diff is a single file, deletions only: `expect.txt` shrinks from 2343
→ 2196 lines.
- Each of the 4 uppercase Win32 tokens (`DWRITE` line 514, `LWIN` 1074,
`VCENTER` 2105, `VREDRAW` 2144 in the original) remains in the file.
- The check-spelling job on this PR should now post a clean report (no
`should be removed` block).

## Background — which PR introduced the drift

| PR | Date | What it changed |
|----|------|-----------------|
| **#47119** | 2026-04-23 | Refreshed check-spelling to 0.0.26; rewrote
`expect.txt` with 938 line-changes (633 deletions, 305 additions). The
duplicated lowercase/uppercase entries and many obsolete tokens
originate here. |

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-05-25 16:29:42 +08:00
Michael Jolley
83285e929a CmdPal: Enable entrance animation for End dock bands (#48099)
## Summary

Fixes #46767

Items pinned to the End section of the dock did not animate on startup,
while Start and Center items did. The EndListView had an explicit empty
`ItemContainerTransitions` collection that suppressed all container
transitions. Removing it allows the default WinUI entrance animations to
play, matching Start and Center behavior.

## Changes

- `DockControl.xaml`: Remove empty `TransitionCollection` override from
EndListView

---------

Co-authored-by: root <root@io.bbq>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 08:31:09 +02:00
Boliang Zhang
26108ff04b [CI] Sign PowerAccent.Common.dll (new managed library from #47211) (#48058)
## Summary

Fixes the **stable** signed release pipeline failure (Dart build
[`147621162`](https://microsoft.visualstudio.com/Dart/_build/results?buildId=147621162))
at the *"Verify all binaries are signed and versioned"* task:

```
Not Signed: + …\extractedMachineMsi\File\BaseApplicationsFiles_File_PowerAccent.Common.dll
```

## Root cause

PR #47211 (*"[Quick Accent] Move language data to PowerAccent.Common
library, refactor"*) introduced the new managed library:

- `src/modules/poweraccent/PowerAccent.Common/PowerAccent.Common.csproj`

`PowerAccent.Common.dll` is referenced by both `PowerAccent.Core` (ships
in the installer root) and `PowerToys.Settings` (ships in WinUI3Apps;
deduplicated against root by `generateAllFileComponents.ps1`). The DLL
is harvested into the MSI's `BaseApplicationsFiles` component group, but
the PR did not update `.pipelines/ESRPSigning_core.json`, so ESRP never
signs it and `versionAndSignCheck.ps1` correctly fails the build.

This is the same kind of omission that PR #48050 fixed for
`YamlDotNet.dll` introduced by #40834 — a new managed library shipping
in the MSI without a matching signing config entry.

## Fix

One additive line in `.pipelines/ESRPSigning_core.json`, placing
`"PowerAccent.Common.dll"` inside the existing alphabetized PowerAccent
block (next to `PowerAccent.Core.dll`).

```diff
             "PowerAccent.Core.dll",
+            "PowerAccent.Common.dll",
             "PowerToys.PowerAccent.dll",
```

## Validation

- `git diff` shows exactly one additive line.
- File still parses as valid JSON (`ConvertFrom-Json` round-trip OK).
- Audited the full file list of PR #47211: `PowerAccent.Common.dll` is
the only new shippable assembly it added (the other new project,
`PowerAccent.Common.UnitTests`, is a test project and is not included in
the installer).
- Dedup logic in
`installer/PowerToysSetupVNext/generateAllFileComponents.ps1` keeps only
the root copy when WinUI3Apps and root copies are byte-identical, so a
single root-level entry is sufficient.

## Follow-up

After merge to `main`, cherry-pick / merge into `stable` to unblock the
0.100 release pipeline. This is the third (and hopefully final) PR in
the 0.100 release-pipeline unblock sequence, after #48050 (sign
`YamlDotNet.dll`) and #48054 (remove duplicate
`QuickAccent_SelectedLanguage_Greek_Polytonic` resource).

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 11:43:17 +08:00
Noraa Junker
fed9e81fdc [Shortcut Guide V2] Fixes (#48043)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

* Fix wrong Shortcut for Shortcut Guide V2 (previously always showed
default)
* Fix outdated OOBE description
* Fix process not stopping when exiting via ESC or close button
* Fixed some missing modules
* Removed regex capability and was able to improve the start up time
through this

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-24 18:08:54 +08:00
Mike Griese
f998c38ac8 cmdpal: fix an AOT-only crash loading the extension page (#48065)
Okay I'll admit this is a clanker fix. 

There's currently a crash when you navigate to an individual extension
page, that only repro's in Release/AOT. And because of
release&trimming&AOT reasons, that's very very difficult to diagnose.
But the clanker added a shitload of logging, and was able to figure out
that:

We were binding to this `Screenshots` collection. Problem is that we
can't safely bind to an `IReadOnlyList` in trimmed scenarios. C#/AOT is
a wonderful world of horrors.

Fixing this is as simple as swapping it for a `ObservableCollection`.
2026-05-23 05:56:14 -05:00
Mike Griese
11083c9fb2 CmdPal: Successfully open when opened on other displays (#48061)
When we were docked on a screen other than the primary display, then
clicking on a dock item that was a `IPage` would result in the palette
window just not showing up at all.

The fix is to make sure that we use the actual position of the current
monitor in the calculation of the final position of the palette HWND.
2026-05-23 05:55:51 -05:00
Boliang Zhang
809601a33c [CmdPal] Add stable AutomationIds across XAML for UI testing (#48033)
## Summary

Adds **77 explicit `AutomationProperties.AutomationId`** declarations (4
→ 81, ~20x) to Command Palette XAML so UI-testing tools (WinAppDriver,
FlaUI, winappCli, Appium) can address controls deterministically instead
of falling back to runtime slugs that change every session. Also covers
the `DataTemplate`-generated long tail (search results, file results,
every extension item, every monitor in Dock settings) by binding
`AutomationId` to existing stable model identifiers via `{x:Bind}`.

Headline: a one-line addition to the four CmdPal `ListItem`
`DataTemplate`s now exposes each instance's underlying `Command.Id`
(e.g. `com.microsoft.cmdpal.builtin.calculator`) as its `AutomationId`.
This is the same pattern as web's `<li data-testid={item.id}>`.

## Why

CmdPal today ships with only **4** explicit
`AutomationProperties.AutomationId` declarations across **44** XAML
files (~303 interactive controls). UI tests are forced to fall back to:

- runtime-generated slugs like `itm-12-9dda` that change every CmdPal
restart, or
- regex-parsing the text output of `winappCli ui inspect`, or
- walking the UIA tree client-side and matching by `Name` (localized,
ambiguous when several controls share a label).

## What changed

### Source changes
- **`Microsoft.CmdPal.UI/ExtViews/ListItemsView.xaml`** — bind
`AutomationProperties.AutomationId="{x:Bind Command.Id, Mode=OneWay}"`
on the root of all 4 `ListItem` `DataTemplate`s (single-row, small-grid,
medium-grid, gallery). Covers **every** runtime list item across all
built-in and third-party providers.
- **`Microsoft.CmdPal.UI/Settings/GeneralPage.xaml`** — 14 IDs on
activation hotkey controls, auto-go-home combo, behaviour toggles,
about-section hyperlinks.
- **`Microsoft.CmdPal.UI/Settings/AppearancePage.xaml`** — 19 IDs on
theme/backdrop/colorization, background image controls, 5 sliders,
behaviour toggles.
- **`Microsoft.CmdPal.UI/Settings/DockSettingsPage.xaml`** — 14 IDs on
enable toggle, theme/colorization/backdrop, background image controls,
plus per-monitor data-bound ID using `DisplayName`.
- **`Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml`** — 4 IDs on
banner/store hyperlinks plus the per-provider `SettingsCard` (data-bound
to the provider's `Id`, e.g. `com.microsoft.cmdpal.builtin.calculator`).
- **`Microsoft.CmdPal.UI/Settings/ExtensionPage.xaml`** — 5 IDs on
provider enable toggle, alias text/activation, fallback rank link.
- **`Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml`** — 5
IDs on install/cancel/copy-command buttons and author/repo/uninstall
links.
- **`Microsoft.CmdPal.UI/Settings/InternalPage.xaml`** — 7 IDs on the
dev-only "Throw exception" and folder/log-opening buttons.
- **`Microsoft.CmdPal.UI/Pages/ShellPage.xaml`** — 1 data-bound ID on
the `CommandTemplate` button (binds to `CommandViewModel.Id`).
- **`Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs`** —
single-line addition exposing the underlying `CommandProviderWrapper.Id`
so the ExtensionsPage template can bind to it.

### Naming convention applied

`CmdPal_<Page>_<Semantic>` PascalCase, with the existing well-known IDs
left untouched to avoid breaking the `modules/cmdpal/*` checklist tests
already referencing them. `Id` / `TestId` suffixes deliberately omitted
(the attribute is already `AutomationId`); control-type suffixes
(`Toggle` / `Button` / `ComboBox`) omitted because the type lives on the
UIA `type` field already.

### Files NOT touched (deferred)

About 5–7 secondary files (`SettingsWindow.xaml`,
`ExtensionGalleryPage.xaml`, `DockControl.xaml`, `DevRibbon.xaml`, deep
style/viewer XAML) — their interactive controls already carry `x:Name`,
which WinUI auto-promotes to `AutomationId` at runtime, so practical
testability is mostly already there. Can be tightened up in a follow-up
PR.

## Risk

-  Zero runtime cost (`AutomationProperties.AutomationId` is metadata
only)
-  Does not affect localization (`AutomationId` is non-localized by
design)
-  Does not affect user-facing accessibility (screen readers consume
`Name` / `AutomationProperties.Name`, untouched)
-  Does not rename any existing ID; `modules/cmdpal/*` tests
referencing `MainSearchBox` / `PrimaryCommandButton` / `ItemsList` /
etc. remain valid
-  All 51 CmdPal XAML files parse as valid XML (PowerShell `[xml]`
round-trip)
-  All `x:Bind` paths verified against the relevant ViewModel types
(`CommandViewModel.Id`, `DockMonitorConfigViewModel.DisplayName`,
`ProviderSettingsViewModel.Id`)

## Validation requested before merge

XAML compilation was attempted locally but blocked by a missing native
NuGet package (`Microsoft.Windows.ImplementationLibrary`) — needs
`tools\build\build-essentials.cmd` to fully restore first. **Reviewer
please run a clean local build of `Microsoft.CmdPal.UI` (or rely on CI)
to confirm the XAML compiler is happy.**

Once built, a quick smoke check:

```powershell
# After CmdPal restart with the new bits installed:
winapp ui search 'CmdPal_'           -w <hwnd> --json  # should list all the new IDs
winapp ui invoke 'CmdPal_AppearancePage_OpenCommandPalette' -w <hwnd>
winapp ui invoke 'com.microsoft.cmdpal.builtin.calculator' -w <hwnd>   # ListItem template binding
```

## Why this matters beyond our tools

This is the same pattern Microsoft's accessibility and WinUI teams
already recommend for any XAML app intended to be automated. It's the
WinUI equivalent of adding `data-testid` in a web app — universally
accepted as low-risk, high-leverage. WinAppDriver, FlaUI, Appium and any
future automation framework all benefit from the same change.
2026-05-22 21:42:19 -05:00
Mike Griese
2c9481e69a cmdpal: update template project to 0.11 SDK (#48066)
title
2026-05-22 21:09:06 -05:00
Boliang Zhang
1d0917d06f [CI] Sign YamlDotNet.dll (ShortcutGuide V2 dependency) (#48050)
## Summary

Fixes the **stable** signed release pipeline failure (Dart build
[`147590319`](https://microsoft.visualstudio.com/Dart/_build/results?buildId=147590319))
at the *"Verify all binaries are signed and versioned"* task on both
`Build Release_x64` and `Build Release_arm64`:

```
Not Signed: + …\extractedMachineMsi\File\WinUI3ApplicationsFiles_File_YamlDotNet.dll
```

## Root cause

PR #40834 (*Shortcut Guide V2*) added a `YamlDotNet` `PackageReference`
to:

- `src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj`
-
`src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj`

Both projects publish into `WinUI3Apps\`, so `YamlDotNet.dll` is
harvested into the MSI by
`installer/PowerToysSetupVNext/generateAllFileComponents.ps1`. The PR
updated `ESRPSigning_core.json` for its own new binaries but missed this
3rd-party transitive dependency, so ESRP never signs it and
`.pipelines/versionAndSignCheck.ps1` correctly fails the build.

## Fix

One additive line to `.pipelines/ESRPSigning_core.json`, placing
`WinUI3Apps\YamlDotNet.dll` next to the other 3rd-party `WinUI3Apps\`
entries (`Google.Apis.*`, `Google.GenAI.dll`, `ReverseMarkdown.dll`,
`SharpCompress.dll`, …).

## Validation

- `git diff` shows exactly one additive line.
- File still parses as valid JSON (`ConvertFrom-Json` round-trip OK).
- Both ShortcutGuide csprojs target
`$(Platform)\$(Configuration)\WinUI3Apps`, so a single
`WinUI3Apps\YamlDotNet.dll` entry covers all uses.

## Follow-up

After merge to `main`, cherry-pick / merge into `stable` so build
`147590319` can be re-queued and the 0.100 release pipeline can proceed.

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 15:43:42 +00:00
moooyo
4edfcee87e [Power Display] Built-in monitor blacklist to mitigate DDC/CI BSOD (#47556, #47968) (#48051)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

Adds a built-in monitor blacklist that skips known-bad monitor models at
the DDC/CI discovery stage — before PowerDisplay sends any capabilities
request to the firmware. Matches by EdidId (the PnP manufacturer +
product code from EDID). Two entries pre-populated from existing crash
reports:

| EdidId | Reported in | Notes |

|-----------|-------------|------------------------------------------------------------------------|
| `LTM2C02` | #47556 | Counterfeit-EDID LG 27MR400 (PnP claims Litemax
40" 2011, actual hw is LG 27" 2024) |
| `GSM7714` | #47968 | LG UltraWide HDR WFHD |

Also logs `[EdidId=…] [FriendlyName=…] [DevicePath=…]` immediately
before each `GetCapabilitiesString*` syscall in `DdcCiController`. If
the kernel call BSODs (`win32kfull` stack-cookie overrun), that is the
last log line that survives — adding a new blacklist entry then takes
one PR to `BuiltInMonitorBlacklist.json` instead of a memory-dump
triage.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Mitigates: #47556
- [x] Mitigates: #47968
- [x] **Communication:** This is the "Add blacklist and prevent the API
call on broken monitor" mitigation outlined in [#47556
(comment)](https://github.com/microsoft/PowerToys/issues/47556#issuecomment-4505498427).
The underlying `win32kfull!DdcciGetCapabilitiesStringFromMonitor` stack
overrun is tracked separately by the Windows team.
- [x] **Tests:** 6 new MSTest unit tests under
`PowerDisplay.Lib.UnitTests`. All 120 PowerDisplay tests pass on x64
Debug.
- [ ] **Localization:** N/A — this PR ships no end-user-facing strings
(no UI surface).
- [ ] **Dev docs:** N/A.
- [ ] **New binaries:** N/A. Data ships as an `<EmbeddedResource>`
inside the existing `PowerDisplay.Models.dll`; no new .dll, no installer
change.
- [ ] **Documentation updated:** N/A.

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

### Threat model

Some retail monitor firmwares ship non-conformant DDC/CI capabilities
strings — typically oversized, not NUL-terminated, or otherwise
malformed. When PowerDisplay calls `GetCapabilitiesStringLength` /
`CapabilitiesRequestAndCapabilitiesReply` on these monitors, the request
flows into
`win32kfull!CPhysicalMonitorHandle::DdcciGetCapabilitiesStringFromMonitor`,
which copies the reply into a stack buffer without bounding the length.
The kernel detects its own stack cookie corruption on the epilogue check
and `__fastfail`s with `BUGCHECK 0x139 / 2
(STACK_COOKIE_CHECK_FAILURE)`.

The kernel-side overrun is a Windows defect, not PowerDisplay's, but
PowerDisplay is currently the most widely deployed consumer of this API
on hot-plug. Until the Windows team fixes the kernel, the only safe
mitigation in user space is to **never call** the capabilities API on
monitor models known to trigger it.

### Architecture

| File | Role |
|---|---|
| `PowerDisplay.Models/MonitorBlacklistEntry.cs` | POCO: `{ edidId,
comments }` |
| `PowerDisplay.Models/BuiltInMonitorBlacklist.json` | Data file
(embedded resource) |
| `PowerDisplay.Models/BuiltInMonitorBlacklist.cs` | Lazy-loaded reader,
AOT-safe (source-gen `JsonSerializerContext`); silent fallback to empty
list on any IO/parse failure |
| `PowerDisplay.Lib/Services/MonitorBlacklistService.cs` |
`IsBlocked(monitorId)` — extracts EdidId via
`MonitorIdentity.EdidIdFromMonitorId`, checks a `HashSet<string>` with
`OrdinalIgnoreCase` |
| `PowerDisplay/Helpers/MonitorManager.cs` | Filters QueryDisplayConfig
inventory by EdidId **before** any controller (DDC/CI or WMI) is
dispatched; logs each skip with `[MonitorBlacklist] Skipping ...` |
| `PowerDisplay.Lib/Drivers/DDC/DdcCiController.cs` | Logs EdidId +
FriendlyName + DevicePath immediately before the first capabilities
syscall per-monitor |

Matching is **model-level granularity** (EdidId, e.g. `GSM7714`) rather
than device-level (full DevicePath). One entry covers every physical
port and every machine with the same monitor model.

### Adding new entries

Once the kernel overrun is fixed this list can shrink. Until then, when
a new BSOD surfaces:

1. User reports BSOD, attaches PowerDisplay log
2. The last line before the crash now reads `DDC: probing capabilities
[EdidId=XXXXXX] [FriendlyName='...'] [DevicePath=...]`
3. Add `{ "edidId": "XXXXXX", "comments": "See #issue" }` to
`BuiltInMonitorBlacklist.json`
4. Submit PR, ship in next release

### Scoped out (deliberate)

A user-customized blacklist surface (Settings UI dialog) was prototyped
and hit multiple WinUI 3 dialog parse / measure crashes inside a
`ContentDialog`. The custom-list code path was reverted in favor of
"built-in only" for v1 reliability. UI can be re-added later on top of
this foundation if needed — the data model and service already support
it.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

1. **Unit tests:** `dotnet test` on `PowerDisplay.Lib.UnitTests` →
**120/120 pass** (including 6 new tests for loader + service: built-in
JSON loads, EdidIds are normalized to upper-case, no blank entries,
`Lazy<>` cache returns same instance, `IsBlocked` returns `false` for
empty built-in list and for unidentifiable monitor IDs).
2. **Clean x64 Debug build** via `tools\build\build-essentials.cmd` +
per-project `tools\build\build.cmd`:
   - `PowerDisplay.Models` — 0 warning, 0 error
   - `PowerDisplay.Lib` — 0 warning, 0 error
   - `PowerDisplay` (app) — 0 warning, 0 error
3. **Smoke test (manual):** launched PowerDisplay, confirmed every
connected monitor produces a `DDC: probing capabilities [EdidId=…]
[FriendlyName=…] [DevicePath=…]` log line right before its caps request.
4. **Empty-list case:** `BuiltInMonitorBlacklist.json` with no entries →
`IsBlocked` returns `false` for any input (verified via unit test).
5. **Non-empty case (manual):** temporarily added a connected monitor's
EdidId to the built-in JSON → that monitor disappears from
`MonitorManager.Monitors` and `[MonitorBlacklist] Skipping ...` log line
appears; removed the entry → monitor reappears, all per-monitor settings
intact.

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2026-05-22 23:18:13 +08:00
Boliang Zhang
33cf6995fc [Settings UI] Remove duplicate QuickAccent_SelectedLanguage_Greek_Polytonic resource (#48054)
## Summary

Fixes the **"Build PowerToys main project"** failure on every PR/CI
build off current `main` (e.g. Dart build
[`147597426`](https://microsoft.visualstudio.com/Dart/_build/results?buildId=147597426)):

```
WINAPPSDKGENERATEPROJECTPRIFILE: Error PRI175: Processing Resources failed with error: Duplicate Entry.
WINAPPSDKGENERATEPROJECTPRIFILE: Error PRI277: Conflicting values for resource
                                  'Resources/QuickAccent_SelectedLanguage_Greek_Polytonic'
```

## Root cause

`src/settings-ui/Settings.UI/Strings/en-us/Resources.resw` contained two
`<data name="QuickAccent_SelectedLanguage_Greek_Polytonic">` entries:

- Line 3597 — original, added by #47021 (*"[PowerAccent] adding greek
polytonic"*), placed in the alphabetized QuickAccent block.
- Line 6175 — duplicate, appended at the file tail by #47211 (*"[Quick
Accent] Move language data to PowerAccent.Common library, refactor"*).

The two entries are byte-identical (same value `Greek Polytonic`, same
`xml:space="preserve"`). The WinAppSDK PRI generator (`MakePri`) rejects
duplicates, so every build off current `main` fails before reaching the
compile step.

## Fix

Remove the tail duplicate (3-line block right before `</root>`). Keeps
the original entry in its alphabetized location.

## Validation

- `git diff` shows exactly 3 deleted lines.
- `[xml](Get-Content … -Raw)` round-trip succeeds — file is still
well-formed XML.
- `Select-String` count of
`QuickAccent_SelectedLanguage_Greek_Polytonic` in the file is now **1**
(was 2).
- Only `en-us` has this key (other localized `.resw` files are populated
later via Touchdown), so no other file needs touching.

## Note

Discovered while investigating a CI failure unrelated to my other PR
#48050 (signing of `WinUI3Apps\YamlDotNet.dll`). Both fixes are needed
for the 0.100 release pipeline; this PR unblocks PR builds off `main`,
and #48050 unblocks the signed release pipeline once both are merged to
`main` and the next `main → stable` sync happens.

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 15:42:56 +02:00
Dave Rayment
81e251c2de [Quick Accent] Move language data to PowerAccent.Common library, refactor (#47211)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Language data for Quick Accent was previously defined in
**PowerAccent.Core/Languages.cs**, internal to the Quick Accent
application itself. The Settings application had no access to the list
and had to maintain a parallel, manually-synchronised list of the
language names and groups, and there was no single place to look up the
complete set of languages.

This PR resolves this and extracts the language data into a new
**PowerAccent.Common** project with no external or UI dependencies,
making it the single source of truth for both the character popup and
Settings UI.

The implicit and non-obvious ordering of the old **Languages.cs** has
been replaced with explicit ordering of the language groups and
languages list.

A new unit test project for the application has also been added.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

The following changes and additions have been made:

New project **PowerAccent.Common** contains:

- **Language.cs** - an enum of Quick Accent's pseudo-ISO identifiers for
each of the supported languages
- **LetterKey.cs** - mirrors the LetterKey enum defined in the
KeyboardListener.idl, and must be kept in sync with that. This exposes
the VK_* data to the Settings UI without it having to reference the
PowerAccentKeyboardService project
- **LanguageGroup.cs** - classifies languages as `Language` (for spoken
languages), `Special` (for sets like Currency and International Phonetic
Alphabet), or `UserDefined` (reserved for future work)
- **LanguageInfo.cs** - a record which combines a language's identity,
group, resource identifier, and character mappings
- **CharacterMappings.cs** - a new version of the old **Languages.cs**,
providing:
    - `All` - the canonical registry of every `LanguageInfo` entry
- `DisplayOrder` - the explicit within-group ordering for languages, for
the default popup ordering, i.e. when the user has not got
frequency-based ordering enabled. This is a culturally-neutral
alphabetical ordering by our pseudo-ISO language IDs, and close to the
previous order, minimising any impact to users' muscle memory
- `GroupDisplayOrder` - the explicit ordering of language groups for
both the popup and the Settings UI
- `LanguageLookup` - new _O(1)_ `Language` to `LanguageInfo` dictionary
- `GetCharacters(LetterKey, Language[])` - a deduplicating character
collector and sorter

A significant improvement over the old **Languages.cs** is that language
ordering is now explicit and in one place. Previously, popup ordering
was an implicit side-effect of a large Union chain across all language
mappings, with the enum, the language mapping declarations and the union
all carrying different orderings. This was a source of confusion and
made adding new languages fragile.

The original **Languages.cs** has been deleted. There's now a thin
`CharacterMappings` adapter that casts the `LetterKey` to the managed
equivalent by numeric value. This is another effort to decouple the
Settings UI and Quick Accent and only share the required elements.

In Settings, the `PowerAccentViewModel` class has been updated. It now
derives the language list directly from `CharacterMappings.All`; the
`InitializeLanguages()` call handles localisation, sorting and grouping
in a single place using `GroupDisplayOrder`.

A new unit test project covers `CharacterMappings`, the `LetterKey`
IDL-to-managed code bridge and general language declaration checks. The
application is now much more robust against changes which alter the
declared order, introduce empty/null elements, and so on. It is now
impossible to add a new language and forget a constituent part (the enum
entry, its place in the display order, the character mappings
themselves) without a test failing.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

See new unit test project for comprehensive tests.

Manually confirmed:

- Triggering Quick Accent with no language selected does not report an
error
- Language order is consistent in the popup, no matter what order the
languages are selected in Settings
- The order of languages in Settings is consistent, alphabetically by
localised name
- All declared languages are present in Settings and no resource IDs
were missed

---------

Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 15:24:26 +08:00
Clint Rutkas
f02b66c88d Fix auto-update relaunch, add config backup, enable auto-download by default (#46889)
## Summary

Addresses three critical issues with the PowerToys update experience
that cause user fragmentation across old versions.

### Changes

**1. Fix relaunch after update (Fixes #42004, #43011, #44071)**
- Stage 1 now passes the PowerToys install directory to Stage 2 as an
argument
- After successful install, Stage 2 relaunches `PowerToys.exe` with
`-report_update_success`
- Users will see a 'successfully updated' toast and PT resumes
automatically

**2. Config backup/restore (Fixes #46179)**
- `BackupConfigFiles()` snapshots all JSON configs to `ConfigBackup/`
before update begins
- `RestoreCorruptedConfigs()` checks for null-byte corruption after
install and auto-restores
- Protects Workspaces, FancyZones, Keyboard Manager, and all other
module settings

**3. Enable auto-download by default**
- New installations default `AutoDownloadUpdates` to `true` (was
`false`)
- Existing users' preferences are preserved — this only affects
first-run defaults
- The runner already defaulted to `true`; this aligns the C# settings
model

### Why this matters

The current updater kills all PowerToys processes, runs the installer,
then **exits without relaunching**. Users lose keyboard remappings,
FancyZones layouts, and Awake settings with no indication why. Combined
with auto-download being off by default, most users are multiple
versions behind.

### Testing

- Verified update flow: Stage 1 → Stage 2 → PT relaunches with success
toast
- Config backup creates mirror of all JSON settings before update
- Corruption detection catches null-byte pattern from #46179
- Graceful fallback: if install dir not provided (old Stage 1), logs
warning but doesn't crash

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-05-22 14:01:56 +08:00
Copilot
386fdcb1e9 [Shortcut Guide] Render key names instead of raw numeric key codes (#48037)
## Summary of the Pull Request

Shortcut Guide was rendering raw numeric key codes in key visuals for
some shortcuts (notably generated PowerToys shortcuts), showing numbers
where key characters/names were expected. This change normalizes numeric
key handling so display output matches actual key labels.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

- **Scope**
- Updated `ShortcutDescriptionToKeysConverter` in `ShortcutGuide.Ui`
only.

- **Behavior change**
- Numeric key codes are no longer passed through as raw integers by
default.
- Arrow keys (`37/38/39/40`) remain numeric to preserve existing glyph
rendering behavior.
- All other numeric key codes are converted to display names via
`Helper.GetKeyName(...)`.

- **Result**
- Shortcut Guide key caps now show expected characters/key names instead
of numeric values for manifest-provided numeric keys.

```csharp
if (int.TryParse(key, out int keyCode))
{
    switch (keyCode)
    {
        case 38:
        case 40:
        case 37:
        case 39:
            shortcutList.Add(keyCode); // keep glyph path
            break;
        default:
            shortcutList.Add(Helper.GetKeyName((uint)keyCode)); // show key label
            break;
    }
}
```

## Validation Steps Performed

No additional validation details are included in this description.

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `o3svsblobprodcus318.vsblob.vsassets.io`
> - Triggering command: `/usr/bin/dotnet dotnet build
/home/REDACTED/work/PowerToys/PowerToys/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj
-v minimal` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/microsoft/PowerToys/settings/copilot/coding_agent)
(admins only)
>
> </details>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Co-authored-by: Noraa Junker <noraa.junker@outlook.com>
2026-05-22 11:08:32 +08:00
Niels Laute
8af6a99136 Settings UX tweaks (#48024)
This PR:

- Adds a MaxWidth to the SCOOBE and OOBE pages, inline with Fluent
design guidance
- Updates the imagery for PowerToys
- Tweaks some small nits in the General settings page: re-ordering,
adding icons, replacing a ToggleSwitch with a CheckBox

<img width="1774" height="1325" alt="image"
src="https://github.com/user-attachments/assets/d825047a-27be-4e8b-a6ce-7f1a71f39dfe"
/>
2026-05-21 17:28:40 +02:00
Noraa Junker
9699d8a802 Shortcut Guide V2 (#40834)
## Summary of the Pull Request


https://github.com/user-attachments/assets/f4afdaf8-2830-4993-82ea-1ee9a6978e4c


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

Work for future PRs:

- [ ] Localization of built-in shortcut files
- [ ] Further customization (we can wait on user feedback for that)
- [ ] Reimplement holding windows key
- [ ] Search bar

<details>
<summary>Images</summary>


https://github.com/user-attachments/assets/f923daa4-d713-463b-ba33-ede72b986c12

<img width="726" height="1388" alt="image"
src="https://github.com/user-attachments/assets/781eff9a-2863-44be-bbe2-25371ef8838e"
/>
<img width="624" height="351" alt="image"
src="https://github.com/user-attachments/assets/ec8a44db-afbc-4e28-8285-ba2a9e345fb9"
/>
<img width="712" height="1086" alt="image"
src="https://github.com/user-attachments/assets/5a3775fc-36e9-4971-8d3f-491e8f8da45a"
/>
<img width="726" height="134" alt="image"
src="https://github.com/user-attachments/assets/7d0a8b1f-d10e-4466-820c-b3efdc5f3c84"
/>


</details>




<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Muyuan Li <116717757+MuyuanMS@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
2026-05-21 17:27:52 +02:00
moooyo
5b16a8c945 fix(PowerDisplay): close window on Escape key (#48016) (#48026)
The PowerDisplay flyout had no keyboard close path: pressing Tab kept
focus inside the window so it never deactivated, and there was no
KeyDown / KeyboardAccelerator wired up. Handle Escape on RootGrid to
hide the window, matching the behavior of other PowerToys flyouts.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:59:07 +02:00
Muyuan Li
fa2b7f6e5f Fix VALID_LABELS mismatches in auto-label-issues workflow (#48027)
## Summary

Corrects four label names in the \VALID_LABELS\ allow-list of the
\uto-label-issues.yml\ workflow to match existing repository labels
exactly.

## Root Cause

The GitHub Actions \ddLabels\ API creates a brand-new label when the
name doesn't exactly match an existing one. The hardcoded list had
typos/mismatches:

| Workflow had | Repo actually has |
|---|---|
| \Product-Power Display\ | \Product-PowerDisplay\ |
| \Product-ColorPicker\ | \Product-Color Picker\ |
| \Product-Command Not Found\ | \Product-CommandNotFound\ |
| \Product-Hosts\ | \Product-Hosts File Editor\ |

## Changes

- Fixed all 4 label strings in \VALID_LABELS\ array
- Deleted the spurious \Product-Power Display\ label that was created by
the mismatch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-21 12:58:46 +02:00
moooyo
f117bfc64e feat(PowerDisplay): migrate legacy "{Source}_{EdidId}_{N}" Ids to new stable ID (#47977)
Carry per-monitor user preferences from the pre-#47712 Id format onto
the current DevicePath-based Ids by matching on EdidId. Without this,
every upgrade silently resets Enable* toggles (input source, color
temperature, power state) for monitors users had already opted in on,
because the direct-Id lookup in ApplyPreservedUserSettings can never
match the old "DDC_DELD1A8_1" / "WMI_BOE0900_2" keys.

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:21:20 +08:00
Niels Laute
52bf042df2 [Workspaces] Move to WPF Fluent and tweak overall UX (#46172)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR includes the following changes:
- Remove the dependency of ControlzEx and ModernWpf, and instead uses
the default WPF Fluent theming. No more third-party packages.
- A lot of UI design refinements: using the proper fontweights, spacing,
Mica background, and default Fluent styles.
- Usability improvements: action buttons are at the top, full width
scrolling.

Related: #46220

<img width="1209" height="879" alt="image"
src="https://github.com/user-attachments/assets/132cdb9a-aa73-4aa2-88b0-c36031b4aea3"
/>


<img width="1060" height="980" alt="image"
src="https://github.com/user-attachments/assets/a04f83d4-74d3-466c-9d5c-f193f436337b"
/>



<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com>
Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
2026-05-21 07:54:19 +02:00
Mike Griese
2fc27b13b6 CmdPal: Fix opening a IPage from a context menu on a dock item (#47991)
Currently, if the user clicks on a dock item which is a Page command,
then we'll attempt to show the cmdpal where the user clicked on a dock
item. This works great. However, if the user right-clicks a dock item to
invoke the context menu, then from that menu clicks on a Page command,
then nothing happens - we don't show the cmdpal window.

Similarly - IInvokableCommand's that return a CommandResult.Confirm - we
never show the cmdpal window so that the confirmation ContentDialog is
shown to the user.

Yes in the long run, these will make more sense to have a better UI for
the "dock flyout" (see #45861). But until then, this fixes these
scenarios.

Closes #45963

very relevant for #47989
2026-05-20 16:03:07 -05:00
Dave Rayment
94b7e3eea2 [Image Resizer] Automatically reload settings changes (#45266)
## Summary of the Pull Request

This PR introduces real-time settings synchronisation for Image Resizer.
External changes to `settings.json` (via the Settings application or
manual edits to the file) are detected and reloaded immediately without
requiring a restart.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

### Reload updates
The reload detection is via an `IFileSystemWatcher` (as file system
operations are abstracted in Image Resizer), which monitors the
`settings.json` file for creation and changes. There is a half second
debounce for changes, as the settings file can rapidly change when the
user is editing the preset name field.

Hot reloading of the properties required refactoring `ReloadCore`, which
replaces the `Sizes` preset collection and updates the Custom and AI
presets, in addition to the application-wide properties like compression
options and the fallback encoder choice.

I changed the combo box data binding from the `SelectedSize` object to
the int `SelectedSizeIndex`. This was required to resolve a specific WPF
issue where reloading the `Sizes` collection would cause issues with
restoring the current combo box selection to the prior value. Binding to
the index decouples the selection state from the object lifecycle during
the reload process.

Selection preservation is based on the preset ID (for user presets) and
preset type (for Custom and AI presets). This ensures that matches are
robust even if the user is in the process of renaming an entry in the
Settings application. If the preset cannot be matched (for example, if
the user deletes the item or changes the ID manually in the file), the
Custom preset is selected.

Selection range checks are maintained from the old code, and additional
checks have been added to ensure that Custom and AI presets will be
present even if they're deleted from the settings file.

The AI preset check from before has been inlined; this guarantees that
the AI resize option will not display if the facility is unavailable on
the current PC, even if it's present in the settings file.

The reload routine is dispatched to the UI thread, as changes involve
updates to the combo box.

### ID recovery
Preset IDs are key to preserving the combo box selection between
reloads, and a couple of changes were necessary to ensure round-tripping
changes were robust.

1. IDs are not reused. The old code could reuse IDs under certain
circumstances, for example if a preset was deleted and re-added via
Settings.
2. The ID Recovery Helper routine sorted the supplied presets by ID
before performing duplicate conflict resolution. This is not required
and it is more natural to assume that the order in the settings file and
the client UI is the source of truth.

New ID assignment is now based on a monotonically increasing ID (seeded
at application start) rather than `Current Maximum ID + 1`. This means
that IDs cannot be reused in the same Settings application session. This
makes the matching process in the client application more reliable.

A small update was made to the ID Recovery Helper to use `HashSet.Add()`
instead of splitting up the check and addition steps (`Add` will return
false if the value already exists), which saves a massive one line of
code.

There were comments about the ID Recovery Helper resolving "empty" or
"invalid" entries; this was inaccurate, as IDs are ints, which must by
definition always have a valid value. The routine only guards against
duplicates, so the comments have been updated to reflect this.

### Miscellaneous
The `Default` Settings property getter previously called `Reload` every
time it was accessed. This is fine when the file is only read at
application startup, but I changed this to lazily instantiate and call
`Reload` a single time.

I refactored duplicate code related to settings file/folder strings, and
also the creation of the Custom and AI size instances.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

Manual testing:
1. Add new preset in the Settings application. The new entry is
reflected in the client application as it is running.
2. Delete new preset. The entry is removed in the client application
list.
3. Edit an existing preset. The property change is reflected in the
client application.
4. Add new preset in the Settings application. Select the new preset in
the client application. Edit the properties of the new preset in the
Settings application and confirm that the updates appear in the client
application.
5. Add new preset in the Settings application. Select the new preset in
the client application. Delete the new preset in Settings. Confirm that
the current preset is removed and the selection changes to the default
in the client application.
6. Change one or more application-wide properties in the settings file
which are represented in the client application, too, e.g. "Make
pictures smaller but not larger" (`imageresizer_shrinkOnly`) or
"Overwrite files" (`imageresizer_replace`). Upon saving, confirm the
checkbox changes immediately in the client application.
7. Edit the `settings.json` file manually by e.g. adding a new Size
preset or editing the width or height property of an existing preset.
Upon saving, the change(s) should be reflected in the client
application.
8. Make a change to an existing or new preset which affects the resize
operation itself, e.g. the dimensions or the "Make pictures smaller but
not larger" setting. Proceed with the resize operation and confirm that
the new changes have been applied.

14 new unit tests have been added to `SetingsTests.cs` to exercise the
`Settings` and `IDRecoveryHelper` functionality.

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 12:18:12 +00:00
Gordon Lam
c334f1d997 [CI] Sign Google.Apis.* and Google.GenAI DLLs (AdvancedPaste Gemini deps) (#48001)
## Summary

Adds the four Google.* DLLs shipped by AdvancedPaste (via
`Microsoft.SemanticKernel.Connectors.Google` for Gemini support) to the
ESRP sign list.

## Problem

The `Verify all binaries are signed and versioned` pipeline step
(`.pipelines/versionAndSignCheck.ps1`) was failing with:

```
Not Signed:  + ...\extractedMachineMsi\File\WinUI3ApplicationsFiles_File_Google.Apis.Auth.dll
Not Signed:  + ...\extractedMachineMsi\File\WinUI3ApplicationsFiles_File_Google.Apis.Core.dll
Not Signed:  + ...\extractedMachineMsi\File\WinUI3ApplicationsFiles_File_Google.Apis.dll
Not Signed:  + ...\extractedMachineMsi\File\WinUI3ApplicationsFiles_File_Google.GenAI.dll
```

These DLLs are transitive NuGet dependencies of
`Microsoft.SemanticKernel.Connectors.Google` (referenced by
`src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj`) and
deploy to `WinUI3Apps\`, but were never added to
`.pipelines/ESRPSigning_core.json`.

## Fix

Added the four DLLs to the second `SignBatch` (`CP-231522`, the
third-party / OSS-library identity) — same batch used for other
connector DLLs like `OpenAI.dll`, `OllamaSharp.dll` and
`Microsoft.SemanticKernel.Connectors.Ollama.dll`.

## Validation

- `ConvertFrom-Json` on the modified file confirms valid JSON.
- Sign-list entries match the actual deploy paths flagged by
`versionAndSignCheck.ps1`.
- The next ESRP signing run will pick them up; the verify step should
pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 17:45:56 +08:00
moooyo
8e74eb2ba8 [PowerDisplay] Auto-disable on detected DDC/CI capability fetch crash (#47556) (#47734)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary

Mitigation for issue #47556 — `KERNEL_SECURITY_CHECK_FAILURE` BSOD
originating in `win32kfull!DdcciGetCapabilitiesStringFromMonitor` when
PowerDisplay calls DDC/CI capability APIs against monitors with
malformed capability strings.

After a detected crash, PowerDisplay auto-disables itself via
`settings.json`, shows an error InfoBar at the top of the PowerDisplay
settings page (page is locked except the Ignore button), so users can
avoid getting stuck in an infinite reboot loop after a crash. And the
user must explicitly dismiss the warning before re-enabling the module.

The actual kernel-side fix is the Windows team's responsibility — this
PR only prevents users from BSOD-ing repeatedly on the same monitor
without warning.

settings page:
<img width="1743" height="1475" alt="image"
src="https://github.com/user-attachments/assets/8cf1b72f-c51a-4955-82d7-213cae49fd4e"
/>


## Mechanism

1. `CrashDetectionScope` IDisposable wraps Phase 2 capability fetch in
`DdcCiController.DiscoverMonitorsAsync`, writing `discovery.lock`
(`WriteThrough` + `Flush(flushToDisk: true)`) before, deleting it on
Dispose.
2. If the process is killed externally (BSOD, FailFast), the lock
survives.
3. On next PowerDisplay.exe startup (Phase 0), `CrashRecovery` detects
the orphan lock and runs a strict fail-fast sequence: write
`crash_detected.flag` → set `enabled.PowerDisplay=false` in global
`settings.json` → signal the new `POWER_DISPLAY_AUTO_DISABLE_EVENT` →
delete the lock (commit point).
4. The runner-loaded `PowerDisplayModuleInterface.dll` runs a one-shot
listener thread that wakes on the event and calls `disable()` to sync
`m_enabled`.
5. `PowerDisplayViewModel` reads the flag at construction and binds
`IsCrashLockActive` to lock the page.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 16:13:16 +08:00
Mike Griese
da9a08aa92 CmdPal: Add support for drag-drop bookmarking to the dock (#47989)
This allows users to drag files & URLS to the dock, to immediately
create a bookmark, and pin that bookmark to the dock.

It also updates the bookmark provider's dock bands. If you bookmark a
folder, then pin that to the dock, when you click it on the dock, we'll
default to the "browse" experience, which will open the list of files in
cmdpal, rather than open in explorer.

Had to make miscellaneous changes to make this all a bit faster:
* DirectoryPage didn't load icons smartly, like we do for bookmarks
* I added a different "observable" collection for dock top-level items,
because it caused a flippin CollectionChanged cascade any time a single
provider had more than one item pinned in the dock. Each item from each
provider would cause us to recreate all the dock view models (???) crazy
* I alas had to make `IBookmarksManager` public, so that the UI could
use it. I hate it, but I couldn't figure out a better way. Bookmarks are
a pretty built-in part, so it's _fine i guess_

re: #45584
2026-05-19 16:49:33 -05:00
Jessica Dene Earley-Cha
b893d633d9 [TEST Version] Event PR Check (#47889)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request

This PR checks if future PRs has added or refines telemetry events, if
so, the bot will add a message to the PR about the needed steps
depending on the PR.

**NOTE**: This PR is submitting a test version, which is only manually
triggered, once tested and confirmed then will move to it being
automatic


<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
2026-05-19 17:28:57 +00:00
Dave Rayment
a7bc09a87a [QuickAccent] Fix UI glitches, DPI-related issues, selection bugs, and add hardware shift key state fallback (#46593)
## Summary of the Pull Request
This PR fixes several issues around the popup selection window's size
and position, selection-related issues which result in flashing or
glitching, and includes more reliable detection of the Shift key.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

This PR includes fixes for the Quick Accent's selection window position,
its width measurement, and letter selection-related issues. In addition,
glitches such as the window flashing the selection colour and the window
appearing blank should be reduced or eliminated entirely.

### Popup width bug

When opening Quick Accent from a letter with many mappings, it would
appear too wide for the display. Even though letters could be selected,
they may be entirely off-screen:

<img width="1578" height="134" alt="image"
src="https://github.com/user-attachments/assets/cfcb2ddb-3cf3-47d5-9386-133a2fc70550"
/>

This was because of this flaw in `GetDisplayMaxWidth`, which is used
directly by the popup to set the maximum width of the characters area:

```csharp
    // In Selector.xaml.cs
    private void SetWindowsSize()
    {
        this.characters.MaxWidth = _powerAccent.GetDisplayMaxWidth();
    }

...
    // In PowerAccent.cs
    public double GetDisplayMaxWidth()
    {
        return WindowsFunctions.GetActiveDisplay().Size.Width - ScreenMinPadding;
    }
```

`GetActiveDisplay` uses the `GetMonitorInfo` API, which exposes the
working area of the display. It returns its values in _raw unscaled
pixel_ values:

```csharp
    public static (Point Location, Size Size, double Dpi) GetActiveDisplay()
    {
        ...
        var res = PInvoke.MonitorFromWindow(guiInfo.hwndActive, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
        MONITORINFO monitorInfo = default;
        monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
        PInvoke.GetMonitorInfo(res, ref monitorInfo);

        ...

        return (location, monitorInfo.rcWork.Size, dpi);
    }
```

However, the `MaxWidth` property must be a _pre-scaled_ value, i.e. in
logical WPF units not physical pixels. The fix is straightforward:

```csharp
    public double GetDisplayMaxWidth()
    {
        var activeDisplay = WindowsFunctions.GetActiveDisplay();
        return (activeDisplay.Size.Width / activeDisplay.Dpi) - ScreenMinPadding;
    }
```

### Popup positioning bug

This is related to a subtle DPI issue in `GetActiveDisplay()`:

```csharp
    public static (Point Location, Size Size, double Dpi) GetActiveDisplay()
    {
        GUITHREADINFO guiInfo = default;
        guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
        PInvoke.GetGUIThreadInfo(0, ref guiInfo);
        var res = PInvoke.MonitorFromWindow(guiInfo.hwndActive, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);

        MONITORINFO monitorInfo = default;
        monitorInfo.cbSize = (uint)Marshal.SizeOf(monitorInfo);
        PInvoke.GetMonitorInfo(res, ref monitorInfo);

        double dpi = PInvoke.GetDpiForWindow(guiInfo.hwndActive) / 96d;
        var location = new Point(monitorInfo.rcWork.left, monitorInfo.rcWork.top);
        return (location, monitorInfo.rcWork.Size, dpi);
    }
```

Here, the application window's DPI is returned. Unfortunately, the
window may report a value which is different from the monitor's own DPI
value. This will consistently happen if the application is not
Per-Monitor DPI-Aware, and the monitor is not at 100% Scale. The effects
are that the Quick Accent popup can appear misaligned or even off-screen
entirely. Quick Accent can still be used, but the user may not be able
to see what they are selecting.

As Quick Accent is using monitor coordinates for setting its location,
the solution is to use the monitor's own DPI value. The fix is to add
this in place of the `GetDpiForWindow` line:

```csharp
        uint dpiRaw = 96; // Safe default
        if (PInvoke.GetDpiForMonitor(res, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out _) == 0)
        {
            dpiRaw = dpiX;
        }

        double dpi = dpiRaw / 96d;
```

### Selection bugs

After dismissing the Quick Accent window, the `_selectedIndex` state was
not properly reset. The next time the window opened, it could attempt to
scroll to or highlight an index that was out-of-bounds for the new
character set. This could result in glitching, such as the window
flashing the selection colour or the initial selection being incorrect.
In this fix, I:

1. Explicitly set `_selectedIndex` to `-1` when the UI hides.
2. Reset the `SelectedIndex` inside Selector.xaml.cs before updating the
`ItemsSource`.

### Shift key activation

In certain cases, a quick press of Shift could fail to move back through
the character list. In this fix, I:

1. Added a native fallback usign GetAsyncKeyState(VK_SHIFT).
2. Updated `ProcessNextChar()` to evaluate `shiftPressed ||
WindowsFunctions.IsShiftState()`.
3. Updated the multiple `if`s in `ProcessNextChar` to be an if/else
structure, to prevent bugs when more than one trigger key is pressed.

### Support added for multi-codepoint graphemes

The current code loops through each `char` of a mapping, calling
`SendInput` multiple times for multi-char sequences. This will fail for
multi-codepoint graphemes, i.e. where the mapping 'letter' is more than
one UTF-16 codepoint. Those characters may appear as `[]`. The amended
`Insert()` in `WindowsFunctions` appends all characters before calling
`SendInput`.

### Miscelleneous

- Added an `OnDpiChanged` handler for the Selector control, so changing
the DPI of the screen should be picked up automatically. (It's
questionable whether this is essential, as the DPI would have to change
while the control was displayed, but it's worth having for robustness.)
- Now using `SetWindowPos` instead of setting the `Left` and `Top` of
the popup control. Also now initialising the popup offscreen to attempt
to reduce flicker and the occurrence of blank window flashes.
- Changed the `Focusable` property of the characters `ListBox` to
`False`, to attempt to reduce flicker and the window flashing the
selector colour.
- Removed `Width` and added `MinWidth` to the letter control in
Selector.xaml. This allows for wider letters or longer multi-letter
mappings.
- Changed the `VirtualizingStackPanel` to a regular `StackPanel`. We do
not have mappings with enough entries for a virtual control to be
necessary, and using StackPanel seemed to have a positive effect on the
appearance of blank window glitches.
- Added `TextTrimming`, `TextWrapping` and `MaxHeight` to the unicode
description `TextBlock`. This helps support extremely long unicode
descriptions. Again, this will enable us to support longer
multi-character mappings in the future.
- Added CsWin32 to the PowerAccent.UI project, to support the
`SetWindowPos` call.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

See separate doc for full test details:


https://docs.google.com/document/d/19uClcUiv7RUDRlbFhazG-Cmu46oNmrAVoJf9bHSjSJU/edit?usp=sharing

---------

Co-authored-by: Muyuan Li (from Dev Box) <muyuanli@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
2026-05-20 00:20:03 +08:00
Marco Guido
6e9b3b1536 [PowerAccent] adding greek polytonic (#47021)
Adds Greek Polytonic characters set to power accent, based on
https://github.com/microsoft/PowerToys/pull/29709
### PR Checklist

- [x]  Closes #46941
- [x] Communication: I've discussed this with core contributors already.
- [ ] Tests: Not sure if there are specific tests for this
- [ ] Documentation updated: Power accent docs

### Detailed Description of the Pull Request / Additional comments
Added all greek polytonic letters to their corresponding english letter
(some duplicated)
(if you wondered about GRC -> ISO 639-3)

### Validation Steps Performed
Compiled and Observed Power Accent

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Dave Rayment <dave.rayment@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 00:19:06 +08:00
Gordon Lam
d973bcbcaa Update Microsoft.SemanticKernel packages from 1.66.0 to 1.71.0 (#47819)
## Summary

Updates the `Microsoft.SemanticKernel` package family from 1.66.0 to
1.71.0 to pick up upstream improvements and bug fixes from the Semantic
Kernel project.

> Note: the `Connectors.AzureAIInference` (`-beta`), `Connectors.Google`
/ `Connectors.MistralAI` / `Connectors.Ollama` (`-alpha`) packages have
no stable upstream release yet, but are required to keep the existing
Advanced Paste AI provider options working.


## Packages updated

**SemanticKernel family:**

| Package | From | To |
|---------|------|----|
| `Microsoft.SemanticKernel` | 1.66.0 | 1.71.0 |
| `Microsoft.SemanticKernel.Connectors.OpenAI` | 1.66.0 | 1.71.0 |
| `Microsoft.SemanticKernel.Connectors.AzureAIInference` | 1.66.0-beta |
1.71.0-beta |
| `Microsoft.SemanticKernel.Connectors.Google` | 1.66.0-alpha |
1.71.0-alpha |
| `Microsoft.SemanticKernel.Connectors.MistralAI` | 1.66.0-alpha |
1.71.0-alpha |
| `Microsoft.SemanticKernel.Connectors.Ollama` | 1.66.0-alpha |
1.71.0-alpha |

**Transitive dependencies bumped to satisfy SK 1.71's resolution
constraints:**

| Package | From | To |
|---------|------|----|
| `Microsoft.Extensions.AI` | 9.9.1 | 10.2.0 |
| `Microsoft.Extensions.AI.OpenAI` | 9.9.1-preview.1.25474.6 |
10.0.1-preview.1.25571.5 |
| `System.Numerics.Tensors` | 9.0.11 | 10.0.2 |
| `Newtonsoft.Json` | 13.0.3 | 13.0.4 |
| `OpenAI` | 2.5.0 | 2.7.0 |
| `System.ClientModel` | 1.7.0 | 1.8.0 |

These transitive bumps were required to avoid `NU1109` package-downgrade
errors after the SK upgrade.

## Scope

- `Directory.Packages.props` only  central package version bumps.
- No source-code changes.

## Consumers

- AdvancedPaste uses the SK `Kernel` / `IChatCompletionService` /
connector surfaces unchanged.
- `LanguageModelProvider` and `FoundryLocalPasteProvider` use the stable
`Microsoft.Extensions.AI` `IChatClient` / `ChatMessage` / `ChatRole` /
`ChatResponse` / `AsIChatClient()` types unchanged across 9.x to 10.x.

## Validation

- Static API-surface review of all SK / `Microsoft.Extensions.AI` call
sites only stable types in use.
- CI build pipeline will provide the full restore + compile verification
across the solution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-19 10:25:41 +02:00
Niels Laute
02fbb916a7 [Quick Accent] Remove wpfui (#46604)
## Summary of the Pull Request

<img
src="https://github.com/user-attachments/assets/8756671f-642a-4bbd-a174-eb13b02cfe59">

This PR removes the dependency on the WpfUI library and uses plain WPF.

## PR Checklist

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

## Detailed Description of the Pull Request / Additional comments

- Replaced `ui:FluentWindow` with a standard WPF `Window` and removed
the `xmlns:ui` WpfUI namespace
- Replaced `<Rectangle.Fill><SolidColorBrush /></Rectangle.Fill>` with
an inline `Fill=` attribute on the selection indicator rectangle
- Simplified `App.xaml` by removing WpfUI theme/controls resource
dictionaries and using `ThemeMode="System"` instead
- Fixed XAML formatting: converted empty `<Application>` to a
self-closing tag, removed extra blank lines in `Window.Resources` and
inside `ControlTemplate`

## Validation Steps Performed

- Manually verified the Quick Accent overlay renders correctly with
accent character selection and character name display

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for
you](https://github.com/microsoft/PowerToys/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-19 08:13:00 +00:00
675 changed files with 331099 additions and 10671 deletions

View File

@@ -17,8 +17,8 @@ LIGHTTURQUOISE
NCol
OLIVEGREEN
PALEBLUE
PArgb
Pbgra
pargb
pbgra
SRGBTo
WHITEONBLACK
@@ -51,6 +51,7 @@ resw
resx
srt
Stereolithography
taskmgr
terabyte
UYVY
xbf
@@ -336,6 +337,10 @@ MRUCMPPROC
MRUINFO
REGSTR
#Xaml
NVI
Storyboards
# Misc Win32 APIs and PInvokes
DEFAULTTONEAREST
INVOKEIDLIST
@@ -360,7 +365,10 @@ FILESYSONLY
URLIS
WAITTIMEOUT
DEFAULTTONEAREST
DWRITE
LWIN
VCENTER
VREDRAW
# COM/WinRT interface prefixes and type fragments
BAlt
@@ -424,3 +432,6 @@ SHELLEXPERIENCEHOST
SHELLHOST
STARTMENUEXPERIENCEHOST
WIDGETBOARD
# URIs
actioncenter

View File

@@ -1,23 +1,51 @@
accelscroll
acq
ADDTO
ADDTOOL
adr
Adr
ALWAYSTIP
APPLYTOSUBMENUS
ARCHMASK
archs
AUDCLNT
autocorr
avx
axisdefer
axisflip
axisstart
backlight
BEOS
bfi
BFIN
bfly
BGRX
bitmaps
bitrev
blits
Borgerding
Borland
breakc
BREAKSCR
BUFFERFLAGS
bugzilla
Cands
capturepath
cbs
centiseconds
cexp
cfx
cfy
cgem
cifx
cify
CLASSW
coeffs
colblocks
constantbuffer
coprime
cpuid
cpx
CREATEDIBSECTION
CREATESTRUCTW
crossfades
@@ -28,109 +56,216 @@ CTLCOLORDLG
CTLCOLOREDIT
CTLCOLORLISTBOX
CTrim
CVTEPI
DBuffer
dcl
dct
ddx
ddy
Deinterleave
denoise
denoised
DEVSOURCE
DFCS
DIVSCALAR
DJGPP
dlg
dlu
dnn
DONTCARE
downsample
DRAWITEM
DRAWITEMSTRUCT
droppedband
Droppedband
DSPs
dsum
dupburst
dupsegments
DWLP
eband
ebx
ECX
EDITCONTROL
EDSP
emmintrin
EMX
ENABLEHOOK
endloop
ENDOFSTREAM
ener
enh
ettings
expectedlock
expf
fabs
fabsf
facbuf
fastscroll
FDE
ffast
FIXDIV
floorf
fmadd
fout
fstride
fxc
GETCHANNELRECT
GETCHECK
GETCOUNT
GETDISPINFO
GETSCREENSAVEACTIVE
GETSCREENSAVETIMEOUT
GETTHUMBRECT
GIFs
glu
groupshared
gru
hcfdark
hcfwhitespace
hlsl
Hsieh
hstride
HTBOTTOMRIGHT
HTHEME
htol
ICONINFORMATION
ICONWARNING
idct
IDIn
IDISHWND
ifft
igc
ilog
imad
imax
imin
immintrin
Inj
interp
inttypes
ishl
itof
jumprecover
kfft
kheight
kissfft
KSDATAFORMAT
ksize
ktime
lastg
latestcapture
ldx
LEFTNOWORDWRAP
legitjumps
lenmem
letterbox
lld
lldx
llu
llums
logfont
lookback
lpc
lpcnet
LPNMHDR
LPNMTTDISPINFO
lround
lte
luma
Luma
maj
manualdrop
maskcache
maxabs
maxcorr
MAXFACTORS
maxperiod
maxstep
memalign
memid
memneeded
MENUINFO
MFSTARTUP
mfxhw
mic
middledrop
minperiod
MIPSr
MJPEG
MMRESULT
momentumreversal
movc
mrate
mrt
MULBYSCALAR
MULC
MWERKS
mycfg
narrowstrip
nbak
nbytes
ncapture
nchw
ncm
nduplicates
nfft
NHWC
niterations
nmonitor
nnet
NONCLIENTMETRICS
NONOTIFY
nonvle
normf
nredraw
nstop
nsubpixel
ntorn
numthreads
nvw
Octasic
osc
OSCE
ovflw
OWNERDRAW
PBGRA
periodictrap
pillarbox
pfdc
pillarbox
playhead
pnmh
pointerreuse
PPW
prereq
PSHR
pstdint
PSWA
pwfx
QCONST
qpc
Qpc
quantums
qweight
RCSEGMODEL
RCZOOMITSCR
readback
READERF
realcapture
REFKNOWNFOLDERID
relu
reposted
RETURNCMD
rnn
rnnoise
rotateleft
rsqrt
rtcd
RTEXT
RTH
rtvs
SCALEIN
SCALEOUT
SCREENSAVE
SCRNSAVE
SCRNSAVECONFIGURE
@@ -138,43 +273,80 @@ scrnsavw
Scrnsavw
scrollramp
SCROLLSIZEGRIP
selfie
selftest
SETBARCOLOR
SETBKCOLOR
SETDEFID
SETRECT
SETSCREENSAVETIMEOUT
SETTIPSIDE
sgem
sgemv
sgv
SHAREMODE
SHAREVIOLATION
shortlist
simde
siv
slowthenfast
smallstart
SNIPOCR
softmax
sqrtf
SROUND
srvs
ssi
startuprecovery
stdint
stf
stopafter
STREAMFLAGS
SUBFROM
subias
submix
sxx
sxy
symbian
synthesising
syy
tallportal
TBTS
tci
tcsicmp
TEXTCALLBACK
TEXTMETRIC
tgsm
THIRDPARTY
tinystep
tme
toolbars
TOOLINFO
TRACKMOUSEEVENT
TRIANGLELIST
TTM
TTN
TWID
UADD
uav
uavs
uge
Unadvise
upscaled
upscales
USUB
utof
vad
vaddq
vaddvq
valgrind
Valin
vandq
vblank
vcgeq
vdup
vectorizer
VERTID
VIDCAP
vld
vle
@@ -184,6 +356,7 @@ vminq
vmlal
vmull
vqaddq
VSHR
vshrn
vsntprintf
vsnwprintf
@@ -194,7 +367,9 @@ WAVEFORMATEXTENSIBLE
webcam
Webcam
webcams
Wextra
wfopen
WGC
wideportal
wil
WMU
@@ -202,11 +377,46 @@ wrapjump
wtol
WTSSESSION
WTSUn
wxyz
xchg
xcorr
XEnd
Xfl
Xiang
Xiph
xmmintrin
xptr
xshift
XStart
XStep
xxxy
xxyx
xxyz
xyw
xywx
xyxx
xyxz
xyzw
xyzx
xzwx
xzxx
Yfl
YInternal
yshift
YUV
yyyx
yyzw
yzw
yzwy
yzyy
Zhou
Zhu
ZMBS
zncc
Zncc
ZNCC
zrh
zwzz
zyzw
zzwz
zzzw

View File

@@ -105,13 +105,13 @@
^src/common/ManagedCommon/ColorFormatHelper\.cs$
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
^src/common/sysinternals/Eula/
^doc/devdocs/modules/cmdpal/initial-sdk-spec/list-elements-mock-002\.pdn$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
^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\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
@@ -135,6 +135,8 @@
^src/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator\.cs$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/modules/ZoomIt/ZoomIt/rnnoise/
^src/modules/ZoomIt/ZoomIt/selfie_segmentation\.onnx$
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
^src/Monaco/
^tools/project_template/ModuleTemplate/resource\.h$

View File

@@ -24,7 +24,8 @@ advancedpaste
advapi
advfirewall
AFeature
affordances
affordance
afterfx
AFX
agentskills
AGGREGATABLE
@@ -62,6 +63,7 @@ APPEXECLINK
appext
apphost
APPLICATIONFRAMEHOST
Applocal
appmanifest
APPMODEL
APPNAME
@@ -80,6 +82,7 @@ ARPPRODUCTICON
ARRAYSIZE
ARROWKEYS
arrowshape
artboard
ARTIFACTSTAGINGDIRECTORY
asf
Ashcraft
@@ -110,14 +113,12 @@ azman
azureaiinference
azureinference
azureopenai
Backlight
backticks
Badflags
Badmode
Badparam
bbwe
BCIE
bck
BESTEFFORT
bezelled
bhid
@@ -156,6 +157,7 @@ breadcrumb
Browsable
BROWSEINFO
bsd
BSOD
bthprops
bti
BTNFACE
@@ -163,6 +165,7 @@ bugreport
bugreportfile
BUILDARCH
BUILDNUMBER
buildsystems
buildtransitive
builttoroam
BUNDLEINFO
@@ -200,6 +203,7 @@ checkmarks
CHILDACTIVATE
CHILDWINDOW
CHOOSEFONT
chu
CIBUILD
cidl
CIELCh
@@ -222,6 +226,7 @@ CLIPSIBLINGS
closesocket
clp
CLSCTX
CLSIDs
clsids
Clusion
cmder
@@ -332,6 +337,7 @@ dacl
DAffine
DAFFINETRANSFORM
datareader
Datasheet
datatracker
Dayof
dbcc
@@ -368,6 +374,7 @@ DEFPUSHBUTTON
deinitialization
DELA
DELD
deld
DELETEDKEYIMAGE
DELETESCANS
DEMOTYPE
@@ -392,6 +399,7 @@ DEVMODE
DEVMODEW
DEVNODES
devpal
devpackages
DEVTYP
dfx
DIALOGEX
@@ -407,7 +415,6 @@ DISPLAYFLAGS
DISPLAYFREQUENCY
displayname
DISPLAYORIENTATION
DISPLAYPORT
divyan
DLGFRAME
dlgmodalframe
@@ -435,9 +442,9 @@ drawingcolor
dreamsofameaningfullife
drivedetectionwarning
DROPFILES
DSPDLOG
DSTINVERT
DString
DSVG
dto
DUMMYUNIONNAME
dumpbin
@@ -457,13 +464,12 @@ DWMNCRENDERINGCHANGED
Dwmp
DWMSENDICONICLIVEPREVIEWBITMAP
DWMSENDICONICTHUMBNAIL
DWMWA
DWMWCP
dwmwa
dwmwcp
DWMWINDOWATTRIBUTE
DWMWINDOWMAXIMIZEDCHANGE
DWORDLONG
dworigin
DWRITE
dxgi
Dxva
eab
@@ -501,7 +507,6 @@ ETDT
etl
etw
eula
eurochange
eventvwr
evt
EWXFORCE
@@ -542,6 +547,7 @@ FFFF
fffffffzzz
FFh
Figma
figma
FILEEXPLORER
fileexploreraddons
fileexplorerpreview
@@ -631,9 +637,11 @@ gpu
grabandmove
GRABANDMOVEMODULEINTERFACE
gradians
GRC
grctlext
GRGX
Gridcustomlayout
gridlines
GSM
gtm
guiddata
@@ -668,6 +676,7 @@ HDEVNOTIFY
hdr
HDROP
hdwwiz
Headered
Helpline
helptext
hgdiobj
@@ -720,6 +729,7 @@ hotkeys
hotlight
hotspot
HPAINTBUFFER
HPS
HRAWINPUT
HREDRAW
hres
@@ -783,6 +793,7 @@ imgflip
inapp
inbox
INCONTACT
indesign
Indo
inetcpl
Infobar
@@ -793,6 +804,7 @@ INITDIALOG
INITGUID
initialfile
INITTOLOGFONTSTRUCT
inkscape
INLINEPREFIX
inlines
Inno
@@ -836,6 +848,7 @@ issecret
ISSEPARATOR
issuecomment
istep
Italicise
ith
IUI
IUWP
@@ -843,17 +856,18 @@ IWIC
jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
JIDEA
jjw
jobject
JOBOBJECT
jpe
JPN
jpnime
jrsoftware
Jsons
jsonval
jxr
Kantai
KBSC
keybd
KEYBDDATA
KEYBDINPUT
@@ -871,6 +885,7 @@ keynum
keyremaps
keyring
keyvault
kfull
KILLFOCUS
killrunner
kmph
@@ -974,12 +989,11 @@ lstrcmpi
lstrcpyn
lstrlen
LTEXT
LTM
LTRREADING
luid
lusrmgr
LVDS
LWA
LWIN
LZero
MAGTRANSFORM
makeappx
@@ -1029,6 +1043,7 @@ Mgmt
Microwaved
middleclickaction
midl
midtones
mii
MIIM
mikeclayton
@@ -1041,8 +1056,6 @@ MINIMIZESTART
MINMAXINFO
minwindef
Mip
Miracast
MIRACAST
mkdn
mlcfg
mmc
@@ -1073,6 +1086,7 @@ MOVESIZEEND
MOVESIZESTART
MRM
mru
msaccess
MSAL
msc
mscorlib
@@ -1081,8 +1095,6 @@ msdata
msdia
MSDL
MSGFLT
MSHCTX
MSHLFLAGS
msiexec
MSIFASTINSTALL
MSIHANDLE
@@ -1092,13 +1104,16 @@ msixbundle
MSIXCA
MSLLHOOKSTRUCT
Mso
mspub
msrc
msstore
mstsc
msvcp
mswhql
MT
MTND
multimonitor
Multiplayer
MULTIPLEUSE
multizone
muxc
@@ -1141,7 +1156,6 @@ netcpl
netframework
netsetup
netsh
newcolor
NEWDIALOGSTYLE
NEWFILE
NEWFILEHEADER
@@ -1187,7 +1201,6 @@ nonclient
NONCLIENTMETRICSW
NONELEVATED
nonspace
nonstd
NOOWNERZORDER
NOPARENTNOTIFY
NOPREFIX
@@ -1202,6 +1215,7 @@ NORMALDISPLAY
NORMALUSER
NOSEARCH
NOSENDCHANGING
NOSIZE
nosize
notdefault
NOTHOUSANDS
@@ -1242,7 +1256,6 @@ OFN
ofs
OICI
OICIIO
oldcolor
olditem
oldpath
oldtheme
@@ -1309,6 +1322,7 @@ pchast
PCIDLIST
PCTSTR
PCWSTR
pdbs
PDBs
PDEVMODE
PDFs
@@ -1337,6 +1351,7 @@ phbm
phbmp
phicon
Photoshop
photoshop
phwnd
pici
pidl
@@ -1345,6 +1360,7 @@ pii
pinfo
pinvoke
pipename
Pitjantjatjara
PKBDLLHOOKSTRUCT
pkgfamily
PKI
@@ -1364,14 +1380,16 @@ POINTERID
POINTERUPDATE
Pokedex
Pomodoro
Popups
popups
POPUPWINDOW
portfile
POSITIONITEM
Postbot
POWERBROADCAST
powerdisplay
POWERDISPLAYMODULEINTERFACE
powerocr
powerpnt
POWERRENAMECONTEXTMENU
powerrenameinput
POWERRENAMETEST
@@ -1398,7 +1416,6 @@ Prefixer
Premul
prependpath
prepopulate
Prereq
prevhost
previewer
PREVIEWHANDLERFRAMEINFO
@@ -1427,6 +1444,7 @@ projectname
PROPERTYKEY
PROPVARIANT
prot
Prt
PRTL
prvpane
psapi
@@ -1472,6 +1490,7 @@ QUERYOPEN
QUEUESYNC
quickaccent
quicklinks
quickmask
QUNS
RAII
randi
@@ -1485,6 +1504,7 @@ RAWPATH
rbhid
Rbuttondown
rclsid
RCW
RCZOOMIT
rdp
RDW
@@ -1492,7 +1512,6 @@ READMODE
READOBJECTS
recents
RECTDESTINATION
rectp
RECTSOURCE
recursesubdirs
recyclebin
@@ -1580,14 +1599,15 @@ SAMESHORTCUTPREVIOUSLYMAPPED
samsung
sancov
SAVEFAILED
scanled
schedtasks
SCID
SCL
Scode
SCREENFONTS
screenruler
screensaver
screenshots
Scrollback
scrollviewer
sddl
SDKDDK
@@ -1757,6 +1777,7 @@ STDAPI
stdc
stdcpp
stdcpplatest
stdext
STDMETHODCALLTYPE
STDMETHODIMP
steamapps
@@ -1794,7 +1815,6 @@ svchost
SVGIn
SVGIO
svgz
SVIDEO
SVSI
SWFO
swp
@@ -1879,6 +1899,7 @@ TNP
Toggleable
tontrager
Toolhelp
toolsets
toolwindow
TOPDOWNDIB
TOUCHEVENTF
@@ -1928,6 +1949,7 @@ ums
uncompilable
UNCPRIORITY
UNDNAME
ungroup
UNICODETEXT
unins
uninsdeletekey
@@ -1940,6 +1962,8 @@ UNLEN
UNORM
unparsable
unremapped
Unsend
Unsubscribes
untriaged
unvirtualized
unwide
@@ -1969,11 +1993,11 @@ valuegenerator
VARTYPE
vbcscompiler
vcamp
VCENTER
vcgtq
VCINSTALLDIR
vcp
Vcpkg
vcpkg
vcpname
VCRT
vcruntime
@@ -1996,6 +2020,7 @@ VIRTKEY
VIRTUALDESK
VISEGRADRELAY
visiblecolorformats
visio
visualeffects
vkey
vmovl
@@ -2005,7 +2030,6 @@ vorrq
VOS
vpaddlq
vqsubq
VREDRAW
vreinterpretq
VSC
VSCBD
@@ -2054,6 +2078,7 @@ winapi
winappsdk
windir
WINDOWCREATED
WINDOWDESTROYED
windowedge
WINDOWINFO
WINDOWNAME
@@ -2080,13 +2105,16 @@ winlogon
winmd
winml
WINNT
winproj
winres
winrt
winsdk
winsta
WINTHRESHOLD
WINVER
winword
winxamlmanager
wireframes
withinrafael
Withscript
wixproj
@@ -2154,9 +2182,11 @@ XUP
XVIRTUALSCREEN
XXL
xxxxxx
Yankunytjatjara
ycombinator
yinle
yinyue
yoko
YVIRTUALSCREEN
zamora
Zenbook

View File

@@ -18,6 +18,13 @@ MIcrosoftEdgeLauncherCsharp
# marker for ignoring a comment to the end of the line
// #no-spell-check.*$
# JavaScript regex literals that start with \b can be reported as "b..." words.
# Example: /\bclass\s+.../
^\s*/\\[b].{3,}?/[gim]*\s*(?:\)(?:;|$)|,$)
# GitHub API header token used in code (not natural language).
\bx-ratelimit-reset\b
# Gaelic
Gàidhlig
@@ -305,3 +312,9 @@ ms-windows-store://\S+
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
# Special licenses text from RNNoise (BSD-style disclaimer: ``AS IS'')
``AS IS''
# Old school moniker for macOS from RNNoise
MacOS

View File

@@ -163,7 +163,7 @@ configuration:
association: Collaborator
then:
- addReply:
reply: Hi! We've identified this issue as a duplicate of another one that already exists on this Issue Tracker. This specific instance is being closed in favor of tracking the concern over on the referenced thread. Thanks for your report!
reply: We've identified this issue as a duplicate of an existing one and are closing this thread so discussion stays in one place.<br/><br/>Please see the comment above for the link to the original tracking issue, and feel free to subscribe there for updates.
- closeIssue
- removeLabel:
label: Needs-Triage

377
.github/scripts/telemetry-pr-check.js vendored Normal file
View File

@@ -0,0 +1,377 @@
#!/usr/bin/env node
/**
* Detects telemetry-event additions/modifications in a pull request and
* posts (or updates) a PR comment when telemetry-related changes are found.
*
* This script is executed by .github/workflows/telemetry-pr-check.yml.
* Keep both files aligned when changing trigger behavior, env usage, or messaging.
*/
const fs = require('node:fs');
const REVIEWER_LOGIN = 'chatasweetie';
const REVIEWER_MENTION = `@${REVIEWER_LOGIN}`;
const COMMENT_MARKER = '<!-- telemetry-event-check -->';
const COMMENT_BODY_WITH_PRIVACY_UPDATE = `${COMMENT_MARKER}
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. After this PR is merged, please follow these next steps:
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process
`;
const COMMENT_BODY_WITHOUT_PRIVACY_UPDATE = `${COMMENT_MARKER}
Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. Please ensure the following before merging:
- [ ] Add your telemetry events to [DATA_AND_PRIVACY](https://github.com/microsoft/PowerToys/blob/main/DATA_AND_PRIVACY.md).md within this PR.
- [ ] Reach out to Jessica (${REVIEWER_MENTION}) to follow up on the next steps: https://aka.ms/pt-telemetry-process`;
const TELEMETRY_PATH_PATTERNS = [
/(^|\/)trace\.(h|hpp|cpp|cs)$/i,
/(^|\/)telemetry\//i,
/(^|\/)events\/.+event\.cs$/i,
/^src\/common\/Telemetry\//i,
/^src\/common\/ManagedTelemetry\//i,
/^src\/runner\/trace\.(h|cpp)$/i,
/^src\/settings-ui\/.+\/Telemetry\//i,
];
const TELEMETRY_LINE_PATTERNS = [
/TraceLoggingWriteWrapper\s*\(/,
/\bTraceLoggingWrite\s*\(/,
/\bTRACELOGGING_DEFINE_PROVIDER\b/,
/\bTraceLoggingOptionProjectTelemetry\b/,
/\bProjectTelemetryPrivacyDataTag\b/,
/\bPROJECT_KEYWORD_MEASURE\b/,
/\bRegisterProvider\s*\(/,
/\bUnregisterProvider\s*\(/,
/\bPowerToysTelemetry\.Log\.WriteEvent\s*\(/,
/\bclass\s+\w+\s*:\s*EventBase\s*,\s*IEvent\b/,
/\bclass\s+\w+\s*:\s*TelemetryBase\b/,
/\bPartA_PrivTags\b/,
/\[EventData\]/,
/\bEventName\b/,
];
function requireEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
function validateRepository(repository) {
if (!/^[^/]+\/[^/]+$/.test(repository)) {
throw new Error(
`GITHUB_REPOSITORY must be in owner/repo format, received: ${JSON.stringify(repository)}`
);
}
}
function readEventPayload(eventPath) {
let raw;
try {
raw = fs.readFileSync(eventPath, 'utf8');
} catch (error) {
throw new Error(`Failed to read event payload at ${eventPath}: ${error.message}`);
}
try {
return JSON.parse(raw);
} catch (error) {
throw new Error(`Failed to parse JSON from ${eventPath}: ${error.message}`);
}
}
function resolvePullNumber(event) {
const fromPullRequest = event?.pull_request?.number;
const fromWorkflowDispatch = event?.inputs?.pr_number;
const rawPullNumber = fromPullRequest ?? fromWorkflowDispatch;
if (rawPullNumber === undefined || rawPullNumber === null || rawPullNumber === '') {
throw new Error(
'Unable to determine pull request number from event payload. Expected pull_request.number or inputs.pr_number.'
);
}
const pullNumber = Number.parseInt(String(rawPullNumber), 10);
if (!Number.isInteger(pullNumber) || pullNumber <= 0) {
throw new Error(`Invalid pull request number: ${JSON.stringify(rawPullNumber)}`);
}
return pullNumber;
}
function isTelemetryPath(filePath) {
return TELEMETRY_PATH_PATTERNS.some((pattern) => pattern.test(filePath));
}
function changedLinesFromPatch(patch) {
if (!patch) {
return [];
}
return patch
.split('\n')
.filter((line) => {
if (line.startsWith('+++') || line.startsWith('---')) {
return false;
}
return line.startsWith('+') || line.startsWith('-');
})
.map((line) => line.slice(1));
}
function hasTelemetryLineSignal(lines) {
return lines.some((line) => TELEMETRY_LINE_PATTERNS.some((pattern) => pattern.test(line)));
}
async function apiRequest(url, method = 'GET', body) {
const token = requireEnv('GITHUB_TOKEN');
let response;
try {
response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
} catch (error) {
throw new Error(`Network error during ${method} ${url}: ${error.message}`);
}
if (!response.ok) {
const text = await response.text();
const rateLimitReset = response.headers.get('x-ratelimit-reset');
const rateLimitHint =
response.status === 403 && rateLimitReset
? ` (rate limit reset at epoch ${rateLimitReset})`
: '';
throw new Error(`${method} ${url} failed (${response.status})${rateLimitHint}: ${text}`);
}
if (response.status === 204) {
return null;
}
try {
return await response.json();
} catch (error) {
throw new Error(`Failed to parse JSON response for ${method} ${url}: ${error.message}`);
}
}
async function getAllPullFiles(apiBaseUrl, repository, pullNumber) {
const files = [];
let page = 1;
while (true) {
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}/files?per_page=100&page=${page}`;
const batch = await apiRequest(url);
if (!Array.isArray(batch)) {
throw new Error(`Unexpected response while listing PR files on page ${page}.`);
}
if (batch.length === 0) {
break;
}
files.push(...batch);
if (batch.length < 100) {
break;
}
page += 1;
}
return files;
}
async function getPullRequest(apiBaseUrl, repository, pullNumber) {
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}`;
const pullRequest = await apiRequest(url);
if (!pullRequest || typeof pullRequest !== 'object') {
throw new Error('Unexpected response while fetching pull request details.');
}
return pullRequest;
}
async function ensureReviewerRequested(apiBaseUrl, repository, pullNumber, pullRequest) {
const authorLogin = String(pullRequest?.user?.login || '').toLowerCase();
const targetReviewer = REVIEWER_LOGIN.toLowerCase();
if (authorLogin === targetReviewer) {
console.log(`Skipping reviewer request: ${REVIEWER_LOGIN} is the PR author.`);
return;
}
const requestedReviewers = Array.isArray(pullRequest?.requested_reviewers)
? pullRequest.requested_reviewers
: [];
const alreadyRequested = requestedReviewers.some(
(reviewer) => String(reviewer?.login || '').toLowerCase() === targetReviewer
);
if (alreadyRequested) {
console.log(`Reviewer ${REVIEWER_LOGIN} is already requested.`);
return;
}
const url = `${apiBaseUrl}/repos/${repository}/pulls/${pullNumber}/requested_reviewers`;
try {
await apiRequest(url, 'POST', { reviewers: [REVIEWER_LOGIN] });
console.log(`Requested reviewer ${REVIEWER_LOGIN}.`);
} catch (error) {
// Reviewer request should not fail the telemetry guidance workflow.
console.warn(
`Unable to request reviewer ${REVIEWER_LOGIN}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
async function findExistingTelemetryComment(apiBaseUrl, repository, pullNumber) {
let page = 1;
while (true) {
const commentsUrl = `${apiBaseUrl}/repos/${repository}/issues/${pullNumber}/comments?per_page=100&page=${page}`;
const comments = await apiRequest(commentsUrl);
if (!Array.isArray(comments)) {
throw new Error(`Unexpected response while listing issue comments on page ${page}.`);
}
const existing = comments.find(
(comment) => typeof comment.body === 'string' && comment.body.includes(COMMENT_MARKER)
);
if (existing) {
return existing;
}
if (comments.length < 100) {
return null;
}
page += 1;
}
}
function detectTelemetryChanges(files) {
const matches = [];
for (const file of files) {
const filename = file.filename || '';
const telemetryPath = isTelemetryPath(filename);
const changedLines = changedLinesFromPatch(file.patch);
const telemetryLineSignal = hasTelemetryLineSignal(changedLines);
// Some large diffs omit patch content. If the file path is telemetry-centric,
// treat it as a telemetry modification to avoid false negatives.
const patchUnavailable = !file.patch && telemetryPath;
if (telemetryPath || telemetryLineSignal || patchUnavailable) {
matches.push({
filename,
telemetryPath,
telemetryLineSignal,
patchUnavailable,
});
}
}
return matches;
}
function hasDataAndPrivacyChange(files) {
return files.some((file) => {
const filename = (file.filename || '').toLowerCase();
return filename === 'data_and_privacy.md';
});
}
async function upsertPrComment(apiBaseUrl, repository, pullNumber, body) {
const existing = await findExistingTelemetryComment(apiBaseUrl, repository, pullNumber);
if (existing) {
const updateUrl = `${apiBaseUrl}/repos/${repository}/issues/comments/${existing.id}`;
await apiRequest(updateUrl, 'PATCH', { body });
console.log(`Updated existing telemetry comment (id: ${existing.id}).`);
return;
}
const createUrl = `${apiBaseUrl}/repos/${repository}/issues/${pullNumber}/comments`;
await apiRequest(createUrl, 'POST', { body });
console.log('Created telemetry comment on PR.');
}
async function main() {
const eventPath = requireEnv('GITHUB_EVENT_PATH');
const repository = requireEnv('GITHUB_REPOSITORY');
const apiBaseUrl = process.env.GITHUB_API_URL || 'https://api.github.com';
validateRepository(repository);
let parsedApiBaseUrl;
try {
parsedApiBaseUrl = new URL(apiBaseUrl);
} catch {
throw new Error(`Invalid GITHUB_API_URL: ${JSON.stringify(apiBaseUrl)}`);
}
const event = readEventPayload(eventPath);
const pullNumber = resolvePullNumber(event);
console.log(`Event name: ${process.env.GITHUB_EVENT_NAME || 'unknown'}`);
console.log(`Repository: ${repository}`);
console.log(`PR number: ${pullNumber}`);
const files = await getAllPullFiles(parsedApiBaseUrl.origin, repository, pullNumber);
if (files.length === 0) {
console.log('No changed files found for PR; skipping telemetry comment update.');
return;
}
const matches = detectTelemetryChanges(files);
const dataAndPrivacyChanged = hasDataAndPrivacyChange(files);
console.log(`Scanned ${files.length} changed files.`);
console.log(`Telemetry matches found: ${matches.length}.`);
console.log(`DATA_AND_PRIVACY.md changed: ${dataAndPrivacyChanged}.`);
if (matches.length === 0) {
console.log('No telemetry-related additions/modifications detected.');
return;
}
for (const match of matches) {
console.log(
`- ${match.filename} (telemetryPath=${match.telemetryPath}, telemetryLineSignal=${match.telemetryLineSignal}, patchUnavailable=${match.patchUnavailable})`
);
}
try {
const pullRequest = await getPullRequest(parsedApiBaseUrl.origin, repository, pullNumber);
await ensureReviewerRequested(parsedApiBaseUrl.origin, repository, pullNumber, pullRequest);
} catch (error) {
console.warn(
'Failed to fetch PR details or request reviewer; continuing to post telemetry guidance comment.'
);
console.warn(error instanceof Error ? error.stack || error.message : error);
}
const commentBody = dataAndPrivacyChanged
? COMMENT_BODY_WITH_PRIVACY_UPDATE
: COMMENT_BODY_WITHOUT_PRIVACY_UPDATE;
await upsertPrComment(apiBaseUrl, repository, pullNumber, commentBody);
}
main().catch((error) => {
console.error('Telemetry PR check failed.');
console.error(error instanceof Error ? error.stack || error.message : error);
process.exit(1);
});

View File

@@ -31,7 +31,9 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
## Migration Strategy
### Recommended Order
### Phase-by-Phase Scope
Work on bounded problems, not the entire codebase at once. Each phase should compile before moving to the next.
1. **Project file** — Update TFM, NuGet packages, set `<UseWinUI>true</UseWinUI>`
2. **Data models and business logic** — No UI dependencies, migrate first
@@ -45,79 +47,213 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
10. **Installer & build pipeline** — Update WiX, signing, build events
11. **Tests** — Adapt for WinUI 3 runtime, async patterns
### Key Principles
### Migration Contract: Prohibited Patterns
- **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.
These rules capture human judgment and must be applied consistently across every file. Do NOT deviate.
**Architecture prohibitions:**
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different lifecycle boilerplate. Merge resources and init code into the generated WinUI 3 App class.
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. Causes phantom build artifacts.
- **Do NOT instantiate services directly** — Use DI and CommunityToolkit.Mvvm patterns.
- **Do NOT create a `Window` subclass for every dialog or sub-page** — use `ContentDialog` for in-app dialogs and `Frame`/`Page` navigation for sub-views. Separate `Window` classes are reserved for distinct top-level surfaces (e.g., FancyZones editor, OOBE).
- **Do NOT omit `WindowsPackageType=None` and `WindowsAppSDKSelfContained=true`** — Both are mandatory in the csproj for every WinUI 3 module in PowerToys. Without them the app crashes at startup with `COMException: ClassFactory cannot supply requested class` because the WinUI 3 runtime DLLs are not found.
**XAML prohibitions:**
- **Do NOT use `{DynamicResource}`** — Replace with `{ThemeResource}` (theme-reactive) or `{StaticResource}`.
- **Do NOT use `{Binding}` in `Setter.Value`** — Not supported in WinUI 3. Use `{StaticResource}`.
- **Do NOT use `{x:Static}`** — Replace with `{x:Bind}`, `x:Uid`, or code-behind.
- **Do NOT use `{x:Type}`** — Not supported. Use `x:DataType` for DataTemplate, or code-behind.
- **Do NOT use `clr-namespace:`** — Replace with `using:` in all xmlns declarations.
- **Do NOT use `Style.Triggers` / `DataTrigger` / `EventTrigger`** — Replace with `VisualStateManager`.
- **Do NOT use `MultiBinding`** — Replace with `x:Bind` function binding or computed ViewModel property.
- **Do NOT use `Visibility="Hidden"`** — WinUI only has `Visible` and `Collapsed`. Use `Opacity="0"` if layout must be preserved.
- **Do NOT use `IsDefault` / `IsCancel`** — Use `AccentButtonStyle` for primary button; handle Enter/Escape in code-behind.
- **Do NOT omit `BasedOn` when overriding default styles** — Without it, your style replaces the entire default. Always use `BasedOn="{StaticResource DefaultButtonStyle}"` etc.
- **Do NOT omit `XamlControlsResources` as first merged dictionary** — It provides default Fluent styles. Without it, controls have no visual appearance.
**Code-behind prohibitions:**
- **Do NOT use `Application.Current.Dispatcher`** — Store `DispatcherQueue` in a static field explicitly.
- **Do NOT use `Window.Current`** — Not supported. Use a custom `App.Window` static property.
- **Do NOT put `DataContext`, `Resources`, or `VisualStateManager` on `Window`** — WinUI 3 `Window` is NOT a `DependencyObject`. Use a root `Page`/`UserControl`/`Grid`.
- **Do NOT use tunneling/preview events** (`PreviewMouseDown`, `PreviewKeyDown`) — WinUI has no tunneling. Use bubbling equivalents with `Handled` property or `AddHandler(handledEventsToo: true)`.
**Resource prohibitions:**
- **Do NOT use `Properties.Resources.MyString`** — Replace with `ResourceLoaderInstance.ResourceLoader.GetString("MyString")`.
- **Do NOT initialize `ResourceLoader`-dependent values as static fields** — Wrap in `Lazy<T>` or null-coalescing property.
- **Do NOT use `pack://` URIs** — Replace with `ms-appx:///` scheme.
## 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` |
| WPF | WinUI 3 | Notes |
|-----|---------|-------|
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) | Split by purpose |
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph |
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
| `System.Windows.Navigation` | **No direct equivalent** | Use `Frame.Navigate()` |
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
| `System.Windows.Interop` | `WinRT.Interop` / `Microsoft.UI.Xaml.Hosting` | HWND interop |
### Control Replacements (No 1:1 Mapping)
These WPF controls have no direct counterpart and require a different control or third-party package:
| WPF Control | WinUI 3 Replacement | Notes |
|-------------|---------------------|-------|
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. Legacy code may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 |
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; Labs component is experimental/partial |
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyout` | `MenuBar` for classic menu, `MenuFlyout` for context |
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
| `DocumentViewer` | `WebView2` | Render PDFs/XPS inside WebView2 |
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
| `RichTextBox` | `RichEditBox` | Rich text editing |
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
| `MessageBox` | `ContentDialog` | Must set `XamlRoot` before `ShowAsync()` |
| `MediaElement` | `MediaPlayerElement` | Different API |
| `AccessText` | Not available | Use `AccessKey` property on target control |
### Layout & Header Controls from CommunityToolkit.WinUI
These WPF controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but the registered XAML namespace is the shorter `CommunityToolkit.WinUI.Controls` (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u)).
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|-------------|---------------------|---------------|----------------|
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
### No Equivalent — Requires Architectural Rework
These WPF features have no WinUI counterpart and require redesign, not find-and-replace:
| WPF Feature | WinUI 3 Replacement Strategy |
|-------------|------------------------------|
| `Style.Triggers` / `DataTrigger` | `VisualStateManager` with `StateTrigger` — see [XAML Migration](./references/xaml-migration.md) |
| `MultiBinding` | `x:Bind` function binding: `{x:Bind local:Converters.Format(VM.A, VM.B), Mode=OneWay}` |
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` from CommunityToolkit.Mvvm. WinUI also has `StandardUICommand` / `XamlUICommand` for platform commands. |
| `AdornerLayer` / `Adorner` | Depends on use case: `TeachingTip`/`InfoBar` (validation), `Popup` (overlays), `PlaceholderText` (watermarks), Canvas overlay (decorations) |
| `Visibility.Hidden` | `Opacity="0"` with `Visibility="Visible"` (preserves layout space) |
| `Window.Resources` / `Window.DataContext` | Move to root `Grid.Resources` / root `Page`/`UserControl` — WinUI `Window` is NOT a DependencyObject |
| Tunneling events (`Preview*`) | Use bubbling equivalents + `Handled` property or `AddHandler(handledEventsToo: true)` |
### Critical API Replacements
| WPF | WinUI 3 | Notes |
|-----|---------|-------|
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`) |
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`), async by default |
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` | Property vs method |
| `Application.Current.Dispatcher` | Store `DispatcherQueue` in static field | See [Threading](./references/threading-and-windowing.md) |
| `Window.Current` | Custom `App.Window` static property | Not supported in Windows App SDK |
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
| `MessageBox.Show()` | `ContentDialog` | Must set `XamlRoot` |
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` | Remove `CommandBinding`; bind `ICommand` directly |
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
| `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 |
| `DataType="{x:Type m:Foo}"` | `x:DataType="m:Foo"` | `x:Type` not supported |
| `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 |
### Event Replacements (Mouse → Pointer)
| 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` | `MouseEventArgs``PointerRoutedEventArgs` |
| `MouseWheel` | `PointerWheelChanged` | Different event args |
| `MouseEnter` / `MouseLeave` | `PointerEntered` / `PointerExited` | |
| `MouseDoubleClick` | `DoubleTapped` | Different event args |
| `PreviewMouseDown` | `PointerPressed` | No tunneling — use `Handled` or `AddHandler` |
| `PreviewKeyDown` | `KeyDown` | `KeyEventArgs``KeyRoutedEventArgs` |
### Property Replacements
| WPF | WinUI 3 | Context |
|-----|---------|---------|
| `Visibility.Hidden` | `Visibility.Collapsed` or `Opacity="0"` | Use `Opacity="0"` to preserve layout |
| `TextWrapping.WrapWithOverflow` | `TextWrapping.Wrap` | WinUI doesn't distinguish |
| `Focusable="True"` | `IsTabStop="True"` | Different property name |
| `ContextMenu=` | `ContextFlyout=` | On any `UIElement` |
| `MediaElement` | `MediaPlayerElement` | Different API |
| `SnapsToDevicePixels` | Not available | WinUI handles pixel snapping internally |
### 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` |
| WPF | WinUI 3 | Notes |
|-----|---------|-------|
| `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` | Required |
| (none) | `Microsoft.Windows.SDK.BuildTools` | Required |
| (none) | `WinUIEx` | Optional, window helpers |
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox` |
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter` |
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only for migrating existing `DataGrid` code; prefer `WinUI.TableView` |
### 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 |
| WPF | WinUI 3 | Notes |
|-----|---------|-------|
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` | CLR → using syntax |
| `{DynamicResource Key}` | `{ThemeResource Key}` | Re-evaluates on theme change |
| `{StaticResource Key}` | `{StaticResource Key}` | Same — resolved once at load |
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind | |
| `{x:Type local:MyType}` | Not supported | Use `x:DataType` for DataTemplate |
| `{x:Array}` | Not supported | Create collections in code-behind |
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` | See [XAML Migration](./references/xaml-migration.md) |
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` | |
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` | |
| `sys:String` / `sys:Int32` / etc. | `x:String` / `x:Int32` / etc. | XAML intrinsic types |
| `<ui:FluentWindow>` (WPF-UI) | `<Window>` | Native + `ExtendsContentIntoTitleBar` |
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` | |
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` | Named style keys |
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / KeyDown | |
| `<AccessText>` | Not available — use `AccessKey` property | |
| `<behaviors:Interaction.Triggers>` | Code-behind or WinUI behaviors | |
| `Window.Resources` | Root container's `Resources` (e.g. `Grid.Resources`) | Window is not a DependencyObject |
### Binding: {Binding} vs {x:Bind}
Both work in WinUI 3. Prefer `{x:Bind}` for new/migrated code.
| Feature | `{Binding}` | `{x:Bind}` |
|---------|------------|------------|
| Default mode | `OneWay` | **`OneTime`** — add `Mode=OneWay` explicitly! |
| Default source | `DataContext` | Page/UserControl code-behind |
| Compile-time validation | No | Yes |
| Function binding | No | Yes (replaces `MultiBinding`) |
| Performance | Reflection-based | Compiled, no reflection |
| `MultiBinding` support | No (not in WinUI) | Use function binding |
## Detailed Reference Docs
@@ -151,6 +287,8 @@ Read only the section relevant to your current task:
| 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` |
| `COMException: ClassFactory cannot supply requested class` at startup | Missing `<WindowsPackageType>None</WindowsPackageType>` and/or `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>` in csproj. Without these, the app tries to locate the Windows App SDK framework package (not installed) instead of using bundled runtime DLLs. **Both properties are mandatory for every WinUI 3 module in PowerToys.** |
| `CombinedGeometry` not available in WinUI 3 | WinUI 3 `UIElement.Clip` only accepts `RectangleGeometry`. For overlay hole effects (exclude region), use a `Path` element with `GeometryGroup FillRule="EvenOdd"` containing two `RectangleGeometry` children — the EvenOdd rule creates a transparent hole where geometries overlap. |
## Troubleshooting
@@ -163,3 +301,4 @@ Read only the section relevant to your current task:
| 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 |
| `COMException` / `ClassFactory` error at app launch | Ensure csproj has `<WindowsPackageType>None</WindowsPackageType>` and `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>`. These are required for all unpackaged WinUI 3 apps in PowerToys — without them the WinUI 3 COM runtime cannot be found. |

View File

@@ -4,24 +4,26 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
## 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` |
| WPF Namespace | WinUI 3 Namespace | Notes |
|---------------|-------------------|-------|
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` | |
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph only |
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` | UI display only — use `Windows.Graphics.Imaging` for processing |
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` | For Frame navigation events; no `NavigationService` |
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
| `System.Windows.Interop` | `WinRT.Interop` | HWND interop |
| `System.Windows.Interop` | `Microsoft.UI.Xaml.Hosting` | XAML Islands |
## Core Type Mapping
@@ -45,7 +47,7 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
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`
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
### Controls With Different Names or Behavior
@@ -56,6 +58,7 @@ These controls exist in both frameworks with the same name — change `System.Wi
| `TabControl` | `TabView` | Different API |
| `Menu` | `MenuBar` | Different API |
| `StatusBar` | Custom `StackPanel` layout | No built-in equivalent |
| `ListBox` | `ListView` (or `ItemsView`) | **Deprecated in WinUI 3 — do not use.** Prefer `ListView`, or `ItemsView` (WinUI 1.5+) for modern collection scenarios |
| `AccessText` | Not available | Use `AccessKey` property on target control |
### WPF-UI (Lepo) to Native WinUI 3
@@ -109,6 +112,43 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
| `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 |
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
| `System.Windows.Input.RoutedUICommand` | `Microsoft.UI.Xaml.Input.StandardUICommand` / `XamlUICommand` | Or use `ICommand` / `[RelayCommand]` |
| `System.Windows.Input.CommandBinding` | **Remove** | Bind `ICommand` directly in XAML |
## Controls That Need Translation (No 1:1 Mapping)
These controls exist in WPF but require a different control, third-party library, or Community Toolkit package in WinUI 3:
| WPF Control | WinUI 3 Replacement | Package / Notes |
|-------------|---------------------|-----------------|
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. PowerToys legacy modules may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 — prefer `WinUI.TableView` for new work. |
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; the Labs component is experimental and partial |
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyoutItem` | `MenuBar` for classic menu, `MenuFlyout` for context |
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
| `DocumentViewer` | `WebView2` | `Microsoft.Web.WebView2` — render PDFs/XPS |
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
| `RichTextBox` | `RichEditBox` | Rich text editing |
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
| `MediaElement` | `MediaPlayerElement` | Different API |
## Layout & Header Controls from CommunityToolkit.WinUI
These WPF layout/header controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but both packages register their controls in the shorter `CommunityToolkit.WinUI.Controls` XAML namespace (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u): *"all controls live under `CommunityToolkit.WinUI.Controls` … This is intentional"*).
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|-------------|---------------------|---------------|----------------|
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
Other primitives in `Primitives`: `ConstrainedBox`, `SwitchPresenter`, `WrapLayout`, `StaggeredPanel`. Other Headered controls in `HeaderedControls`: `HeaderedItemsControl`, `HeaderedTreeView`.
## NuGet Package Migration
@@ -124,6 +164,11 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
| (none) | `WinUIEx` | Optional, window helpers |
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
| (none) | `CommunityToolkit.WinUI.Extensions` | Optional |
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter` |
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter`, `PropertySizer` |
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only if migrating existing `DataGrid` code; prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work |
| (none) | `Microsoft.Web.WebView2` | If using WebView |
## Project File Changes
@@ -167,8 +212,9 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
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`
- **CRITICAL: Add `WindowsPackageType=None`** — marks the app as unpackaged (no MSIX). Without this, the build produces an MSIX-style package that won't run as a standalone PowerToys module.
- **CRITICAL: Add `WindowsAppSDKSelfContained=true`** — bundles the Windows App SDK runtime DLLs (e.g. `Microsoft.UI.Xaml.dll`) into the output directory. Without this, the app throws `COMException: ClassFactory cannot supply requested class` at startup because the WinUI 3 COM classes cannot be found.
- Add `SelfContained=true` (usually via `Common.SelfContained.props`)
- 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>/`

View File

@@ -127,6 +127,74 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
</Window>
```
#### Recommended: Use WindowEx from WinUIEx
> **Tip:** Prefer `WinUIEx.WindowEx` over bare `Window`. It restores many WPF-like window properties directly in XAML, avoiding boilerplate code-behind for common windowing tasks.
```xml
<!-- WinUI 3 with WindowEx (preferred in PowerToys) -->
<winuiex:WindowEx
xmlns:winuiex="using:WinUIEx"
x:Class="MyApp.MainWindow"
MinWidth="480"
MinHeight="320"
IsShownInSwitchers="True"
IsTitleBarVisible="True">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
...
</Grid>
</winuiex:WindowEx>
```
Properties available on `WindowEx` that mirror WPF `Window`:
| WPF Window Property | WindowEx Property | Notes |
|---------------------|-------------------|-------|
| `MinWidth` / `MinHeight` | `MinWidth` / `MinHeight` | Set directly in XAML |
| `Width` / `Height` | `Width` / `Height` | Initial window size |
| `WindowState` | `WindowState` | Minimized, Maximized, Normal |
| `Title` | `Title` or `x:Uid` | Window title |
| `Icon` | Use `TitleBar.IconSource` | Via WinUI TitleBar control |
| `ShowInTaskbar` | `IsShownInSwitchers` | Alt-Tab visibility |
| `TopMost` | `IsAlwaysOnTop` | Always-on-top window |
NuGet: `WinUIEx` — already referenced by most PowerToys modules.
#### Recommended: Page-in-Window Architecture
> **Tip:** WinUI 3 `Window` is NOT a `FrameworkElement` — it does not support `Resources`, `DataContext`, `x:Bind`, or `VisualStateManager` directly. Place a `Page` as the Window's root content to regain these WPF-like capabilities.
```xml
<!-- WinUI 3 — Window contains a Page for full FrameworkElement support -->
<winuiex:WindowEx x:Class="MyApp.MainWindow"
xmlns:winuiex="using:WinUIEx"
xmlns:views="using:MyApp.Views">
<views:MainPage x:Name="mainPage" />
</winuiex:WindowEx>
```
```xml
<!-- MainPage.xaml — has full FrameworkElement capabilities -->
<Page x:Class="MyApp.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Page.Resources>
<!-- Resources work here (unlike on Window) -->
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
</Page.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<!-- VisualStateManager works here (unlike on Window) -->
</VisualStateManager.VisualStateGroups>
...
</Grid>
</Page>
```
This is the standard pattern in PowerToys (e.g., FileLocksmith, EnvironmentVariables).
### App.xaml Resources
```xml
@@ -150,6 +218,20 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
</Application.Resources>
```
### CommunityToolkit.WinUI — WPF Replacement Controls
> **Tip:** The `CommunityToolkit.WinUI` package provides many controls and helpers familiar to WPF developers that are missing from WinUI 3 out of the box. Before writing custom replacements, check whether CommunityToolkit already provides what you need.
Key packages (XAML namespace is `using:CommunityToolkit.WinUI.Controls` for the `Controls.*` family):
- **`CommunityToolkit.WinUI.Controls.Primitives`** — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter`
- **`CommunityToolkit.WinUI.Controls.HeaderedControls`** — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView`
- **`CommunityToolkit.WinUI.Controls.SettingsControls`** — `SettingsCard`, `SettingsExpander`
- **`CommunityToolkit.WinUI.Controls.Sizers`** — `GridSplitter`, `PropertySizer`, `ContentSizer`
- **`CommunityToolkit.WinUI.UI.Controls.DataGrid`** — legacy v7 `DataGrid` (no longer maintained); prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work
- **`CommunityToolkit.WinUI.Converters`** — Common value converters (`BoolToVisibilityConverter`, `StringFormatConverter`, etc.)
- **`CommunityToolkit.WinUI.Behaviors`** — XAML behaviors for animations and interactions
- **`CommunityToolkit.WinUI.Extensions`** — Extension methods for WinUI types
### Common Control Replacements
```xml
@@ -187,11 +269,135 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
<!-- Handle Enter/Escape keys in code-behind if needed -->
```
## No-Equivalent Patterns (Requires Architectural Rework)
These WPF features demand design changes, not find-and-replace. Read this section BEFORE attempting to migrate any file that uses these patterns.
### MultiBinding → x:Bind Function Binding
WinUI does not support `MultiBinding`. Replace with `x:Bind` function binding (most direct replacement), a computed ViewModel property, or multiple simple bindings.
**WPF:**
```xml
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
```
**WinUI 3:**
```xml
<TextBlock Text="{x:Bind local:Converters.FormatFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}" />
```
```csharp
public static class Converters
{
public static string FormatFullName(string first, string last) => $"{first} {last}";
}
```
### Adorners → Context-Dependent Replacements
WPF's `AdornerLayer` has no WinUI equivalent. Choose replacement by use case:
| Adorner Use Case | WinUI 3 Replacement |
|------------------|---------------------|
| Validation indicators | `TeachingTip`, `InfoBar`, or InputValidation templates |
| Resize handles | `Popup` positioned relative to target |
| Drag preview | `DragItemsStarting` event with custom DragUI |
| Overlay decorations | Canvas overlay or Popup layer |
| Watermark / Placeholder | `TextBox.PlaceholderText` (built-in) |
### RoutedUICommand → ICommand / RelayCommand
WinUI does not support routed commands or `CommandBinding`. Replace with standard `ICommand` pattern:
```csharp
// CommunityToolkit.Mvvm
[RelayCommand(CanExecute = nameof(CanSave))]
private void Save() { /* save logic */ }
private bool CanSave() => IsDirty;
```
WinUI 3 also provides `StandardUICommand` and `XamlUICommand` for pre-defined platform commands (Cut, Copy, Paste, Delete) with built-in icons and keyboard accelerators.
### Tunneling / Preview Events
WinUI has no tunneling event model. `PreviewMouseDown`, `PreviewKeyDown`, etc. do not exist.
- Replace with the bubbling equivalent (`PointerPressed`, `KeyDown`)
- If you relied on tunneling to intercept events before children, restructure using the `Handled` property
- For must-handle scenarios, use `AddHandler` with `handledEventsToo: true`:
```csharp
myElement.AddHandler(UIElement.PointerPressedEvent,
new PointerEventHandler(OnPointerPressed), handledEventsToo: true);
```
## Style and Template Changes
### Implicit Styles — Always Use BasedOn
> **Warning:** In WinUI 3, always use `BasedOn` when overriding default control styles. Without it, your style **replaces the entire default style** rather than extending it.
```xml
<!-- WRONG — replaces entire default style, control may lose all visual appearance -->
<Style TargetType="Button">
<Setter Property="Background" Value="Red" />
</Style>
<!-- CORRECT — extends the default style -->
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
<Setter Property="Background" Value="Red" />
</Style>
```
### Triggers → VisualStateManager
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported. Two replacement approaches:
#### Approach 1: StateTrigger (direct DataTrigger replacement — simpler)
Use this for data-driven state changes. This is the closest equivalent to WPF `DataTrigger`:
**WPF:**
```xml
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
```
**WinUI 3:**
```xml
<Border x:Name="MyBorder">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Active">
<VisualState.StateTriggers>
<StateTrigger IsActive="{x:Bind ViewModel.IsActive, Mode=OneWay}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="MyBorder.Background" Value="Green" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
```
Note: `VisualStateManager` must be placed on a control inside the Window, NOT on the Window itself.
#### Approach 2: ControlTemplate (for property triggers like IsMouseOver)
Use this when replacing `<Trigger Property="IsMouseOver">` or similar control-state triggers:
**WPF:**
```xml
@@ -200,9 +406,6 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
<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>
```
@@ -249,11 +452,42 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
| `Disabled` | `Disabled` |
| `Pressed` | `Pressed` |
## Visibility.Hidden — No Equivalent
WinUI only has `Visible` and `Collapsed`. There is no `Hidden`.
| WPF | WinUI 3 | Behavior |
|-----|---------|----------|
| `Visibility.Visible` | `Visibility.Visible` | Rendered and occupies layout space |
| `Visibility.Hidden` | **Not available** | Use `Opacity="0"` with `Visibility="Visible"` to hide but keep layout |
| `Visibility.Collapsed` | `Visibility.Collapsed` | Not rendered, no layout space |
## Resource Dictionary Changes
### Window.Resources → Grid.Resources
### XamlControlsResources Must Be First
WinUI 3 `Window` is NOT a `DependencyObject` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
> **Warning:** `XamlControlsResources` must be the **first** merged dictionary in `App.xaml`. It provides the default Fluent styles. Omitting it gives you controls with no visual appearance. Resource paths use `ms-appx:///` instead of relative paths.
```xml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- MUST be first -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Then your custom dictionaries -->
<ResourceDictionary Source="ms-appx:///Styles/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
```
### Window.Resources → Grid.Resources (or use Page)
WinUI 3 `Window` is NOT a `FrameworkElement` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
**Preferred approach:** Use the [Page-in-Window architecture](#recommended-page-in-window-architecture) described above. A `Page` inside the `Window` gives you full `FrameworkElement` capabilities (Resources, DataContext, x:Bind, VisualStateManager).
**Fallback** (for simple windows without a Page):
```xml
<!-- WPF -->
@@ -317,19 +551,25 @@ Both are available. Prefer `{x:Bind}` for compile-time safety and performance.
| Performance | Reflection-based | Compiled |
| Function binding | No | Yes |
### WPF-Specific Binding Features to Remove
### Binding Differences from WPF
These WPF binding patterns behave differently in WinUI 3 — review on a case-by-case basis rather than mechanically removing.
```xml
<!-- These WPF-only features must be removed or rewritten -->
<!-- UpdateSourceTrigger: limited support in WinUI 3 -->
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
<!-- WinUI 3: UpdateSourceTrigger not needed; TextBox uses PropertyChanged by default -->
<!-- WinUI 3: UpdateSourceTrigger=LostFocus does NOT exist; PropertyChanged is the TextBox default.
Prefer x:Bind, which binds with PropertyChanged semantics by default for TwoWay. -->
<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 -->
<!-- RelativeSource: Self and TemplatedParent ARE supported in WinUI 3 -->
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource Self}}" />
<!-- Works in WinUI 3. FindAncestor mode is NOT supported — use ElementName, x:Bind,
or the CommunityToolkit FrameworkElementExtensions.Ancestor attached property to reach ancestors. -->
<!-- {Binding} empty path: works in WinUI 3 (binds to the current DataContext) -->
<ItemsControl ItemsSource="{Binding}" />
<!-- WinUI 3: Must specify explicit path -->
<!-- This is valid. Note: x:Bind requires an explicit path — there is no empty-path x:Bind. -->
<ItemsControl ItemsSource="{x:Bind ViewModel.Items}" />
```
@@ -355,6 +595,64 @@ ExtendsContentIntoTitleBar="True" <!-- Set in code-behind -->
| `IsHitTestVisible` | `IsHitTestVisible` | Same |
| `TextBox.VerticalScrollBarVisibility` | `ScrollViewer.VerticalScrollBarVisibility` (attached) | Attached property |
## Complete Find-and-Replace Reference
Use this table for mechanical batch translation. Apply these rules consistently to every file.
### XAML Attribute Replacements
| Find | Replace With | Context |
|------|-------------|---------|
| `ContextMenu=` | `ContextFlyout=` | On any UIElement |
| `{DynamicResource ` | `{ThemeResource ` | Theme-responsive references |
| `{x:Static prefix:Resources.Key}` | `x:Uid="Key"` (with `.resw`) | Resource string — most common WPF case; mechanical `{x:Bind}` will NOT compile here |
| `{x:Static prefix:Type.Member}` | `{x:Bind prefix:Type.Member}` | Static field/property reference (function binding) |
| `Visibility="Hidden"` | `Visibility="Collapsed"` | Or use `Opacity="0"` for layout |
| `MouseLeftButtonDown` | `PointerPressed` | Event handlers |
| `MouseLeftButtonUp` | `PointerReleased` | Event handlers |
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` + check `IsRightButtonPressed` |
| `MouseRightButtonUp` | `PointerReleased` + check `IsRightButtonPressed` | No direct WinUI event; use `RightTapped` only for context-menu-open semantics |
| `MouseEnter` | `PointerEntered` | Event handlers |
| `MouseLeave` | `PointerExited` | Event handlers |
| `MouseMove` | `PointerMoved` | Event handlers |
| `MouseWheel` | `PointerWheelChanged` | Event handlers |
| `MouseDoubleClick` | `DoubleTapped` | Event handlers |
| `PreviewMouseDown` | `PointerPressed` | No tunneling in WinUI — remove Preview |
| `PreviewMouseUp` | `PointerReleased` | No tunneling in WinUI — remove Preview |
| `PreviewKeyDown` | `KeyDown` | No tunneling in WinUI — remove Preview |
| `PreviewKeyUp` | `KeyUp` | No tunneling in WinUI — remove Preview |
| `PreviewMouseWheel` | `PointerWheelChanged` | No tunneling in WinUI — remove Preview |
| `Focusable="True"` | `IsTabStop="True"` | Focus behavior |
| `Focusable="False"` | `IsTabStop="False"` | Focus behavior |
| `TextWrapping="WrapWithOverflow"` | `TextWrapping="Wrap"` | TextBlock, TextBox |
| `MediaElement` | `MediaPlayerElement` | Media playback |
| `clr-namespace:` | `using:` | xmlns declarations |
| `;assembly=` | (remove) | Assembly qualification not needed |
### Code-Behind Replacements
| Find | Replace With |
|------|-------------|
| `using System.Windows;` | `using Microsoft.UI.Xaml;` |
| `using System.Windows.Controls;` | `using Microsoft.UI.Xaml.Controls;` |
| `using System.Windows.Media;` | `using Microsoft.UI.Xaml.Media;` |
| `using System.Windows.Data;` | `using Microsoft.UI.Xaml.Data;` |
| `using System.Windows.Input;` | `using Microsoft.UI.Xaml.Input;` |
| `using System.Windows.Threading;` | `using Microsoft.UI.Dispatching;` |
| `using System.Windows.Shapes;` | `using Microsoft.UI.Xaml.Shapes;` |
| `using System.Windows.Markup;` | `using Microsoft.UI.Xaml.Markup;` |
| `using System.Windows.Automation;` | `using Microsoft.UI.Xaml.Automation;` |
| `using System.Windows.Media.Animation;` | `using Microsoft.UI.Xaml.Media.Animation;` |
| `using System.Windows.Documents;` | `using Microsoft.UI.Xaml.Documents;` |
| `using System.Windows.Navigation;` | `using Microsoft.UI.Xaml.Navigation;` |
| `Dispatcher.Invoke(` | `DispatcherQueue.TryEnqueue(` |
| `Dispatcher.BeginInvoke(` | `DispatcherQueue.TryEnqueue(` |
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` |
| `MouseEventArgs` | `PointerRoutedEventArgs` |
| `KeyEventArgs` | `KeyRoutedEventArgs` |
| `RoutedUICommand` | `RelayCommand` (CommunityToolkit.Mvvm) |
| `CommandBinding` | Remove; bind ICommand directly |
## XAML Formatting (XamlStyler)
After migration, run XamlStyler to normalize formatting:

View File

@@ -1,8 +1,10 @@
name: Automatic Triaging on Issue Creation
name: Automatic Triaging on Issue/PR Creation
on:
issues:
types: [opened, reopened]
pull_request_target:
types: [opened, reopened, synchronize]
# Manual trigger: go to Actions → "Automatic Triaging on Issue Creation" → Run workflow.
# Enter one or more comma-separated issue numbers (e.g. "1234" or "1234,1235,1236")
# to apply AI-generated area labels to existing untriaged issues.
@@ -15,12 +17,14 @@ on:
permissions:
models: read
issues: write
pull-requests: write
concurrency:
# Each workflow run gets its own concurrency group.
# For issue events, group by issue number so a rapid close+reopen only runs once.
# For pull request events, group by PR number so rapid updates coalesce.
# For manual dispatch (which may cover multiple issues), use the unique run ID.
group: ${{ github.event_name == 'issues' && format('{0}-issue-{1}', github.workflow, github.event.issue.number) || github.run_id }}
group: ${{ github.event_name == 'issues' && format('{0}-issues-{1}', github.workflow, github.event.issue.number) || github.event_name == 'pull_request_target' && format('{0}-pr-{1}', github.workflow, github.event.pull_request.number) || github.run_id }}
cancel-in-progress: true
jobs:
@@ -38,7 +42,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// When triggered manually, process each supplied issue number in turn.
// When triggered by an issue event, use the event's issue number.
// When triggered by an issue or PR event, use the event's number.
let issueNumbers;
if (context.eventName === 'workflow_dispatch') {
issueNumbers = String(context.payload.inputs.issue_numbers)
@@ -55,23 +59,25 @@ jobs:
}
for (const issueNumber of issueNumbers) {
console.log(`\n--- Processing issue #${issueNumber} ---`);
console.log(`\n--- Processing item #${issueNumber} ---`);
await labelIssue(issueNumber);
}
async function labelIssue(issueNumber) {
// Fetch the issue so both the automatic and manual paths have the same data.
// Fetch as an issue resource; PRs are represented by issues with a pull_request field.
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
});
const itemType = issue.pull_request ? 'Pull request' : 'Issue';
const title = issue.title ?? '';
const body = issue.body ?? '';
if (!title && !body) {
console.log(`Issue #${issueNumber} has no title or body; skipping.`);
console.log(`${itemType} #${issueNumber} has no title or body; skipping.`);
return;
}
@@ -88,8 +94,8 @@ jobs:
'Product-Advanced Paste',
'Product-Always On Top',
'Product-Awake',
'Product-ColorPicker',
'Product-Command Not Found',
'Product-Color Picker',
'Product-CommandNotFound',
'Product-Command Palette',
'Product-CropAndLock',
'Product-Environment Variables',
@@ -98,7 +104,7 @@ jobs:
'Product-File Locksmith',
'Product-Find My Mouse',
'Product-Grab And Move',
'Product-Hosts',
'Product-Hosts File Editor',
'Product-Image Resizer',
'Product-Keyboard Manager',
'Product-LightSwitch',
@@ -109,7 +115,7 @@ jobs:
'Product-Mouse Without Borders',
'Product-New+',
'Product-Peek',
'Product-Power Display',
'Product-PowerDisplay',
'Product-PowerRename',
'Product-PowerToys Run',
'Product-Quick Accent',
@@ -124,8 +130,8 @@ jobs:
'Area-Localization',
];
const systemPrompt = `You are a GitHub issue triage assistant for the microsoft/PowerToys repository.
Your job is to classify issues by assigning the correct area label(s).
const systemPrompt = `You are a GitHub triage assistant for the microsoft/PowerToys repository.
Your job is to classify issues and pull requests by assigning the correct area label(s).
Rules:
- Only return labels from the following list, exactly as written:
@@ -136,9 +142,9 @@ jobs:
- Respond with ONLY a JSON array of label strings, no explanation.
Example: ["Product-FancyZones","Product-Settings"]`;
const userPrompt = `Issue title: ${title}
const userPrompt = `${itemType} title: ${title}
Issue body:
${itemType} body:
${body.slice(0, MAX_BODY_LENGTH)}`;
// Validate that the token is available before making the API call.
@@ -203,11 +209,25 @@ jobs:
return;
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: toApply,
});
console.log(`Issue #${issueNumber}: added labels: ${toApply.join(', ')}`);
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: toApply,
});
console.log(`${itemType} #${issueNumber}: added labels: ${toApply.join(', ')}`);
} catch (error) {
// Some contexts (for example, restricted integrations) can deny
// label writes even when workflow permissions request write scope.
// Skip without failing the entire triage workflow.
const status = error?.status;
const message = error?.message ?? String(error);
if (status === 403 && message.includes('Resource not accessible by integration')) {
console.log(`${itemType} #${issueNumber}: skipping label write due to restricted token context (403).`);
return;
}
throw error;
}
}

View File

@@ -21,6 +21,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4

View File

@@ -27,7 +27,7 @@ jobs:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0

View File

@@ -0,0 +1,67 @@
# Builds the Dev Docs website from doc/devdocs with docmd and publishes it to GitHub Pages.
#
# The generated site is uploaded as a Pages artifact and deployed directly. It is never
# committed to the repo, so doc/devdocs-website/site stays untracked (see .gitignore).
#
# Requires GitHub Pages to be enabled with "Source: GitHub Actions" under the repository
# Settings -> Pages.
name: Publish Dev Docs Website
on:
push:
branches:
- main
paths:
- 'doc/devdocs/**'
- 'doc/devdocs-website/**'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
# Allow only one Pages deployment at a time and let an in-progress deploy finish.
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
# Full history so docmd's git plugin can resolve per-page "last updated" dates.
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: latest
- name: Build static site with docmd
working-directory: doc/devdocs-website
# docmd is pinned in package.json; dependencies are installed fresh each run.
run: |
npm install
npm run build
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: doc/devdocs-website/site
# v4+ excludes dotfiles by default; keep docmd's generated .nojekyll.
include-hidden-files: true
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

View File

@@ -0,0 +1,35 @@
# NOTE: This workflow depends on .github/scripts/telemetry-pr-check.js for telemetry detection and PR comments.
# Keep this workflow and script behavior in sync when making changes.
name: Telemetry PR Check
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
workflow_dispatch:
inputs:
pr_number:
description: "Pull Request Number to test against"
required: true
type: string
permissions:
contents: read
pull-requests: write
concurrency:
group: telemetry-pr-check-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
detect-telemetry-events:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Detect telemetry event changes and comment PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node .github/scripts/telemetry-pr-check.js

11
.gitignore vendored
View File

@@ -19,6 +19,9 @@
[Rr]eleases/
x64/
x86/
!**/rnnoise/
!**/rnnoise/x86/
!**/rnnoise/x86/**
ARM64/
bld/
[Bb]in/
@@ -370,3 +373,11 @@ installer/*/*.wxs.bk
.squad-workstream
.github/agents/**squad**.md
.github/workflows/**squad**.yml
# vcpkg manifest mode installed packages
vcpkg_installed/
deps/vcpkg/
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
docs/superpowers/

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "deps/spdlog"]
path = deps/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "deps/expected-lite"]
path = deps/expected-lite
url = https://github.com/martinmoene/expected-lite.git

View File

@@ -212,6 +212,7 @@
"WinUI3Apps\\PowerToys.NewPlus.ShellExtension.win10.dll",
"PowerAccent.Core.dll",
"PowerAccent.Common.dll",
"PowerToys.PowerAccent.dll",
"PowerToys.PowerAccent.exe",
"PowerToys.PowerAccentModuleInterface.dll",
@@ -243,8 +244,12 @@
"WinUI3Apps\\PowerToys.RegistryPreview.dll",
"WinUI3Apps\\PowerToys.RegistryPreview.exe",
"PowerToys.ShortcutGuide.exe",
"PowerToys.ShortcutGuideModuleInterface.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.exe",
"WinUI3Apps\\PowerToys.ShortcutGuide.dll",
"WinUI3Apps\\PowerToys.ShortcutGuideModuleInterface.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.dll",
"WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe",
"WinUI3Apps\\ShortcutGuide.CPPProject.dll",
"PowerToys.ZoomIt.exe",
"PowerToys.ZoomItModuleInterface.dll",
@@ -383,6 +388,11 @@
"ColorCode.Core.dll",
"Microsoft.SemanticKernel.Connectors.Ollama.dll",
"OllamaSharp.dll",
"WinUI3Apps\\Google.Apis.dll",
"WinUI3Apps\\Google.Apis.Auth.dll",
"WinUI3Apps\\Google.Apis.Core.dll",
"WinUI3Apps\\Google.GenAI.dll",
"WinUI3Apps\\YamlDotNet.dll",
"boost_regex-vc143-mt-gd-x32-1_87.dll",
"boost_regex-vc143-mt-gd-x64-1_87.dll",

View File

@@ -104,6 +104,10 @@ extends:
# Have msbuild use the release nuget config profile
additionalBuildOptions: /p:RestoreConfigFile="$(Build.SourcesDirectory)\.pipelines\release-nuget.config" /p:EnableCmdPalAOT=true
beforeBuildSteps:
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
# to redirect it to a safe Microsoft-controlled location
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
# Sets versions for all PowerToy created DLLs
- pwsh: |-
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment ''
@@ -140,6 +144,10 @@ extends:
signCertName: $(SigningSignCertName)
useManagedIdentity: $(SigningUseManagedIdentity)
clientId: $(SigningOriginalClientId)
beforeBuildSteps:
# Install the Terrapin retrieval tool, which replaces vcpkg's download handler
# to redirect it to a safe Microsoft-controlled location
- template: .pipelines/v2/templates/steps-install-terrapin.yml@self
- stage: Publish
displayName: Publish

View File

@@ -270,6 +270,34 @@ jobs:
parameters:
directory: $(build.sourcesdirectory)\src\modules\cmdpal
# --- vcpkg detection + binary cache --------------------------------------
# PowerToys consumes spdlog (and, over time, other native deps) via vcpkg in
# manifest mode. steps-install-vcpkg.yml prefers the vcpkg shipped with
# Visual Studio (Microsoft.VisualStudio.Component.Vcpkg) and falls back to a
# fresh clone of microsoft/vcpkg into deps/vcpkg if VS doesn't have it.
# Either way it sets the VCPKG_ROOT pipeline variable; MSBuild integration
# is wired globally from Cpp.Build.props (with vcpkg.targets in
# Cpp.Build.targets) using the three-tier VcpkgRoot fallback
# (env var > VS-shipped > deps/vcpkg runtime clone).
#
# Vcpkg's MSBuild integration runs `vcpkg install` once per project, so the
# binary cache below saves ~3-5 minutes per triplet on cache hits.
- template: .\steps-install-vcpkg.yml
parameters:
useVSPreview: ${{ parameters.useVSPreview }}
- ${{ if eq(parameters.enablePackageCaching, true) }}:
- task: Cache@2
displayName: 'Cache vcpkg binary archives'
inputs:
# Key on the inputs vcpkg uses to compute its package ABI: the manifest,
# configuration, every overlay-port file, and the agent OS.
key: '"vcpkg" | "$(Agent.OS)" | vcpkg.json | vcpkg-configuration.json | deps/vcpkg-overlays/**'
restoreKeys: |
"vcpkg" | "$(Agent.OS)"
"vcpkg"
path: $(LOCALAPPDATA)\vcpkg\archives
- ${{ parameters.beforeBuildSteps }}

View File

@@ -15,6 +15,9 @@ parameters:
- name: signingIdentity
type: object
default: {}
- name: beforeBuildSteps
type: stepList
default: []
jobs:
- job: "BuildSDK"
@@ -45,6 +48,8 @@ jobs:
parameters:
directory: $(build.sourcesdirectory)\src\modules\cmdpal
- ${{ parameters.beforeBuildSteps }}
- pwsh: |-
& "$(build.sourcesdirectory)\src\modules\cmdpal\extensionsdk\nuget\BuildSDKHelper.ps1" -Configuration "Release" -BuildStep "build" -IsAzurePipelineBuild
displayName: Build SDK

View File

@@ -0,0 +1,6 @@
steps:
- pwsh: |-
nuget install -source "https://microsoft.pkgs.visualstudio.com/Dart/_packaging/PowerToysDependencies/nuget/v3/index.json" TerrapinRetrievalTool -Prerelease -OutputDirectory _trt -Config "$(Build.SourcesDirectory)\.pipelines\release-nuget.config"
$TerrapinRetrievalToolPath = (Get-Item _trt\TerrapinRetrievalTool.*\win-x64\TerrapinRetrievalTool.exe).FullName
Write-Host "##vso[task.setvariable variable=X_VCPKG_ASSET_SOURCES]x-script,${TerrapinRetrievalToolPath} -b https://vcpkg.storage.devpackages.microsoft.io/artifacts/ -a true -u None -p {url} -s {sha512} -d {dst};x-block-origin"
displayName: Set up the Terrapin Retrieval Tool (vcpkg cache)

View File

@@ -0,0 +1,41 @@
# Adapted from microsoft/terminal build/pipelines/templates-v2/steps-install-vcpkg.yml.
#
# Detects vcpkg from (in order):
# 1. The Visual Studio installation (Microsoft.VisualStudio.Component.Vcpkg,
# declared in the repo-root .vsconfig).
# 2. A local clone at deps/vcpkg, cloned and bootstrapped on demand.
#
# Sets the pipeline-scoped VCPKG_ROOT variable; the rest of the build
# resolves vcpkg through it (see the three-tier VcpkgRoot fallback in
# Cpp.Build.props). No repo-level vcpkg submodule required.
parameters:
- name: useVSPreview
type: boolean
default: false
steps:
- pwsh: |-
# vswhere -prerelease is opt-in via the useVSPreview parameter so CI on
# stable VS doesn't accidentally pick up a Preview install when both
# are present. Matches the existing useVSPreview plumbing for
# verifyAndSetLatestVCToolsVersion.ps1.
$vswhereArgs = @('-latest', '-requires', 'Microsoft.VisualStudio.Component.Vcpkg', '-property', 'installationPath')
$useVSPreview = '${{ parameters.useVSPreview }}' -eq 'True'
if ($useVSPreview) { $vswhereArgs = @('-prerelease') + $vswhereArgs }
$VsInstallRoot = & 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vswhereArgs
If ([String]::IsNullOrEmpty($VsInstallRoot)) {
Remove-Item -Recurse -Force deps/vcpkg -ErrorAction:Ignore
git clone https://github.com/microsoft/vcpkg deps/vcpkg
if ($LASTEXITCODE -ne 0) { throw "git clone vcpkg failed (exit $LASTEXITCODE)" }
Push-Location deps/vcpkg
& ./bootstrap-vcpkg.bat -disableMetrics
if ($LASTEXITCODE -ne 0) { Pop-Location; throw "bootstrap-vcpkg failed (exit $LASTEXITCODE)" }
$VcpkgRoot = $PWD
Pop-Location
Write-Host "Using vcpkg from local checkout ($VcpkgRoot)"
} Else {
$VcpkgRoot = Join-Path $VsInstallRoot 'VC\vcpkg'
Write-Host "Using vcpkg from Visual Studio installation ($VcpkgRoot)"
}
Write-Host "##vso[task.setvariable variable=VCPKG_ROOT]$VcpkgRoot"
displayName: Detect VS vcpkg or bootstrap locally

View File

@@ -48,6 +48,11 @@ foreach ($csprojFile in $csprojFilesArray) {
continue
}
# The PowerAccent.Common project does not target WinRT, so skip it
if ($csprojFile -like '*PowerAccent.Common.csproj') {
continue
}
$importExists = Test-ImportSharedCsWinRTProps -filePath $csprojFile
if (!$importExists) {
Write-Output "$csprojFile need to import 'Common.Dotnet.CsWinRT.props'."

View File

@@ -6,10 +6,8 @@
"Microsoft.VisualStudio.Workload.NativeDesktop",
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Workload.Universal",
"Microsoft.VisualStudio.Component.Windows10SDK.19041",
"Microsoft.VisualStudio.Component.Windows10SDK.20348",
"Microsoft.VisualStudio.Component.Windows10SDK.22621",
"Microsoft.VisualStudio.Component.Windows10SDK.26100",
"Microsoft.VisualStudio.Component.Windows11SDK.22621",
"Microsoft.VisualStudio.Component.Windows11SDK.26100",
"Microsoft.VisualStudio.ComponentGroup.UWP.VC",
"Microsoft.VisualStudio.Component.UWP.VC.ARM64",
"Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre",
@@ -18,6 +16,8 @@
"Microsoft.VisualStudio.Component.VC.ATL.ARM64.Spectre",
"Microsoft.VisualStudio.Component.VC.ATL",
"Microsoft.VisualStudio.Component.VC.ATL.Spectre",
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs"
"Microsoft.VisualStudio.Component.Vcpkg",
"Microsoft.VisualStudio.Component.WindowsAppSdkSupport.CSharp",
"Microsoft.VisualStudio.Component.WindowsAppSdkSupport.Cpp"
]
}

View File

@@ -39,7 +39,8 @@
<PropertyGroup>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
<PreferredToolArchitecture Condition="'$(PROCESSOR_ARCHITECTURE)' == 'ARM64' or '$(PROCESSOR_ARCHITEW6432)' == 'ARM64'">arm64</PreferredToolArchitecture>
<VcpkgEnabled>false</VcpkgEnabled>
<!-- vcpkg.targets is imported via Cpp.Build.targets after Microsoft.Cpp.targets. -->
<ForceImportAfterCppTargets>$(MSBuildThisFileDirectory)Cpp.Build.targets</ForceImportAfterCppTargets>
<ReplaceWildcardsInProjectItems>true</ReplaceWildcardsInProjectItems>
<ExternalIncludePath>$(MSBuildThisFileDirectory)deps;$(MSBuildThisFileDirectory)packages;$(ExternalIncludePath)</ExternalIncludePath>
<!-- Enable control flow guard for C++ projects that don't consume any C++ files -->
@@ -121,6 +122,48 @@
<SpectreMitigation>Spectre</SpectreMitigation>
</PropertyGroup>
<!--
vcpkg integration. Set globally and loaded before Microsoft.Cpp.props (via
ForceImportBeforeCppProps) so that vcpkg.props' ClCompile hook is in place
before the C++ targets run. VcpkgRoot is resolved via the same three-tier
fallback used by microsoft/terminal (env var → VS-shipped → deps/vcpkg).
-->
<PropertyGroup Label="vcpkg">
<VcpkgEnabled>true</VcpkgEnabled>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgManifestEnabled>true</VcpkgManifestEnabled>
<VcpkgManifestRoot>$(MSBuildThisFileDirectory)</VcpkgManifestRoot>
<VcpkgOSTarget>windows</VcpkgOSTarget>
<VcpkgUseStatic>true</VcpkgUseStatic>
<!--
Force VcpkgConfiguration to follow $(Configuration). Without this,
vcpkg.props infers VcpkgConfiguration from $(UseDebugLibraries), which
Microsoft.Cpp.Default.props has already defaulted to 'false' by the
time vcpkg.props is imported here (the PowerToys-wide Debug override
below runs LATER). That would silently link the Release-built spdlog
into Debug consumers and trigger LNK2038 (MT/MTd, _ITERATOR_DEBUG_LEVEL).
-->
<VcpkgConfiguration>$(Configuration)</VcpkgConfiguration>
<!-- vcpkg validates triplets case-sensitively; PowerToys uses ARM64 capital-case. -->
<VcpkgPlatformTarget Condition="'$(Platform)' == 'ARM64'">arm64</VcpkgPlatformTarget>
<VcpkgApplocalDeps>false</VcpkgApplocalDeps>
<VcpkgInstalledDir>$(MSBuildThisFileDirectory)vcpkg_installed\$(Platform)\</VcpkgInstalledDir>
<VcpkgRoot Condition="'$(VcpkgRoot)' == ''">$(VCPKG_ROOT)</VcpkgRoot>
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' and '$(VsInstallRoot)' != ''">$(VsInstallRoot)\VC\vcpkg</VcpkgRoot>
<VcpkgRoot Condition="'$(VcpkgRoot)' == '' or !Exists('$(VcpkgRoot)\vcpkg.exe')">$(MSBuildThisFileDirectory)deps\vcpkg</VcpkgRoot>
<CAExcludePath>$(CAExcludePath);$(VcpkgInstalledDir)</CAExcludePath>
<VCPkgLocalAppDataDisabled>true</VCPkgLocalAppDataDisabled>
</PropertyGroup>
<!-- Fail fast with an actionable message instead of opaque C1083 spdlog/spdlog.h errors. -->
<Target Name="PowerToysEnsureVcpkgAvailable"
BeforeTargets="PrepareForBuild"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and !Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')">
<Error Text="PowerToys requires the 'vcpkg' Visual Studio component, but it was not found.%0A%0AOpen the Visual Studio Installer, click Modify on your VS install, search for 'vcpkg', enable 'C++ vcpkg package manager', and click Modify. (Visual Studio will also prompt you to install missing .vsconfig components when you open PowerToys.slnx.)%0A%0AIf you have vcpkg installed elsewhere, set the VCPKG_ROOT environment variable to its root before building.%0A%0ASearched: '$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props'" />
</Target>
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.props')" />
<!-- Debug/Release props -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>

16
Cpp.Build.targets Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!--
PowerToys global C++ post-targets. Wired in via
<ForceImportAfterCppTargets> in Cpp.Build.props so MSBuild loads this
file AFTER Microsoft.Cpp.targets for every .vcxproj.
Conditionally imports vcpkg.targets to hook ClCompile into vcpkg's
VcpkgInstallManifestDependencies target so spdlog headers are
auto-discovered on the include path and spdlog.lib is auto-linked.
vcpkg.props is imported in Cpp.Build.props (before Microsoft.Cpp.props);
vcpkg.targets needs the matching "after" hook here.
-->
<Import Project="$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets"
Condition="'$(MSBuildProjectExtension)' == '.vcxproj' and '$(VcpkgEnabled)' == 'true' and Exists('$(VcpkgRoot)\scripts\buildsystems\msbuild\vcpkg.targets')" />
</Project>

View File

@@ -41,34 +41,34 @@
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.102" />
<PackageVersion Include="Microsoft.CommandPalette.Extensions" Version="0.9.260303001" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.8" />
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.7" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.8" />
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.250303.1" />
<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" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.1-preview.1.25571.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.8" />
<PackageVersion Include="Microsoft.AI.Foundry.Local" Version="0.3.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.66.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.66.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" Version="1.66.0-beta" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.66.0-alpha" />
<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.SemanticKernel" Version="1.71.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.71.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureAIInference" Version="1.71.0-beta" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Google" Version="1.71.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.MistralAI" Version="1.71.0-alpha" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.71.0-alpha" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.2" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3719.77" />
<!-- 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="10.0.7" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="10.0.8" />
<PackageVersion Include="Microsoft.WindowsPackageManager.ComInterop" Version="1.10.340" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.7" />
<PackageVersion Include="Microsoft.Windows.Compatibility" Version="10.0.8" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.269" />
<!-- CsWinRT version needs to be set to have a WinRT.Runtime.dll at the same version contained inside the NET SDK we're currently building on CI. -->
<!--
@@ -78,10 +78,10 @@
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.250325.1"/>
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.0.20" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.185" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.0.1" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.1.0" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.2.3" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.2.0" />
<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" />
@@ -90,11 +90,12 @@
<PackageVersion Include="MSTest" Version="$(MSTestVersion)" />
<PackageVersion Include="MSTest.TestFramework" Version="$(MSTestVersion)" />
<PackageVersion Include="NJsonSchema" Version="11.4.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="NLog" Version="5.2.8" />
<PackageVersion Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageVersion Include="NLog.Schema" Version="5.2.8" />
<PackageVersion Include="OpenAI" Version="2.5.0" />
<PackageVersion Include="OpenAI" Version="2.7.0" />
<PackageVersion Include="Polly.Core" Version="8.6.5" />
<PackageVersion Include="ReverseMarkdown" Version="4.1.0" />
<PackageVersion Include="RtfPipe" Version="2.0.7677.4303" />
<PackageVersion Include="ScipBe.Common.Office.OneNote" Version="3.0.1" />
@@ -105,28 +106,28 @@
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<!-- Package System.CodeDom 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.Management but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.CodeDom" Version="10.0.7" />
<PackageVersion Include="System.CodeDom" Version="10.0.8" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.7" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.7" />
<PackageVersion Include="System.Data.OleDb" Version="10.0.7" />
<PackageVersion Include="System.ComponentModel.Composition" Version="10.0.8" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.8" />
<PackageVersion Include="System.Data.OleDb" Version="10.0.8" />
<!-- Package System.Diagnostics.EventLog 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.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.7" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.8" />
<!-- Package System.Diagnostics.PerformanceCounter added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.11. -->
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.7" />
<PackageVersion Include="System.ClientModel" Version="1.7.0" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.7" />
<PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="10.0.8" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.8" />
<PackageVersion Include="System.IO.Abstractions" Version="22.0.13" />
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="10.0.7" />
<PackageVersion Include="System.Management" Version="10.0.8" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.2" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="10.0.7" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.7" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.7" />
<PackageVersion Include="System.Text.Json" Version="10.0.7" />
<PackageVersion Include="System.Runtime.Caching" Version="10.0.8" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.8" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.8" />
<PackageVersion Include="System.Text.Json" Version="10.0.8" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="ToolGood.Words.Pinyin" Version="3.1.0.3" />
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
@@ -134,8 +135,8 @@
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
<PackageVersion Include="WinUIEx" Version="2.8.0" />
<PackageVersion Include="WmiLight" Version="6.14.0" />
<PackageVersion Include="WPF-UI" Version="3.0.5" />
<PackageVersion Include="WyHash" Version="1.0.5" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="WixToolset.Heat" Version="5.0.2" />
<PackageVersion Include="WixToolset.Firewall.wixext" Version="5.0.2" />
<PackageVersion Include="WixToolset.Util.wixext" Version="5.0.2" />

View File

@@ -1600,5 +1600,5 @@ SOFTWARE.
- UTF.Unknown
- WinUIEx
- WmiLight
- WPF-UI
- WyHash
- YamlDotNet

View File

@@ -57,6 +57,7 @@
<Project Path="src/common/UnitTests-CommonLib/UnitTests-CommonLib.vcxproj" Id="1a066c63-64b3-45f8-92fe-664e1cce8077" />
<Project Path="src/common/UnitTests-CommonUtils/UnitTests-CommonUtils.vcxproj" Id="8b5cfb38-ccba-40a8-ad7a-89c57b070884" />
<Project Path="src/common/updating/updating.vcxproj" Id="17da04df-e393-4397-9cf0-84dabe11032e" />
<Project Path="src/common/updating/UnitTests/UpdatingUnitTests.vcxproj" Id="a1b2c3d4-e5f6-7890-abcd-ef1234567890" />
<Project Path="src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
</Folder>
<Folder Name="/common/interop/">
@@ -67,10 +68,7 @@
<Project Path="src/common/interop/PowerToys.Interop.vcxproj" Id="f055103b-f80b-4d0c-bf48-057c55620033" />
</Folder>
<Folder Name="/common/log/">
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd">
<BuildDependency Project="src/logging/logging.vcxproj" />
</Project>
<Project Path="src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f" />
<Project Path="src/common/logger/logger.vcxproj" Id="d9b8fc84-322a-4f9f-bbb9-20915c47ddfd" />
</Folder>
<Folder Name="/common/notifications/">
<Project Path="src/common/notifications/BackgroundActivator/BackgroundActivator.vcxproj" Id="0b593a6c-4143-4337-860e-db5710fb87db" />
@@ -658,7 +656,10 @@
</Project>
</Folder>
<Folder Name="/modules/launcher/Tests/">
<File Path="src/modules/launcher/Plugins/Microsoft.Plugin.Folder.UnitTests/Microsoft.Plugin.Folder.UnitTests.csproj" />
<Project Path="src/modules/launcher/Plugins/Microsoft.Plugin.Folder.UnitTests/Microsoft.Plugin.Folder.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest/Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -801,6 +802,14 @@
<Project Path="src/modules/peek/peek/peek.vcxproj" Id="a1425b53-3d61-4679-8623-e64a0d3d0a48" />
</Folder>
<Folder Name="/modules/PowerAccent/">
<Project Path="src/modules/poweraccent/PowerAccent.Common.UnitTests/PowerAccent.Common.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/poweraccent/PowerAccent.Common/PowerAccent.Common.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/poweraccent/PowerAccent.Core/PowerAccent.Core.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
@@ -989,9 +998,20 @@
<Platform Solution="*|x64" Project="x64" />
</Project>
</Folder>
<Folder Name="/modules/shortcutguide/">
<Project Path="src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj" Id="2edb3eb4-fa92-4bff-b2d8-566584837231" />
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="2d604c07-51fc-46bb-9eb7-75aecc7f5e81" />
<Folder Name="/modules/ShortcutGuide/">
<Project Path="src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/ShortcutGuide/ShortcutGuide.UnitTests/ShortcutGuide.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj" Id="e487304a-b1fb-4e6b-8e70-014051af5b99" />
</Folder>
<Folder Name="/modules/Workspaces/">
<Project Path="src/modules/Workspaces/Workspaces.ModuleServices/Workspaces.ModuleServices.csproj">
@@ -1126,3 +1146,5 @@
<Project Path="src/Update/PowerToys.Update.vcxproj" Id="44ce9ae1-4390-42c5-bacc-0fd6b40aa203" />
<Project Path="tools/project_template/ModuleTemplate/ModuleTemplateCompileTest.vcxproj" Id="64a80062-4d8b-4229-8a38-dfa1d7497749" />
</Solution>

1
deps/expected-lite vendored

Submodule deps/expected-lite deleted from 95b9cb015f

7
deps/expected.props vendored
View File

@@ -1,7 +0,0 @@
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)expected-lite\include\nonstd\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
</Project>

1
deps/spdlog vendored

Submodule deps/spdlog deleted from 616866fcf4

View File

@@ -1,94 +0,0 @@
// spdlog-msvc-fix.h
//
// Workaround for MSVC 14.51 (compiler version 19.51, _MSC_VER >= 1951) removing
// stdext::checked_array_iterator. Force-included for all spdlog consumers via
// deps/spdlog.props, because spdlog v1.8.5's bundled fmt format.h(357) still
// references this type inside #if defined(_SECURE_SCL) && _SECURE_SCL -- a
// branch entered in Debug builds where _ITERATOR_DEBUG_LEVEL > 0.
//
// On MSVC 14.50 and earlier, the type still exists in <iterator>, so this shim
// is a no-op via the _MSC_VER guard. On MSVC 14.51+, it provides a minimal
// pointer-backed substitute that satisfies the bundled fmt's usage:
//
// template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
// template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
// return {p, size};
// }
// ... return make_checked(get_data(c) + size, n);
//
// When deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and drops this
// dependency), this shim and its <ForcedIncludeFiles> entry in deps/spdlog.props
// can be deleted.
#pragma once
#if defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1951
#include <cstddef>
#include <iterator>
#include <type_traits>
namespace stdext
{
template <typename _Ptr>
class checked_array_iterator
{
_Ptr _Myarray = nullptr;
std::size_t _Mysize = 0;
std::size_t _Myindex = 0;
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = std::remove_cv_t<std::remove_pointer_t<_Ptr>>;
using difference_type = std::ptrdiff_t;
using pointer = _Ptr;
using reference = std::remove_pointer_t<_Ptr>&;
constexpr checked_array_iterator() = default;
constexpr checked_array_iterator(_Ptr arr, std::size_t size, std::size_t idx = 0) noexcept
: _Myarray(arr), _Mysize(size), _Myindex(idx)
{
}
constexpr reference operator*() const noexcept { return _Myarray[_Myindex]; }
constexpr pointer operator->() const noexcept { return _Myarray + _Myindex; }
constexpr reference operator[](difference_type n) const noexcept
{
return _Myarray[_Myindex + static_cast<std::size_t>(n)];
}
constexpr checked_array_iterator& operator++() noexcept { ++_Myindex; return *this; }
constexpr checked_array_iterator operator++(int) noexcept { auto t = *this; ++_Myindex; return t; }
constexpr checked_array_iterator& operator--() noexcept { --_Myindex; return *this; }
constexpr checked_array_iterator operator--(int) noexcept { auto t = *this; --_Myindex; return t; }
constexpr checked_array_iterator& operator+=(difference_type n) noexcept
{
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) + n);
return *this;
}
constexpr checked_array_iterator& operator-=(difference_type n) noexcept
{
_Myindex = static_cast<std::size_t>(static_cast<difference_type>(_Myindex) - n);
return *this;
}
friend constexpr checked_array_iterator operator+(checked_array_iterator it, difference_type n) noexcept { it += n; return it; }
friend constexpr checked_array_iterator operator+(difference_type n, checked_array_iterator it) noexcept { return it + n; }
friend constexpr checked_array_iterator operator-(checked_array_iterator it, difference_type n) noexcept { it -= n; return it; }
friend constexpr difference_type operator-(checked_array_iterator a, checked_array_iterator b) noexcept
{
return static_cast<difference_type>(a._Myindex) - static_cast<difference_type>(b._Myindex);
}
friend constexpr bool operator==(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex == b._Myindex; }
friend constexpr bool operator!=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a == b); }
friend constexpr bool operator<(checked_array_iterator a, checked_array_iterator b) noexcept { return a._Myindex < b._Myindex; }
friend constexpr bool operator>(checked_array_iterator a, checked_array_iterator b) noexcept { return b < a; }
friend constexpr bool operator<=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(b < a); }
friend constexpr bool operator>=(checked_array_iterator a, checked_array_iterator b) noexcept { return !(a < b); }
};
} // namespace stdext
#endif // __cplusplus && _MSC_VER >= 1951

19
deps/spdlog.props vendored
View File

@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ForcedIncludeFiles>$(MSBuildThisFileDirectory)spdlog-msvc-fix\include\spdlog-msvc-fix.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
</ClCompile>
</ItemDefinitionGroup>
<!--
SPDLOG_* preprocessor defines for spdlog consumers. The actual vcpkg
integration (VcpkgEnabled, VcpkgRoot, triplet, manifest install) lives
in Cpp.Build.props; this file just carries the defines that match how
the pre-vcpkg in-tree build was configured.
-->
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
</Project>

View File

@@ -0,0 +1,16 @@
--- a/include/spdlog/fmt/bundled/format.h
+++ b/include/spdlog/fmt/bundled/format.h
@@ -354,7 +354,12 @@ inline typename Container::value_type* get_data(Container& c) {
return c.data();
}
-#if defined(_SECURE_SCL) && _SECURE_SCL
+// PowerToys: stdext::checked_array_iterator was deprecated in VS 2019 16.10
+// and removed entirely in MSVC 14.51 (compiler 19.51, _MSC_VER >= 1951;
+// see microsoft/STL STL4043). Skip the broken branch on those toolsets so the
+// pointer-based fallback below is used instead. Drop this guard once
+// deps/spdlog is bumped past v1.14 (which ships fmt 10.2 and removes this code).
+#if defined(_SECURE_SCL) && _SECURE_SCL && (!defined(_MSC_VER) || _MSC_VER < 1951)
// Make a checked iterator to avoid MSVC warnings.
template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {

View File

@@ -0,0 +1,43 @@
# PowerToys overlay port for spdlog.
#
# Pinned to the same git commit that the deleted deps/spdlog submodule pointed
# at, so this is a 1:1 submodule->vcpkg migration with no version change
# (per the maintainer guidance: convert one submodule at a time, atomic
# commit, don't also bump the version).
#
# A single hunk patch works around MSVC 14.51 STL4043 (removal of
# stdext::checked_array_iterator) in spdlog's bundled fmt 7. Drop this overlay
# (and switch to upstream vcpkg's spdlog port) once PowerToys bumps spdlog
# past v1.14, which ships fmt 10.2 and removes the affected code path.
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO gabime/spdlog
REF 616866fcf40340ea25a8f218369bad810ef58e72
SHA512 2076c527c7768627e6856b2f7ef663b185fd6251894cffd9299203d00f3d2de5696461060442dd72b96c9d3f0fd27f7f63ad2edfdf295e9b06c5fac6d6212faf
HEAD_REF v1.x
PATCHES
msvc-14.51-stdext-checked-array-iterator.patch
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
-DSPDLOG_BUILD_EXAMPLE=OFF
-DSPDLOG_BUILD_TESTS=OFF
-DSPDLOG_BUILD_BENCH=OFF
-DSPDLOG_FMT_EXTERNAL=OFF
-DSPDLOG_WCHAR_SUPPORT=ON
-DSPDLOG_WCHAR_FILENAMES=ON
-DSPDLOG_NO_EXCEPTIONS=OFF
-DSPDLOG_BUILD_SHARED=OFF
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(PACKAGE_NAME spdlog CONFIG_PATH lib/cmake/spdlog)
vcpkg_fixup_pkgconfig()
vcpkg_copy_pdbs()
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

18
deps/vcpkg-overlays/spdlog/vcpkg.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "spdlog",
"version-string": "1.8.5-pt-616866fc",
"port-version": 0,
"description": "Very fast, header-only/compiled, C++ logging library. PowerToys overlay pinned to gabime/spdlog@616866fc (the exact submodule commit before this migration), with a single-hunk patch that works around MSVC 14.51 removing stdext::checked_array_iterator (STL4043).",
"homepage": "https://github.com/gabime/spdlog",
"license": "MIT",
"dependencies": [
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
]
}

2
doc/devdocs-website/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
# docmd build output — published to GitHub Pages by CI, not committed.
site/

View File

@@ -0,0 +1,3 @@
# No lockfile: docmd is pinned in package.json and installed fresh on every build
# (locally and in CI), so a tracked package-lock.json is unnecessary here.
package-lock=false

View File

@@ -0,0 +1,44 @@
# Dev Docs Website
This folder hosts the [docmd](https://docmd.io/) project that turns the PowerToys developer
documentation in [`doc/devdocs`](../devdocs) into a static website.
## Generated site
The `site/` folder is the docmd build output. It is **not committed** to the repository &mdash; it is
git-ignored and rebuilt on demand. You only need it locally when previewing your changes (see below).
Publishing is handled by the
[Publish Dev Docs Website](../../.github/workflows/regenerate-devdocs-website.yml) GitHub Action, which
runs whenever files under `doc/devdocs` (or this folder) change on the `main` branch &mdash; it can also
be triggered manually from the **Actions** tab. The action builds the site and deploys it straight to
GitHub Pages as an artifact, so nothing is written back to the repository.
> [!NOTE]
> The action requires GitHub Pages to be enabled with **Source: GitHub Actions** under the repository
> **Settings → Pages**.
## Editing the docs
To change the documentation, edit the Markdown files under [`doc/devdocs`](../devdocs). The remaining
files in this folder are maintained by hand and are safe to edit:
- `docmd.config.json` &mdash; docmd configuration (title, source, output, plugins)
- `package.json` &mdash; pins the docmd version used to build the site
- `docmd-plugins/` &mdash; local build-time docmd plugins
> [!TIP]
> Link to repository files with repo-root-relative paths such as `/src/modules/.../Foo.cpp`.
> VS Code resolves these against the workspace root (so they open the local file), and the
> bundled `github-source-links` plugin rewrites them to
> `https://github.com/microsoft/PowerToys/blob/main/...` on the published site.
## Building locally
Requires [Node.js](https://nodejs.org/).
```powershell
npm install # install dependencies (first time only)
npm run dev # start a local preview server at http://localhost:3000
npm run build # generate the static site into ./site
```

View File

@@ -0,0 +1,52 @@
// docmd plugin: github-source-links
//
// The dev docs link to source files with repo-root-relative paths such as
// "/src/modules/.../Foo.cpp". VS Code resolves those against the workspace root,
// so they stay clickable while editing locally. On the published static site,
// however, a "/src/..." link resolves against the site origin and 404s.
//
// This plugin rewrites those links to absolute GitHub blob URLs so they work on
// the published site, while the Markdown sources stay untouched (keeping local
// VS Code navigation intact).
//
// It hooks markdownSetup at the Markdown token level, so it only rewrites links
// written in the docs' content. docmd's own generated links (sidebar, breadcrumbs,
// canonical tags) are never seen here, which matters because an internal doc route
// like "/tools/build-tools" is otherwise indistinguishable from a repo path like
// "/tools/BugReportTool" once rendered to HTML.
//
// docmd appends a trailing slash to the rewritten links (".../Foo.cpp/"); GitHub
// resolves that to the file anyway, so it is left as-is for simplicity.
const REPO_BLOB_BASE = 'https://github.com/microsoft/PowerToys/blob/main';
export default {
plugin: {
name: 'github-source-links',
version: '1.0.0',
capabilities: ['markdown'],
},
markdownSetup(md) {
const defaultRender =
md.renderer.rules.link_open ||
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const token = tokens[idx];
const hrefIndex = token.attrIndex('href');
if (hrefIndex >= 0) {
const href = token.attrs[hrefIndex][1];
// Only repo-root-relative links ("/src/..."). Leave protocol-relative
// ("//host"), absolute ("https://..."), relative and anchor links alone.
if (href.length > 1 && href[0] === '/' && href[1] !== '/') {
token.attrs[hrefIndex][1] = REPO_BLOB_BASE + href;
}
}
return defaultRender(tokens, idx, options, env, self);
};
},
};

View File

@@ -0,0 +1,8 @@
{
"name": "docmd-plugin-github-source-links",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "index.js",
"description": "docmd plugin that rewrites repo-root-relative doc links to GitHub blob URLs at build time."
}

View File

@@ -0,0 +1,9 @@
{
"title": "PowerToys Dev Docs",
"src": "../devdocs",
"out": "site",
"base": "/",
"plugins": {
"docmd-plugin-github-source-links": {}
}
}

View File

@@ -0,0 +1,16 @@
{
"name": "powertoys-devdocs-website",
"version": "1.0.0",
"private": true,
"description": "Static Dev Docs website generated from doc/devdocs with docmd.",
"scripts": {
"dev": "docmd dev",
"build": "docmd build"
},
"dependencies": {
"docmd-plugin-github-source-links": "file:./docmd-plugins/github-source-links"
},
"devDependencies": {
"@docmd/core": "0.8.6"
}
}

View File

@@ -97,6 +97,10 @@ The Shell Process Debugging Tool is a Visual Studio extension that helps debug m
- Check Event Viewer for application crashes related to `PowerToys.Settings.exe`
- Crash dumps can be obtained from Event Viewer
### Debugging Command Palette
Command Palette can be easily debugged using the solution filter in `src/modules/cmdpal/Command Palette.slnf`. This will open Command Palette as its own Visual Studio solution that can be run and debugged directly in Visual Studio without the need for the Shell Process Debugging Tool.
## Troubleshooting Build Errors
### Missing Image Files or Corrupted Build State

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -42,4 +42,4 @@ In the PR that adds a new plugin, reference a new issue to track the work for fu
- [ ] Add the resource folder to https://github.com/microsoft/PowerToys/blob/21247c0bb09a1bee3d14d6efa53d0c247f7236af/installer/PowerToysSetup/Product.wxs#L825
- [ ] Add the resource files under the section https://github.com/microsoft/PowerToys/blob/21247c0bb09a1bee3d14d6efa53d0c247f7236af/installer/PowerToysSetup/Product.wxs#L882
- [ ] Your plugin's executable file (DLL) has to have correct version informations after building it. (This version information will be shown on the settings page.)
- [ ] Your plugin's executable file (DLL) has to have correct version information after building it. (This version information will be shown on the settings page.)

View File

@@ -9,12 +9,14 @@
[Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Shortcut+Guide%22+)
## Overview
Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when the Windows key is pressed and held. It provides a visual reference for Windows key combinations, helping users discover and utilize built-in Windows shortcuts.
Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when a user-set keyboard shortcut is pressed. It helps users discover and remember keyboard shortcuts for Windows and apps.
> [!NOTE]
> The spec for the manifest files is in development and will be linked here once available.
## Usage
- Press and hold the Windows key to display the overlay of available shortcuts
- Press the hotkey again to dismiss the overlay
- The overlay displays Windows shortcuts with their corresponding actions
- Press the user-defined hotkey to display the overlay
- Press the hotkey again or press ESC to dismiss the overlay
## Build and Debug Instructions
@@ -25,67 +27,89 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor
4. The executable is named PowerToys.ShortcutGuide.exe
### Debug
1. Right-click the ShortcutGuide project and select 'Set as Startup Project'
1. Right-click the ShortcutGuide.Ui project and select 'Set as Startup Project'
2. Right-click the project again and select 'Debug'
## Code Structure
> [!NOTE]
> When run in debug mode, the window behaves differently than in release mode. It will not automatically close when loosing focus, it will be displayed on top of all other windows, and it is not hidden from the taskbar.
![Diagram](../images/shortcutguide/diagram.png)
## Project Structure
### Core Files
The Shortcut Guide module consists of the following 4 projects:
#### [`dllmain.cpp`](/src/modules/shortcut_guide/dllmain.cpp)
Contains DLL boilerplate code. Implements the PowertoyModuleIface, including enable/disable functionality and GPO policy handling. Captures hotkey events and starts the PowerToys.ShortcutGuide.exe process to display the shortcut guide window.
### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj
#### [`shortcut_guide.cpp`](/src/modules/shortcut_guide/shortcut_guide.cpp)
Contains the module interface code. It initializes the settings values and the keyboard event listener. Defines the OverlayWindow class, which manages the overall logic and event handling for the PowerToys Shortcut Guide.
This is the main UI project for the Shortcut Guide module. Upon startup it does the following tasks:
#### [`overlay_window.cpp`](/src/modules/shortcut_guide/overlay_window.cpp)
Contains the code for loading the SVGs, creating and rendering of the overlay window. Manages and displays overlay windows with SVG graphics through two main classes:
- D2DOverlaySVG: Handles loading, resizing, and manipulation of SVG graphics
- D2DOverlayWindow: Manages the display and behavior of the overlay window
1. Copies the built-in manifest files to the users manifest directory (overwriting existing files).
2. Generate the `index.yml` manifest file.
3. Populate the PowerToys shortcut manifest with the user-defined shortcuts.
4. Starts the UI.
#### [`keyboard_state.cpp`](/src/modules/shortcut_guide/keyboard_state.cpp)
Contains helper methods for checking the current state of the keyboard.
### Related files in PowerToys.Interop
#### [`target_state.cpp`](/src/modules/shortcut_guide/target_state.cpp)
State machine that handles the keyboard events. It's responsible for deciding when to show the overlay, when to suppress the Start menu (if the overlay is displayed long enough), etc. Handles state transitions and synchronization to ensure the overlay is shown or hidden appropriately based on user interactions.
#### [`excluded_app.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp)
#### [`trace.cpp`](/src/modules/shortcut_guide/trace.cpp)
Contains code for telemetry.
This file contains one function with the following signature:
### Supporting Files
```cpp
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide()
```
#### [`animation.cpp`](/src/modules/shortcut_guide/animation.cpp)
Handles the timing and interpolation of animations. Calculates the current value of an animation based on elapsed time and a specified easing function.
This function checks if the current window is excluded from the Shortcut Guide overlay. It returns `true` if the current window is excluded otherwise it returns `false`.
#### [`d2d_svg.cpp`](/src/modules/shortcut_guide/d2d_svg.cpp)
Provides functionality for loading, resizing, recoloring, rendering, and manipulating SVG images using Direct2D.
#### [`tasklist_positions.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp)
#### [`d2d_text.cpp`](/src/modules/shortcut_guide/d2d_text.cpp)
Handles creation, resizing, alignment, and rendering of text using Direct2D and DirectWrite.
This file contains helper functions to retrieve the positions of the taskbar buttons. It exports the following function:
#### [`d2d_window.cpp`](/src/modules/shortcut_guide/d2d_window.cpp)
Manages a window using Direct2D and Direct3D for rendering. Handles window creation, resizing, rendering, and destruction.
```cpp
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size)
```
#### [`native_event_waiter.cpp`](/src/modules/shortcut_guide/native_event_waiter.cpp)
Waits for a named event and executes a specified action when the event is triggered. Uses a separate thread to handle event waiting and action execution.
This function retrieves the positions of the taskbar buttons for a given monitor. It returns an array of `TasklistButton` structures (max 10), which contain the position and size of each button.
#### [`tasklist_positions.cpp`](/src/modules/shortcut_guide/tasklist_positions.cpp)
Handles retrieving and updating the positions and information of taskbar buttons in Windows.
`monitor` must be the monitor handle of the monitor containing the taskbar instance of which the buttons should be retrieved.
#### [`main.cpp`](/src/modules/shortcut_guide/main.cpp)
The entry point for the PowerToys Shortcut Guide application. Handles initialization, ensures single instance execution, manages parent process termination, creates and displays the overlay window, and runs the main event loop.
`size` will contain the resulting array size.
It determines the positions through Windows `FindWindowEx` function.
For the primary taskbar it searches for:
* A window called "Shell_TrayWnd"
* that contains a window called "ReBarWindow32"
* that contains a window called "MSTaskSwWClass"
* that contains a window called "MSTaskListWClass"
For any secondary taskbar it searches for:
* A window called "Shell_SecondaryTrayWnd"
* that contains a window called "WorkerW"
* that contains a window called "MSTaskListWClass"
It then enumerates all the button elements inside "MSTaskListWClass" while skipping such with a same name (which implies the user does not use combining taskbar buttons)
If this method fails, which it will for newer versions of Windows, it falls back to searching for:
* A window called "Shell_TrayWnd" or "Shell_SecondaryTrayWnd"
* that contains a window called "Windows.UI.Composition.DesktopWindowContentBridge"
* that contains a window called "Windows.UI.Input.InputSite.WindowClass"
* the first child element
It then enumerates all the button elements inside the selected while skipping such with a same name (which implies the user does not use combining taskbar buttons) and such that do not start with "Appid:" (which are not actual taskbar buttons related to apps, but others like the widgets or the search button).
### [`ShortcutGuide.IndexYmlGenerator`](/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/)
This application generates the `index.yml` manifest file.
It is a separate project so that its code can be easier ported to WinGet in the future.
### [`ShortcutGuideModuleInterface`](/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj)
The module interface that handles opening and closing the user interface.
## Features and Limitations
- The overlay displays Windows shortcuts (Windows key combinations)
- The module supports localization, but only for the Windows controls on the left side of the overlay
- Currently the displayed shortcuts (Except the ones from PowerToys) are not localized.
- It's currently rated as a P3 (lower priority) module
## Future Development
A community-contributed version 2 is in development that will support:
- Application-specific shortcuts based on the active application
- Additional shortcuts beyond Windows key combinations
- PowerToys shortcuts
- Implementing with WinGet to get new shortcut manifest files
- Adding localization support for the built-in manifest files

View File

@@ -11,7 +11,7 @@ Welcome to the PowerToys developer documentation. This documentation provides in
- Desktop Development with C++
- WinUI application development
- .NET desktop development
- Windows 10 SDK (10.0.22621.0)
- Windows 11 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -1,19 +0,0 @@
# Backlog
This file captures the prioritized list of issues the FancyZones team will tackle
## On deck
## Backlog
Add tests to the new editor [197](https://github.com/microsoft/PowerToys/issues/197)
Cycle through windows in a Zone [175](https://github.com/microsoft/PowerToys/issues/175)
Minimize/restore windows in a zone as a group [174](https://github.com/microsoft/PowerToys/issues/174)
FancyZones should support custom layouts for different "environments" [177](https://github.com/microsoft/PowerToys/issues/177)
Win+arrow is directional based on zone rect [162](https://github.com/microsoft/PowerToys/issues/162)
Dragging a zoned window should restore size to a checkpointed size instead of current rect [166](https://github.com/microsoft/PowerToys/issues/166)
FancyZones should merge with MTND and include zone moves in the pop-up [178](https://github.com/microsoft/PowerToys/issues/178)
Drag to edge of screen automatically switches virtual desktops [168](https://github.com/microsoft/PowerToys/issues/168)
Visual updates for Win+Arrow [171](https://github.com/microsoft/PowerToys/issues/171)
Add "magnetic dragging and resizing" mode to FancyZones [181](https://github.com/microsoft/PowerToys/issues/181)
Create layout from current windows [159](https://github.com/microsoft/PowerToys/issues/159)
Zone sets that have a dynamic number of zones [160](https://github.com/microsoft/PowerToys/issues/160)

View File

@@ -1,24 +0,0 @@
# PowerToys Backlog
The list below is the set of utilities we're considering and the rough priority order of the utilities. If you have feedback on the order of the utilities, please use the issues for each one to provide that feedback. Note that new features for existing utilities (dock / undock zone layouts for FancyZones) are tracked in the backlog for each utility.
## On deck
* Maximize to new desktop widget - The MTND widget shows a pop-up button when a user hovers over the maximize / restore button on any window. Clicking it creates a new desktop, sends the app to that desktop and maximizes the app on the new desktop.
* [Process terminate tool](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/Terminate%20Spec.md)
* [Animated gif screen recorder](https://github.com/indierawk2k2/PowerToys-1/blob/master/specs/GIF%20Maker%20Spec.md)
## Backlog
Please use issues and votes to guide the project to suggest new ideas and help us prioritize the list below.
1. [Keyboard shortcut manager](https://github.com/microsoft/PowerToys/issues/6)
2. [Win+R replacement](https://github.com/microsoft/PowerToys/issues/44)
3. Resource use tool (maps between a resource like a file handle to an app and vice-versa)
4. Performance analysis over time to track which processes have been slowing down your machine
5. Better Alt+Tab including browser tab integration and search for running apps
6. [Battery tracker](https://github.com/microsoft/PowerToys/issues/7)
7. [Quick resolution swaps in taskbar](https://github.com/microsoft/PowerToys/issues/27)
8. Mouse events without focus
9. Cmd (or PS or Bash) from here
10. Contents menu file browsing

View File

@@ -1,13 +0,0 @@
# Backlog
This file captures the prioritized list of issues for the Windows key shortcut guide
## On deck
Windows key shortcut guide animation performance is choppy [198](https://github.com/microsoft/PowerToys/issues/198)
Shortcut guide strings should be localized [199](https://github.com/microsoft/PowerToys/issues/199)
## Backlog
Add Win+Shift+S to the WKSG (screenshot tool) [179](https://github.com/microsoft/PowerToys/issues/179)
Replace SVG with software-generated content. [156](https://github.com/microsoft/PowerToys/issues/156)
Shortcut sorting [154](https://github.com/microsoft/PowerToys/issues/154)
Make shortcut descriptors clickable [152](https://github.com/microsoft/PowerToys/issues/152)

View File

@@ -1,114 +0,0 @@
---
last-update: 1-18-2026
---
# PowerToys Awake Changelog
## Builds
The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
| Build ID | Build Date |
|:-------------------------------------------------------------------|:------------------|
| [`DIDACT_01182026`](#DIDACT_01182026-january-18-2026) | January 18, 2026 |
| [`TILLSON_11272024`](#TILLSON_11272024-november-27-2024) | November 27, 2024 |
| [`PROMETHEAN_09082024`](#PROMETHEAN_09082024-september-8-2024) | September 8, 2024 |
| [`VISEGRADRELAY_08152024`](#VISEGRADRELAY_08152024-august-15-2024) | August 15, 2024 |
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
| `ARBITER_01312022` | January 31, 2022 |
### `DIDACT_01182026` (January 18, 2026)
>[!NOTE]
>See pull request: [Awake - `DIDACT_01182026`](https://github.com/microsoft/PowerToys/pull/44795)
- [#32544](https://github.com/microsoft/PowerToys/issues/32544) Fixed an issue where Awake settings became non-functional after the PC wakes from sleep. Added `WM_POWERBROADCAST` handling to detect system resume events (`PBT_APMRESUMEAUTOMATIC`, `PBT_APMRESUMESUSPEND`) and re-apply `SetThreadExecutionState` to restore the awake state.
- [#36150](https://github.com/microsoft/PowerToys/issues/36150) Fixed an issue where Awake would not prevent sleep when AC power is connected. Added `PBT_APMPOWERSTATUSCHANGE` handling to re-apply `SetThreadExecutionState` when the power source changes (AC/battery transitions).
- Fixed an issue where toggling "Keep screen on" during an active timed session would disrupt the countdown timer. The display setting now updates directly without restarting the timer, preserving the exact remaining time.
- [#41918](https://github.com/microsoft/PowerToys/issues/41918) Fixed `WM_COMMAND` message processing flaw in `TrayHelper.WndProc` that incorrectly compared enum values against enum count. Added proper bounds checking for custom tray time entries.
- Investigated [#44134](https://github.com/microsoft/PowerToys/issues/44134) - documented that `ES_DISPLAY_REQUIRED` (used when "Keep display on" is enabled) blocks Task Scheduler idle detection, preventing scheduled maintenance tasks like SSD TRIM. Workaround: disable "Keep display on" or manually run `Optimize-Volume -DriveLetter C -ReTrim`. Additional investigation needed for potential "idle window" feature.
- [#41738](https://github.com/microsoft/PowerToys/issues/41738) Fixed `--display-on` CLI flag default from `true` to `false` to align with documentation and PowerToys settings behavior. This is a breaking change for scripts relying on the undocumented default.
- [#41674](https://github.com/microsoft/PowerToys/issues/41674) Fixed silent failure when `SetThreadExecutionState` fails. The monitor thread now handles the return value, logs an error, and reverts to passive mode with updated tray icon.
- [#38770](https://github.com/microsoft/PowerToys/issues/38770) Fixed tray icon failing to appear after Windows updates. Increased retry attempts and delays for icon Add operations (10 attempts, up to ~15.5 seconds total) while keeping existing fast retry behavior for Update/Delete operations.
- [#40501](https://github.com/microsoft/PowerToys/issues/40501) Fixed tray icon not disappearing when Awake is disabled. The `SetShellIcon` function was incorrectly requiring an icon for Delete operations, causing the `NIM_DELETE` message to never be sent.
- [#40659](https://github.com/microsoft/PowerToys/issues/40659) Fixed potential stack overflow crash in EXPIRABLE mode. Added early return after SaveSettings when correcting past expiration times, matching the pattern used by other mode handlers to prevent reentrant execution.
### `TILLSON_11272024` (November 27, 2024)
>[!NOTE]
>See pull request: [Awake - `TILLSON_11272024`](https://github.com/microsoft/PowerToys/pull/36049)
- [#35250](https://github.com/microsoft/PowerToys/issues/35250) Updates the icon retry policy, making sure that the icon consistently and correctly renders in the tray.
- [#35848](https://github.com/microsoft/PowerToys/issues/35848) Fixed a bug where custom tray time shortcuts for longer than 24 hours would be parsed as zero hours/zero minutes.
- [#34716](https://github.com/microsoft/PowerToys/issues/34716) Properly recover the state icon in the tray after an `explorer.exe` crash.
- Added configuration safeguards to make sure that invalid values for timed keep-awake times do not result in exceptions.
- Updated the tray initialization logic, making sure we wait for it to be properly created before setting icons.
- Expanded logging capabilities to track invoking functions.
- Added command validation logic to make sure that incorrect command line arguments display an error.
- Display state now shown in the tray tooltip.
- When timed mode is used, changing the display setting will no longer reset the timer.
### `PROMETHEAN_09082024` (September 8, 2024)
>[!NOTE]
>See pull request: [Awake - `PROMETHEAN_09082024`](https://github.com/microsoft/PowerToys/pull/34717)
- Updating the initialization logic to make sure that settings are respected for proper group policy and single-instance detection.
- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixed a bug from the previous release that incorrectly synchronized threads for shell icon creation and initialized parent PID when it was not parented.
### `VISEGRADRELAY_08152024` (August 15, 2024)
>[!NOTE]
>See pull request: [Awake - `VISEGRADRELAY_08152024`](https://github.com/microsoft/PowerToys/pull/34316)
- [#34148](https://github.com/microsoft/PowerToys/issues/34148) Fixes the issue where the Awake icon is not displayed.
- [#17969](https://github.com/microsoft/PowerToys/issues/17969) Add the ability to bind the process target to the parent of the Awake launcher.
- PID binding now correctly ignores irrelevant parameters (e.g., expiration, interval) and only works for indefinite periods.
- Amending the native API surface to make sure that the Win32 error is set correctly.
### `DAISY023_04102024` (April 10, 2024)
>[!NOTE]
>See pull request: [Awake Update - `DAISY023_04102024`](https://github.com/microsoft/PowerToys/pull/32378)
- [#33630](https://github.com/microsoft/PowerToys/issues/33630) When in the UI and you select `0` as hours and `0` as minutes in `TIMED` awake mode, the UI becomes non-responsive whenever you try to get back to timed after it rolls back to `PASSIVE`.
- [#12714](https://github.com/microsoft/PowerToys/issues/12714) Adds the option to keep track of Awake state through tray tooltip.
- [#11996](https://github.com/microsoft/PowerToys/issues/11996) Adds custom icons support for mode changes in Awake.
- Removes the dependency on `System.Windows.Forms` and instead uses native Windows APIs to create the tray icon.
- Removes redundant/unused code that impacted application performance.
- Updates dependent packages to their latest versions (`Microsoft.Windows.CsWinRT` and `System.Reactive`).
### `ATRIOX_04132023` (April 13, 2023)
- Moves from using `Task.Run` to spin up threads to actually using a blocking queue that properly sets thread parameters on the same thread.
- Moves back to using native Windows APIs through P/Invoke instead of using a package.
- Move away from custom logging and to built-in logging that is consistent with the rest of PowerToys.
- Updates `System.CommandLine` and `System.Reactive` to the latest preview versions of the package.
### `LIBRARIAN_03202022` (March 20, 2022)
- Changed the tray context menu to be following OS conventions instead of the style offered by Windows Forms. This introduces better support for DPI scaling and theming in the future.
- Custom times in the tray can now be configured in the `settings.json` file for awake, through the `tray_times` property. The property values are representative of a `Dictionary<string, int>` and can be in the form of `"YOUR_NAME": LENGTH_IN_SECONDS`:
```json
{
"properties": {
"awake_keep_display_on": true,
"awake_mode": 2,
"awake_hours": 0,
"awake_minutes": 3,
"tray_times": {
"Custom length": 1800,
"Another custom length": 3600
}
},
"name": "Awake",
"version": "1.0"
}
```
- Proper Awake background window closure was implemented to ensure that the process collects the correct handle instead of the empty one that was previously done through `System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow()`. This likely can help with the Awake process that is left hanging after PowerToys itself closes.

View File

@@ -0,0 +1,326 @@
# WinGet Manifest Keyboard Shortcuts schema
## 1 What this spec is about
This spec provides an extension to the existing [WinGet manifest schema](https://github.com/microsoft/winget-pkgs/blob/master/doc/manifest/README.md) in form of an additional yaml file, that describes keyboard shortcuts the application provides.
These yaml files are saved on a per-user base and so called manifest interpreters can then display these manifests in a human-friendly version.
### 1.1 What this spec is not about
This spec does not provide a way to back up or save user-defined keyboard shortcuts.
## 2 Save location of manifests
### 2.1 WinGet
These files are saved online along with the other manifest files in the [WinGet Package repository](https://github.com/microsoft/winget-pkgs).
### 2.2 Locally
All manifests and one index file are saved locally under `%LocalAppData%/Microsoft/WinGet/KeyboardShortcuts`. All apps are allowed to add their manifest files there. In addition Package Managers (like WinGet) and manifest interpreters (like PowerToys Shortcut Guide) can control and add other manifests themselves.
#### 2.2.1 Downloading manifests
When WinGet or other package managers download a package, they should also download the corresponding keyboard shortcuts manifest file and save it in the local directory, given such a file exists in the WinGet repository.
The downloader is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory.
#### 2.2.2 Updating manifests
When a manifest interpreter starts, it should download the latest version of the manifests from the WinGet repository and save them in the local directory. If a manifest interpreter is not able to download the manifests or they do not exist, it should use the locally saved manifests.
The updater is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory.
> Note: WinGet must provide a way to update the keyboard shortcuts manifests given a package id.
### 2.3 File names
The file name of a keyboard shortcuts file is the WinGet package identifier, plus the locale of the strings of the file and at last the `.KBSC.yaml` file extension.
For example the package "test.bar" saves its manifest with `en-US` strings in `test.bar.en-US.KBSC.yaml`.
#### 2.3.1 No winget package available
If an application has no corresponding WinGet package its name starts with a plus (`+`) symbol.
### 2.4 Reserved namespaces
Every name starting with `+WindowsNT` is reserved for the Windows OS and its components.
## 3 File syntax
All relevant files are written in [YAML](https://yaml.org/spec).
> Note: A JSON schema will be provided as soon as the spec reaches a further step
### 3.1 Manifest Schema vNext Keyboard Shortcuts File
```
PackageName: # The package unique identifier
WindowFilter: # The filter of window processes to which the shortcuts apply to
BackgroundProcess: # Optionally allows applying WindowFilter to background processes
Shortcuts: # List of sections with keyboard shortcuts
- SectionName: # Name of the category of shortcuts
Properties: # List of shortcuts in the category
- Name: # Name of the shortcut
Description: # Optional description of the shortcut
AdditionalInfo: # Optional additional information about the shortcut
Recommended: # Optionally determines if the shortcut is displayed in a designated recommended area
Shortcut: # An array of shortcuts that need to be pressed
- Win: # Determines if the Windows Key is part of the shortcut
Ctrl: # Determines if the Ctrl Key is part of the shortcut
Shift: # Determines if the Shift Key is part of the shortcut
Alt: # Determines if the Alt Key is part of the shortcut
Keys: # Array of keys that need to be pressed
```
Per Application/Package one or more Keyboard manifests can be declared. Every manifest must have a different locale and the same `PackageName`, `WindowFilter` and `BackgroundProcess` fields.
<details>
<summary><b>PackageName</b> - The package unique identifier</summary>
Package identifier (see 2.1 for more information on the package identifier).
</details>
<details>
<summary><b>WindowFilter</b> - The filter of window processes to which the shortcuts apply to</summary>
This field declares for which process name the shortcuts should be shown (To rephrase: For which processes the shortcut will have an effect if pressed). The value can be either an exact process executable name, for example `explorer.exe` or `chrome.exe`, or a single asterisk (`*`) to apply to any process. No other wildcard patterns are supported by this specification.
</details>
<details>
<summary><b>BackgroundProcess</b> - Optionally allows applying WindowFilter to background processes.</summary>
**Optional field**
Defaults to `False`. Determines if WindowFilter should apply to background processes as well (Rephrased: When the process is running, the shortcuts will apply).
</details>
<details>
<summary><b>Shortcuts</b> - List of sections with keyboard shortcuts</summary>
List of different section (also called categories) of shortcuts.
</details>
<details>
<summary><b>SectionName</b> - Name of the category of shortcuts</summary>
Name of the section of shortcuts.
**Special sections**:
Special sections start with an identifier enclosed between `<` and `>`. This declares the category as a special display. If the interpreter of the manifest file can't understand the content this section should be left out.
</details>
<details>
<summary><b>Properties</b> - List of shortcuts in the category</summary>
</details>
<details>
<summary><b>Name</b> - Name of the shortcut</summary>
Name of the shortcut. This is the name that will be displayed in the interpreter.
</details>
<details>
<summary><b>Description</b> - Optional description of the shortcut</summary>
Optional description of the shortcut. This is the description that will be displayed by the interpreter.
</details>
<details>
<summary><b>AdditionalInfo</b> - Optional additional information about the shortcut</summary>
Array of additional information about the shortcut. This is the additional information that will be displayed by the interpreter and are not part of this manifest.
**Example**:
For example, if the shortcut is only available on a certain Windows version, this information could be added here.
```yaml
AdditionalInfo:
- MinWindowsVersion: "10.0.19041.0"
```
</details>
<details>
<summary><b>Shortcut</b> - An array of shortcuts that need to be pressed</summary>
An array of shortcuts that need to be pressed. This allows defining sequential shortcuts that need to be pressed in order to trigger the action.
</details>
<details>
<summary><b>Win</b> - Determines if the Windows Key is part of the shortcut</summary>
Refers to the left Windows Key on the keyboard.
</details>
<details>
<summary><b>Ctrl</b> - Determines if the Ctrl Key is part of the shortcut</summary>
Refers to the left Ctrl Key on the keyboard.
</details>
<details>
<summary><b>Shift</b> - Determines if the Shift Key is part of the shortcut</summary>
Refers to the left Shift Key on the keyboard.
</details>
<details>
<summary><b>Alt</b> - Determines if the Alt Key is part of the shortcut</summary>
Refers to the left Alt Key on the keyboard.
</details>
<details>
<summary><b>Recommended</b> - Optionally determines if the shortcut is displayed in a designated recommended area</summary>
**Optional field**
Defaults to `False`. Determines if the shortcut should be displayed in a designated recommended area. This is a visual hint for the user that this shortcut is important.
</details>
<details>
<summary><b>Keys</b> - Array of keys that need to be pressed</summary>
A string array of all the keys that need to be pressed. If a number is supplied, it should be read as a [KeyCode](https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes) and displayed accordingly (based on the Keyboard Layout of the user).
**Literal digit keys**:
Because a bare number is interpreted as a virtual-key code, a literal digit key must be authored using the `<N>` notation (the digit enclosed between `<` and `>`), where `N` is `0``9`. For example, `<9>` represents the literal `9` key (as in the "switch to the last tab" shortcut), not the virtual-key code `9` (which is `Tab`). The interpreter strips the brackets and displays just the digit.
This applies only to a single literal digit. A range such as `1 - 8` is a free-form label, not a key, and is supplied verbatim (the brackets would only be trimmed from the ends, so `<1> - <8>` would not render as intended).
**Special keys**:
Special keys are enclosed between `<` and `>` and correspond to a key that should be displayed in a certain way. If the interpreter of the manifest file can't understand the content, the brackets should be left out.
By convention these tokens are written as double-quoted strings in the YAML (for example `"<Enter>"` and `"<9>"`), matching the quoting used for punctuation key values. YAML treats the quoted and unquoted forms identically, so quoting is for consistency rather than a strict requirement for bracketed tokens.
|Name|Description|
|----|-----------|
|`<Office>`| Corresponds to the Office key on some Windows keyboards |
|`<Copilot>`| Corresponds to the Copilot key on some Windows keyboards |
|`<Left>`| Corresponds to the left arrow key |
|`<Right>`| Corresponds to the right arrow key |
|`<Up>`| Corresponds to the up arrow key |
|`<Down>`| Corresponds to the down arrow key |
|`<Enter>`| Corresponds to the Enter key |
|`<Space>`| Corresponds to the Space key |
|`<Tab>`| Corresponds to the Tab key |
|`<Backspace>`| Corresponds to the Backspace key |
|`<Delete>`| Corresponds to the Delete key |
|`<Insert>`| Corresponds to the Insert key |
|`<Home>`| Corresponds to the Home key |
|`<End>`| Corresponds to the End key |
|`<PrtScr>`| Corresponds to the Print Screen key |
|`<Pause>`| Corresponds to the pause key |
|`<PageUp>`| Corresponds to the Page Up key |
|`<PageDown>`| Corresponds to the Page Down key |
|`<Escape>`| Corresponds to the Escape key |
|`<Arrow>`| Corresponds to either the left, right, up or down arrow key |
|`<ArrowLR>`| Corresponds to either the left or right arrow key |
|`<ArrowUD>`| Corresponds to either the up or down arrow key |
|`<Underlined letter>`| Corresponds to any letter that is _underlined_ in the UI |
</details>
#### 3.2.2 Example
```yaml
PackageName: Microsoft.PowerToys
WindowFilter: "*"
BackgroundProcess: True
Shortcuts:
- SectionName: General
Properties:
- Name: Advanced Paste
Shortcut:
- Win: True
Ctrl: False
Alt: False
Shift: False
Keys:
- 86
Description: Open Advanced Paste window
- Name: Advanced Paste
Shortcut:
- Win: True
Ctrl: True
Alt: True
Shift: False
Keys:
- 86
Description: Paste as plain text directly
```
### 3.2 `index.yaml` file
The `index.yaml` file is a file that contains all the information about the different manifest files that are saved in the same directory. This file is only available locally and is not saved in the WinGet repository as it is specific to the user.
```yaml
DefaultShellName: # The package identifier of the default shell used in Windows
Index: # List of all manifest files
- WindowFilter: # The filter of window processes to which the shortcuts apply to
BackgroundProcess: # Optionally allows applying WindowFilter to background processes
Apps: # List of all manifest files for the filter
```
<details>
<summary><b>DefaultShellName</b> - The package identifier of the default shell used in Windows</summary>
This declares the package identifier of the default shell used in Windows. Most commonly it is `+WindowsNT.Shell`. Although not enforced, only the shell declared in the registry key `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell` should be used here.
</details>
<details>
<summary><b>Index</b> - List of all manifest files</summary>
</details>
<details>
<summary><b>WindowFilter</b> - The filter of window processes to which the shortcuts apply to</summary>
See the `WindowFilter` field in the manifest file for more information.
</details>
<details>
<summary><b>BackgroundProcess</b> - Optionally allows applying WindowFilter to background processes</summary>
**Optional field**
See the `BackgroundProcess` field in the manifest file for more information.
</details>
<details>
<summary><b>Apps</b> - List of all the package identifiers applying for the filter</summary>
</details>
#### 3.2.1 Example
```yaml
DefaultShellName: "+WindowsNT.Shell"
Index:
- Filter: "*"
BackgroundProcess: True
Apps: ["+WindowsNT.Shell", "Microsoft.PowerToys"]
- Filter: "explorer.exe"
Apps: ["+WindowsNT.WindowsExplorer"]
- Filter: "taskmgr.exe"
Apps: ["+WindowsNT.TaskManager"]
- Filter: "msedge.exe"
Apps: ["+WindowsNT.Edge"]
```

View File

@@ -79,3 +79,4 @@ Below are community created plugins that target a website or software. They are
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
| [DiskAnalyzer](https://github.com/valley-soft/powertoys-diskanalyzer) | [ValleySoft](https://github.com/valley-soft) | Scan folders, find the largest files, and view drive space usage. |

View File

@@ -1,37 +0,0 @@
# Unofficial community driven install methods
These are community driven alternative install methods to Windows Package Manager (WinGet) and GitHub. The PowerToys teams does not update or manage these install methods.
These will be listed in alphabetical order.
## Chocolatey
Download and upgrade PowerToys from [Chocolatey](https://chocolatey.org). If you have any issues when installing/upgrading the package please go to the [package page](https://chocolatey.org/packages/powertoys) and follow the [Chocolatey triage process](https://chocolatey.org/docs/package-triage-process)
To install PowerToys, run the following command from the command line / PowerShell:
```powershell
choco install powertoys
```
To upgrade PowerToys, run the following command from the command line / PowerShell:
```powershell
choco upgrade powertoys
```
## Scoop
Download and update PowerToys from [Scoop](https://scoop.sh).
To install PowerToys, run the following command from the command line / PowerShell:
```powershell
scoop install powertoys
```
To update PowerToys, run the following command from the command line / PowerShell:
```powershell
scoop update powertoys
```

View File

@@ -8,9 +8,6 @@
</Project>
<Project Path="../src/common/Telemetry/EtwTrace/EtwTrace.vcxproj" Id="8f021b46-362b-485c-bfba-ccf83e820cbd" />
<Project Path="../src/common/version/version.vcxproj" Id="cc6e41ac-8174-4e8a-8d22-85dd7f4851df" />
<Project Path="../src/logging/logging.vcxproj" Id="7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f">
<Build Solution="Debug|ARM64" Project="false" />
</Project>
<Project Path="PowerToysSetupCustomActionsVNext/PowerToysSetupCustomActionsVNext.vcxproj" Id="b3a354b0-1e54-4b55-a962-fb5af9330c19">
<Build Solution="Debug|ARM64" Project="false" />
</Project>

View File

@@ -2,26 +2,39 @@
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define ShortcutGuideSvgFiles=?>
<?define ShortcutGuideSvgFilesPath=$(var.BinDir)\Assets\ShortcutGuide\?>
<?define ShortcutGuideAssetsFiles=?>
<?define ShortcutGuideAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\?>
<?define ShortcutGuideManifestsFiles=?>
<?define ShortcutGuideManifestsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ShortcutGuide\Manifests\?>
<Fragment>
<!-- Shortcut guide files -->
<DirectoryRef Id="BaseApplicationsAssetsFolder">
<Directory Id="ShortcutGuideSvgsInstallFolder" Name="ShortcutGuide" />
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="ShortcutGuideAssetsFolder" Name="ShortcutGuide">
<Directory Id="ShortcutGuideManifestsFolder" Name="Manifests" />
</Directory>
</DirectoryRef>
<DirectoryRef Id="ShortcutGuideSvgsInstallFolder" FileSource="$(var.ShortcutGuideSvgFilesPath)">
<DirectoryRef Id="ShortcutGuideAssetsFolder" FileSource="$(var.ShortcutGuideAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShortcutGuideSvgFiles_Component_Def-->
<!--ShortcutGuideAssetsFiles_Component_Def-->
</DirectoryRef>
<DirectoryRef Id="ShortcutGuideManifestsFolder" FileSource="$(var.ShortcutGuideManifestsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--ShortcutGuideManifestsFiles_Component_Def-->
</DirectoryRef>
<!-- Shortcut guide -->
<ComponentGroup Id="ShortcutGuideComponentGroup">
<Component Id="RemoveShortcutGuideFolder" Guid="AD1ABC55-B593-4A60-A86A-BA8C0ED493A5" Directory="ShortcutGuideSvgsInstallFolder">
<ComponentGroup Id="ShortcutGuideComponentGroup" >
<Component Id="RemoveShortcutGuideFolder" Guid="AD1ABC55-B593-4A60-A86A-BA8C0ED493A5" Directory="ShortcutGuideAssetsFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShortcutGuideFolder" Value="" KeyPath="yes" />
<RegistryValue Type="string" Name="RemoveShortcutGuideFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderShortcutGuideSvgsInstallFolder" Directory="ShortcutGuideSvgsInstallFolder" On="uninstall" />
<RemoveFolder Id="RemoveFolderShortcutGuideAssetsInstallFolder" Directory="ShortcutGuideAssetsFolder" On="uninstall"/>
</Component>
<Component Id="RemoveShortcutGuideManifestsFolder" Guid="F47E2C3A-8D91-4B6F-A2E5-9C8D7F6A1B3E" Directory="ShortcutGuideManifestsFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveShortcutGuideManifestsFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderShortcutGuideManifestsInstallFolder" Directory="ShortcutGuideManifestsFolder" On="uninstall"/>
</Component>
</ComponentGroup>

View File

@@ -28,7 +28,7 @@ Function Generate-FileList() {
$fileExclusionList = @("*.pdb", "*.lastcodeanalysissucceeded", "createdump.exe", "powertoys.exe")
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri")
$fileInclusionList = @("*.dll", "*.exe", "*.json", "*.msix", "*.png", "*.gif", "*.ico", "*.cur", "*.svg", "index.html", "reg.js", "gitignore.js", "srt.js", "monacoSpecialLanguages.js", "customTokenThemeRules.js", "*.pri", "*.yml")
# MFC DLLs leak into the output via WindowsAppSDKSelfContained but no PowerToys binary imports them.
# Verified with dumpbin /dependents across all 2176 binaries — zero consumers.
@@ -112,6 +112,8 @@ Function Generate-FileComponents() {
foreach ($file in $fileList) {
$fileTmp = $file -replace "-", "_"
$fileTmp = $fileTmp -replace "[^A-Za-z0-9_.]", "_"
if ($fileTmp -match "^[^A-Za-z_]") { $fileTmp = "_$fileTmp" }
$componentDefs +=
@"
<File Id="$($fileListName)_File_$($fileTmp)" Source="`$(var.$($fileListName)Path)\$($file)" />`r`n
@@ -397,8 +399,24 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat
## Plugins
#ShortcutGuide
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\"
Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
# Ensure manifest yml files are in the build output (the Build target's CopyToOutputDirectory
# may not run reliably under -graph mode in solution builds).
$sgManifestsSrc = "$PSScriptRoot..\..\..\src\modules\ShortcutGuide\ShortcutGuide.Ui\Assets\ShortcutGuide\Manifests"
$sgManifestsDst = "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests"
Write-Host "ShortcutGuide manifests: src=$sgManifestsSrc exists=$(Test-Path $sgManifestsSrc)"
Write-Host "ShortcutGuide manifests: dst=$sgManifestsDst exists=$(Test-Path $sgManifestsDst)"
if (Test-Path $sgManifestsSrc) {
New-Item -Path $sgManifestsDst -ItemType Directory -Force | Out-Null
Copy-Item "$sgManifestsSrc\*.yml" -Destination $sgManifestsDst -Force
$copied = (Get-ChildItem "$sgManifestsDst\*.yml" -ErrorAction SilentlyContinue).Count
Write-Host "ShortcutGuide manifests: copied $copied yml files to build output"
} else {
Write-Host "WARNING: ShortcutGuide manifest source not found at $sgManifestsSrc"
}
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideAssetsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\"
Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideManifestsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\Manifests\"
Generate-FileComponents -fileListName "ShortcutGuideManifestsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs
#Settings
Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\"

View File

@@ -14,7 +14,6 @@
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>

View File

@@ -14,6 +14,8 @@
#include <common/updating/updating.h>
#include <common/updating/updateState.h>
#include <common/updating/installer.h>
#include <common/updating/configBackup.h>
#include <common/updating/updateLifecycle.h>
#include <common/utils/elevation.h>
#include <common/utils/HttpClient.h>
@@ -21,6 +23,8 @@
#include <common/utils/resources.h>
#include <common/utils/timeutil.h>
#include <wil/resource.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/logger/logger.h>
@@ -36,19 +40,64 @@ using namespace cmdArg;
namespace fs = std::filesystem;
void CleanupStaleTempUpdaters()
{
// Remove orphaned PowerToys.Update.*.exe files from previous runs
try
{
std::error_code ec;
const auto tempDir = fs::temp_directory_path();
for (const auto& entry : fs::directory_iterator(tempDir, ec))
{
if (ec)
{
break;
}
if (!entry.is_regular_file())
{
continue;
}
const auto filename = entry.path().filename().wstring();
if (filename.starts_with(L"PowerToys.Update.") && filename.ends_with(L".exe"))
{
// Skip our own file (current PID)
const auto ownFilename = L"PowerToys.Update." + std::to_wstring(GetCurrentProcessId()) + L".exe";
if (filename == ownFilename)
{
continue;
}
fs::remove(entry.path(), ec);
// Failure to delete is expected if another updater is still running
}
}
}
catch (...)
{
// Best-effort cleanup; don't block the update
}
}
std::optional<fs::path> CopySelfToTempDir()
{
CleanupStaleTempUpdaters();
std::error_code error;
auto dst_path = fs::temp_directory_path() / "PowerToys.Update.exe";
auto dst_path = fs::temp_directory_path() / (L"PowerToys.Update." + std::to_wstring(GetCurrentProcessId()) + L".exe");
fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error);
if (error)
{
return std::nullopt;
}
return std::move(dst_path);
return dst_path;
}
// The installer filename read from UpdateState.json is validated by
// updating::IsSafeDownloadedInstallerFilename (common/updating/updateLifecycle.h)
// so it can be unit-tested. See ObtainInstaller below for how it's used.
std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
{
using namespace updating;
@@ -57,36 +106,29 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
auto state = UpdateState::read();
const auto new_version_info = std::move(get_github_version_info_async()).get();
if (std::holds_alternative<version_up_to_date>(*new_version_info))
// Handle readyToInstall first — the installer is already on disk,
// so we don't need a GitHub API call (which may fail if offline).
if (state.state == UpdateState::readyToInstall)
{
isUpToDate = true;
Logger::error("Invoked with -update_now argument, but no update was available");
return std::nullopt;
}
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
{
if (!new_version_info)
if (!IsSafeDownloadedInstallerFilename(state.downloadedInstallerFilename))
{
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
Logger::error(L"Ignoring unexpected downloadedInstallerFilename from update state: {}", state.downloadedInstallerFilename);
return std::nullopt;
}
// Cleanup old updates before downloading the latest
updating::cleanup_updates();
const fs::path updatesDir = get_pending_updates_path();
fs::path installer{ updatesDir / state.downloadedInstallerFilename };
auto downloaded_installer = std::move(download_new_version_async(std::get<new_version_download_info>(*new_version_info))).get();
if (!downloaded_installer)
// Make sure the resolved path actually stays within the Updates directory.
std::error_code ec;
const fs::path normalizedInstaller = fs::weakly_canonical(installer, ec);
const fs::path normalizedUpdatesDir = fs::weakly_canonical(updatesDir, ec);
if (ec || normalizedInstaller.parent_path() != normalizedUpdatesDir)
{
Logger::error("Couldn't download new installer");
Logger::error(L"Resolved installer path is outside the updates directory: {}", installer.native());
return std::nullopt;
}
return downloaded_installer;
}
else if (state.state == UpdateState::readyToInstall)
{
fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename };
if (fs::is_regular_file(installer))
{
return std::move(installer);
@@ -97,12 +139,44 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
return std::nullopt;
}
}
else if (state.state == UpdateState::upToDate)
if (state.state == UpdateState::upToDate)
{
isUpToDate = true;
return std::nullopt;
}
const auto new_version_info = std::move(get_github_version_info_async()).get();
// Check for error BEFORE dereferencing — the old code crashed here
// when GitHub API was unreachable (new_version_info held an error string).
if (!new_version_info)
{
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
return std::nullopt;
}
if (std::holds_alternative<version_up_to_date>(*new_version_info))
{
isUpToDate = true;
Logger::error("Invoked with -update_now argument, but no update was available");
return std::nullopt;
}
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
{
// 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();
if (!downloaded_installer)
{
Logger::error("Couldn't download new installer");
}
return downloaded_installer;
}
Logger::error("Invoked with -update_now argument, but update state was invalid");
return std::nullopt;
}
@@ -116,13 +190,32 @@ bool InstallNewVersionStage1(fs::path installer)
if (pt_main_window != nullptr)
{
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
// Get the process that owns the tray window so we can wait for it to exit
DWORD ptProcessId = 0;
GetWindowThreadProcessId(pt_main_window, &ptProcessId);
// Use SendMessageTimeoutW to avoid blocking indefinitely if the
// tray window thread is hung or unresponsive.
DWORD_PTR result = 0;
SendMessageTimeoutW(pt_main_window, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 5000, &result);
// Wait for PT to actually exit before launching installer.
// Without this, the installer may find PT files locked.
if (ptProcessId != 0)
{
wil::unique_handle ptProcess{ OpenProcess(SYNCHRONIZE, FALSE, ptProcessId) };
if (ptProcess)
{
WaitForSingleObject(ptProcess.get(), 10000); // 10 second timeout
}
}
}
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 };
arguments += L" \"";
arguments += installer.c_str();
arguments += L"\"";
// Pass the install directory so Stage 2 can relaunch PowerToys after install
const std::wstring installDir = get_module_folderpath();
std::wstring arguments = updating::BuildStage2Arguments(
UPDATE_NOW_LAUNCH_STAGE2, installer, fs::path(installDir));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
sei.lpFile = copy_in_temp->c_str();
@@ -190,9 +283,16 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &nArgs);
if (!args || nArgs < 2)
{
if (args)
{
LocalFree(args);
}
return 1;
}
// D3 fix: ensure args is freed on all exit paths
auto freeArgs = wil::scope_exit([&] { LocalFree(args); });
std::wstring_view action{ args[1] };
std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
@@ -201,6 +301,11 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
if (action == UPDATE_NOW_LAUNCH_STAGE1)
{
// Backup config files before the update to protect against corruption
Logger::info("Backing up config files before update");
auto backupResult = updating::BackupConfigFiles(fs::path(PTSettingsHelper::get_root_save_folder_location()));
Logger::info("Config backup complete: {} files backed up, {} errors", backupResult.filesBackedUp, backupResult.errors);
bool isUpToDate = false;
auto installerPath = ObtainInstaller(isUpToDate);
bool failed = !installerPath.has_value();
@@ -217,6 +322,12 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
}
else if (action == UPDATE_NOW_LAUNCH_STAGE2)
{
if (nArgs < 3)
{
Logger::error("Stage 2 invoked without installer path argument");
return 1;
}
using namespace std::string_view_literals;
const bool failed = !InstallNewVersionStage2(args[2]);
if (failed)
@@ -227,6 +338,39 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
state.state = UpdateState::errorDownloading;
});
}
// Always check for corrupted configs after Stage 2, regardless
// of install success/failure. A failed install may still corrupt configs.
Logger::info("Checking for corrupted config files after update");
auto restoreResult = updating::RestoreCorruptedConfigs(fs::path(PTSettingsHelper::get_root_save_folder_location()));
Logger::info("Config restore check complete: {}/{} files restored, {} errors",
restoreResult.filesRestored, restoreResult.filesChecked, restoreResult.errors);
if (!failed)
{
// Relaunch PowerToys from the install directory
if (updating::CanRelaunchAfterUpdate(nArgs))
{
std::wstring ptExePath = updating::BuildPowerToysExePath(args[3]);
Logger::info(L"Relaunching PowerToys after update: {}", ptExePath);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
sei.lpFile = ptExePath.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = UPDATE_REPORT_SUCCESS;
if (!ShellExecuteExW(&sei))
{
Logger::error(L"Failed to relaunch PowerToys after update");
}
}
else
{
Logger::warn("Install directory not provided to Stage 2 - cannot relaunch PowerToys");
}
}
return failed;
}

View File

@@ -14,7 +14,6 @@
<PropertyGroup Label="Configuration">
</PropertyGroup>
<Import Project="$(RepoRoot)deps\expected.props" />
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
</PropertyGroup>

View File

@@ -161,7 +161,7 @@
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdcpp17</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT

View File

@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using Microsoft.UI.Composition;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
namespace Microsoft.PowerToys.Common.UI.Controls.Backdrops;
/// <summary>
/// A <see cref="SystemBackdrop"/> that renders desktop acrylic and stays in
/// the active visual state even when the hosting window is not activated.
/// </summary>
/// <remarks>
/// The built-in <see cref="DesktopAcrylicBackdrop"/> tracks the host window's
/// <c>IsInputActive</c> state and falls back to a solid color whenever the
/// window is not the foreground window. That makes it unusable for transient,
/// non-activating surfaces such as toasts or popups created with
/// <c>SW_SHOWNA</c> / <c>WS_EX_TRANSPARENT</c>, where the window is never
/// activated by design.
///
/// This backdrop drives a <see cref="DesktopAcrylicController"/> with a
/// <see cref="SystemBackdropConfiguration"/> whose <c>IsInputActive</c> is
/// permanently <see langword="true"/>, so the native acrylic effect is always
/// rendered.
/// </remarks>
public sealed partial class AlwaysActiveDesktopAcrylicBackdrop : SystemBackdrop
{
private readonly Dictionary<ICompositionSupportsSystemBackdrop, BackdropTarget> _targets = new();
protected override void OnTargetConnected(ICompositionSupportsSystemBackdrop connectedTarget, XamlRoot xamlRoot)
{
base.OnTargetConnected(connectedTarget, xamlRoot);
var configuration = new SystemBackdropConfiguration
{
IsInputActive = true,
Theme = ResolveTheme(xamlRoot),
};
var controller = new DesktopAcrylicController();
controller.SetSystemBackdropConfiguration(configuration);
controller.AddSystemBackdropTarget(connectedTarget);
var target = new BackdropTarget(controller, configuration, xamlRoot);
_targets[connectedTarget] = target;
if (xamlRoot.Content is FrameworkElement rootElement)
{
rootElement.ActualThemeChanged += target.OnActualThemeChanged;
}
}
protected override void OnTargetDisconnected(ICompositionSupportsSystemBackdrop disconnectedTarget)
{
base.OnTargetDisconnected(disconnectedTarget);
if (_targets.Remove(disconnectedTarget, out var target))
{
if (target.XamlRoot.Content is FrameworkElement rootElement)
{
rootElement.ActualThemeChanged -= target.OnActualThemeChanged;
}
target.Controller.RemoveSystemBackdropTarget(disconnectedTarget);
target.Controller.Dispose();
}
}
private static SystemBackdropTheme ResolveTheme(XamlRoot xamlRoot) =>
xamlRoot.Content is FrameworkElement rootElement
? rootElement.ActualTheme switch
{
ElementTheme.Dark => SystemBackdropTheme.Dark,
ElementTheme.Light => SystemBackdropTheme.Light,
_ => SystemBackdropTheme.Default,
}
: SystemBackdropTheme.Default;
private sealed class BackdropTarget
{
public BackdropTarget(DesktopAcrylicController controller, SystemBackdropConfiguration configuration, XamlRoot xamlRoot)
{
Controller = controller;
Configuration = configuration;
XamlRoot = xamlRoot;
}
public DesktopAcrylicController Controller { get; }
public SystemBackdropConfiguration Configuration { get; }
public XamlRoot XamlRoot { get; }
public void OnActualThemeChanged(FrameworkElement sender, object args)
{
Configuration.Theme = ResolveTheme(XamlRoot);
}
}
}

View File

@@ -18,6 +18,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />

View File

@@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Common.UI.Controls;
/// <summary>
/// A floating "card" surface for transient PowerToys overlays (toasts,
/// banners, indicators). Provides the PowerToys-standard chrome — 1 px
/// border in <c>SurfaceStrokeColorDefaultBrush</c>, 8 px corner radius,
/// a <c>ThemeShadow</c>, and an always-active desktop acrylic backdrop —
/// via a default <see cref="Microsoft.UI.Xaml.Controls.ControlTemplate"/>.
/// </summary>
/// <remarks>
/// Lives inside a <see cref="Window.TransparentWindow"/>. Apps that want a
/// different look supply their own <c>Style TargetType="TransparentCard"</c>
/// in resources — the standard WinUI restyle path.
/// </remarks>
public sealed partial class TransparentCard : ContentControl
{
public TransparentCard()
{
DefaultStyleKey = typeof(TransparentCard);
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:backdrops="using:Microsoft.PowerToys.Common.UI.Controls.Backdrops"
xmlns:local="using:Microsoft.PowerToys.Common.UI.Controls">
<Style BasedOn="{StaticResource DefaultTransparentCardStyle}" TargetType="local:TransparentCard" />
<Style x:Key="DefaultTransparentCardStyle" TargetType="local:TransparentCard">
<Setter Property="BorderBrush" Value="{ThemeResource SurfaceStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TransparentCard">
<Grid
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Translation="0,0,24">
<Grid.Shadow>
<ThemeShadow />
</Grid.Shadow>
<SystemBackdropElement CornerRadius="{TemplateBinding CornerRadius}">
<SystemBackdropElement.SystemBackdrop>
<backdrops:AlwaysActiveDesktopAcrylicBackdrop />
</SystemBackdropElement.SystemBackdrop>
</SystemBackdropElement>
<ContentPresenter
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -4,5 +4,6 @@
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/KeyVisual/KeyCharPresenter.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/IsEnabledTextBlock/IsEnabledTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/ShortcutWithTextLabelControl/ShortcutWithTextLabelControl.xaml" />
<ResourceDictionary Source="ms-appx:///PowerToys.Common.UI.Controls/Controls/TransparentCard/TransparentCard.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -0,0 +1,290 @@
// 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.Runtime.InteropServices;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Markup;
using WinUIEx;
namespace Microsoft.PowerToys.Common.UI.Controls.Window;
/// <summary>
/// Reusable transparent host window for transient overlays
/// (toasts, banners, indicators) that should not steal foreground.
/// </summary>
/// <remarks>
/// <para>The constructor applies all of the boilerplate that PowerToys overlays
/// currently hand-roll:</para>
/// <list type="bullet">
/// <item>Strip the native frame and caption (<c>WS_THICKFRAME</c> etc.).</item>
/// <item>Disable the Win11 1-pixel DWM border and corner rounding.</item>
/// <item>Mark the window as a tool window so it stays out of the taskbar and Alt-Tab.</item>
/// <item>Extend content into the title bar and collapse the title bar.</item>
/// </list>
/// <para>The visible chrome (acrylic + border + corner radius + shadow) lives
/// in a <see cref="TransparentCard"/> that the constructor assigns to
/// <see cref="Microsoft.UI.Xaml.Window.Content"/>. Consumers supply their own
/// UI via <see cref="InnerContent"/> — which is the XAML default-content slot
/// thanks to <see cref="ContentPropertyAttribute"/> — so a derived window can
/// be written as <c>&lt;common:TransparentWindow&gt;&lt;TextBlock/&gt;&lt;/common:TransparentWindow&gt;</c>.</para>
/// <para>Transparency is achieved with a <see cref="TransparentTintBackdrop"/>
/// system backdrop so the area outside the <see cref="TransparentCard"/> is
/// fully see-through. That buffer area is NOT click-through, so consumers
/// should keep it as small as possible (just enough to give the card's
/// shadow + slide animation room to breathe — roughly 24 px on each side).</para>
/// <para><see cref="Show"/> and <see cref="Hide"/> coordinate <c>SW_SHOWNA</c>
/// (no-activate), the <see cref="Microsoft.UI.Xaml.UIElement.Visibility"/>
/// toggle on the card, and a debounced
/// <see cref="Microsoft.UI.Windowing.AppWindow.Hide"/> sized from the longest
/// animation in <see cref="HideAnimations"/>. Animations target the card so
/// the entire surface (border, acrylic, shadow, inner content) slides as one.</para>
/// </remarks>
[ContentProperty(Name = nameof(InnerContent))]
public partial class TransparentWindow : WinUIEx.WindowEx
{
private const uint DwmwaColorNone = 0xFFFFFFFE;
private const int DwmwaWindowCornerPreference = 33;
private const int DwmwaBorderColor = 34;
private const int DwmwcpDoNotRound = 1;
private const int GwlExStyle = -20;
private const int WsExToolWindow = 0x00000080;
private const int SwShowNa = 8;
private readonly DispatcherQueueTimer _hideCloseTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
private readonly nint _hwnd;
private readonly TransparentCard _card;
private ImplicitAnimationSet _showAnimations;
private ImplicitAnimationSet _hideAnimations;
public TransparentWindow()
{
AppWindow.Hide();
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
_hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
HwndExtensions.ToggleWindowStyle(_hwnd, false, WindowStyle.TiledWindow);
unsafe
{
uint borderColor = DwmwaColorNone;
_ = DwmSetWindowAttribute(_hwnd, DwmwaBorderColor, &borderColor, sizeof(uint));
int cornerPref = DwmwcpDoNotRound;
_ = DwmSetWindowAttribute(_hwnd, DwmwaWindowCornerPreference, &cornerPref, sizeof(int));
}
ApplyExStyleBit(WsExToolWindow, true);
_showAnimations = BuildDefaultShowAnimations();
_hideAnimations = BuildDefaultHideAnimations();
_card = new TransparentCard();
Content = _card;
SystemBackdrop = new TransparentTintBackdrop();
}
/// <summary>
/// Gets the <see cref="TransparentCard"/> that provides the window's
/// visible chrome (acrylic + border + shadow). Consumers can configure
/// its layout (e.g. <c>HorizontalAlignment</c>, <c>VerticalAlignment</c>,
/// <c>MaxWidth</c>, <c>Margin</c>) to position the card inside the
/// window, or apply a custom <c>Style</c> to change its look.
/// </summary>
public TransparentCard Card => _card;
/// <summary>
/// Gets or sets the visual hosted inside the window's
/// <see cref="TransparentCard"/>. This is the XAML default-content slot:
/// child elements declared between the opening and closing
/// <c>TransparentWindow</c> tags in a derived .xaml are routed here.
/// </summary>
public object? InnerContent
{
get => _card.Content;
set => _card.Content = value;
}
/// <summary>
/// Gets or sets the animations played against
/// <see cref="Microsoft.UI.Xaml.Window.Content"/> when <see cref="Show"/>
/// flips it to <see cref="Visibility.Visible"/>. Defaults to a 200 ms
/// fade-in plus a 250 ms slide-up of 24 px.
/// </summary>
public ImplicitAnimationSet ShowAnimations
{
get => _showAnimations;
set => _showAnimations = value ?? new ImplicitAnimationSet();
}
/// <summary>
/// Gets or sets the animations played against
/// <see cref="Microsoft.UI.Xaml.Window.Content"/> when <see cref="Hide"/>
/// flips it to <see cref="Visibility.Collapsed"/>. Defaults to a 180 ms
/// fade-out plus a 180 ms slide-down of 12 px.
/// </summary>
public ImplicitAnimationSet HideAnimations
{
get => _hideAnimations;
set => _hideAnimations = value ?? new ImplicitAnimationSet();
}
/// <summary>
/// Shows the window without activation (<c>SW_SHOWNA</c>) and flips
/// <see cref="Microsoft.UI.Xaml.Window.Content"/> to
/// <see cref="Visibility.Visible"/> so <see cref="ShowAnimations"/> plays.
/// Repeated calls reset the content to its hidden pose first so the show
/// animation re-triggers cleanly. Any pending hide is cancelled.
/// </summary>
public void Show()
{
DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
() =>
{
_hideCloseTimer.Stop();
if (Content is UIElement content)
{
// Re-apply each call so swapping animation collections at
// runtime takes effect on the next show/hide cycle.
Implicit.SetShowAnimations(content, _showAnimations);
Implicit.SetHideAnimations(content, _hideAnimations);
// Reset to the hidden pose so the show animation always
// animates from the configured starting frame.
content.Visibility = Visibility.Collapsed;
}
_ = ShowWindow(_hwnd, SwShowNa);
if (Content is UIElement c2)
{
c2.Visibility = Visibility.Visible;
}
});
}
/// <summary>
/// Flips <see cref="Microsoft.UI.Xaml.Window.Content"/> to
/// <see cref="Visibility.Collapsed"/> so <see cref="HideAnimations"/>
/// plays, then hides the underlying
/// <see cref="Microsoft.UI.Windowing.AppWindow"/> once the longest
/// animation in <see cref="HideAnimations"/> (delay + duration) has
/// completed.
/// </summary>
public void Hide()
{
DispatcherQueue.TryEnqueue(
DispatcherQueuePriority.Low,
() =>
{
if (Content is UIElement content)
{
content.Visibility = Visibility.Collapsed;
}
_hideCloseTimer.Debounce(
AppWindow.Hide,
interval: GetAnimationSetTotalDuration(_hideAnimations),
immediate: false);
});
}
private static TimeSpan GetAnimationSetTotalDuration(ImplicitAnimationSet set)
{
TimeSpan longest = TimeSpan.Zero;
foreach (var animation in set)
{
if (animation is Animation anim)
{
var total = (anim.Delay ?? TimeSpan.Zero) + (anim.Duration ?? TimeSpan.Zero);
if (total > longest)
{
longest = total;
}
}
}
return longest;
}
private void ApplyExStyleBit(int bit, bool set)
{
if (_hwnd == 0)
{
return;
}
nint exStyle = GetWindowLongPtr(_hwnd, GwlExStyle);
nint updated = set ? exStyle | bit : exStyle & ~(nint)bit;
if (updated != exStyle)
{
_ = SetWindowLongPtr(_hwnd, GwlExStyle, updated);
}
}
private static ImplicitAnimationSet BuildDefaultShowAnimations() => new()
{
new OpacityAnimation
{
From = 0,
To = 1.0,
Duration = TimeSpan.FromMilliseconds(200),
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut,
EasingType = EasingType.Cubic,
},
new TranslationAnimation
{
From = "0,24,32",
To = "0,0,32",
Duration = TimeSpan.FromMilliseconds(250),
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut,
EasingType = EasingType.Cubic,
},
};
private static ImplicitAnimationSet BuildDefaultHideAnimations() => new()
{
new OpacityAnimation
{
From = 1.0,
To = 0,
Duration = TimeSpan.FromMilliseconds(180),
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseIn,
EasingType = EasingType.Cubic,
},
new TranslationAnimation
{
From = "0,0,32",
To = "0,12,32",
Duration = TimeSpan.FromMilliseconds(180),
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseIn,
EasingType = EasingType.Cubic,
},
};
[LibraryImport("user32.dll", EntryPoint = "GetWindowLongPtrW")]
private static partial nint GetWindowLongPtr(nint hWnd, int nIndex);
[LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
private static partial nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
[LibraryImport("dwmapi.dll")]
private static unsafe partial int DwmSetWindowAttribute(nint hwnd, int dwAttribute, void* pvAttribute, int cbAttribute);
}

View File

@@ -2,43 +2,35 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ManagedCommon
{
public static class IdRecoveryHelper
{
/// <summary>
/// Fixes invalid IDs in the given list by assigning unique values.
/// It ensures that all IDs are non-empty and unique, correcting any duplicates or empty IDs.
/// Ensures that all items in the provided list have unique IDs. Duplicate IDs are replaced
/// with the next available unique ID.
/// </summary>
/// <param name="items">The list of items that may contain invalid IDs.</param>
/// <param name="items">The list of items that may contain duplicate IDs.</param>
public static void RecoverInvalidIds<T>(IEnumerable<T> items)
where T : class, IHasId
{
var idSet = new HashSet<int>();
int newId = 0;
var sortedItems = items.OrderBy(i => i.Id).ToList(); // Sort items by ID for consistent processing
var seenIds = new HashSet<int>();
int nextAvailableId = 0;
// Iterate through the list and fix invalid IDs
foreach (var item in sortedItems)
foreach (var item in items)
{
// If the ID is invalid or already exists in the set (duplicate), assign a new unique ID
if (!idSet.Add(item.Id))
// If this ID is already used, assign a new unique ID.
if (!seenIds.Add(item.Id))
{
// Find the next available unique ID
while (idSet.Contains(newId))
// Find the next unused ID.
while (!seenIds.Add(nextAvailableId))
{
newId++;
nextAvailableId++;
}
item.Id = newId;
idSet.Add(newId); // Add the newly assigned ID to the set
item.Id = nextAvailableId;
}
}
}

View File

@@ -27,7 +27,7 @@
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\deps\spdlog\include;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\utils;..\Telemetry;..\..\;..\..\..\deps\;..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp23</LanguageStandard>
<PreprocessorDefinitions>SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_HEADER_ONLY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>

View File

@@ -259,6 +259,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT;
}
hstring Constants::AutoDisablePowerDisplayEvent()
{
return CommonSharedConstants::POWER_DISPLAY_AUTO_DISABLE_EVENT;
}
hstring Constants::RefreshPowerDisplayMonitorsEvent()
{
return CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT;

View File

@@ -68,6 +68,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring ShowCmdPalEvent();
static hstring TogglePowerDisplayEvent();
static hstring TerminatePowerDisplayEvent();
static hstring AutoDisablePowerDisplayEvent();
static hstring RefreshPowerDisplayMonitorsEvent();
static hstring SettingsUpdatedPowerDisplayEvent();
static hstring PowerDisplaySendSettingsTelemetryEvent();

View File

@@ -65,6 +65,7 @@ namespace PowerToys
static String ShowCmdPalEvent();
static String TogglePowerDisplayEvent();
static String TerminatePowerDisplayEvent();
static String AutoDisablePowerDisplayEvent();
static String RefreshPowerDisplayMonitorsEvent();
static String SettingsUpdatedPowerDisplayEvent();
static String PowerDisplaySendSettingsTelemetryEvent();

View File

@@ -41,7 +41,6 @@
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
@@ -100,6 +99,7 @@
<ClInclude Include="Constants.h">
<DependentUpon>KeyboardListener.idl</DependentUpon>
</ClInclude>
<ClInclude Include="excluded_app.h" />
<ClInclude Include="HotkeyManager.h">
<DependentUpon>HotkeyManager.idl</DependentUpon>
</ClInclude>
@@ -114,6 +114,7 @@
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="shared_constants.h" />
<ClInclude Include="tasklist_positions.h" />
<ClInclude Include="TwoWayPipeMessageIPCManaged.h">
<DependentUpon>TwoWayPipeMessageIPCManaged.idl</DependentUpon>
</ClInclude>
@@ -127,6 +128,7 @@
<ClCompile Include="Constants.cpp">
<DependentUpon>KeyboardListener.idl</DependentUpon>
</ClCompile>
<ClCompile Include="excluded_app.cpp" />
<ClCompile Include="HotkeyManager.cpp">
<DependentUpon>HotkeyManager.idl</DependentUpon>
</ClCompile>
@@ -140,6 +142,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="tasklist_positions.cpp" />
<ClCompile Include="TwoWayPipeMessageIPCManaged.cpp">
<DependentUpon>TwoWayPipeMessageIPCManaged.idl</DependentUpon>
</ClCompile>
@@ -165,6 +168,9 @@
<Midl Include="TwoWayPipeMessageIPCManaged.idl" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>

View File

@@ -54,6 +54,12 @@
<ClInclude Include="Constants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="excluded_app.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tasklist_positions.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="keyboard_layout.cpp">
@@ -83,6 +89,12 @@
<ClCompile Include="Constants.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="excluded_app.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tasklist_positions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="interop.rc">

View File

@@ -0,0 +1,41 @@
#include "pch.h"
#include "excluded_app.h"
#include <../utils/string_utils.h>
extern "C"
{
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide()
{
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Shortcut Guide");
auto settingsObject = settings.get_raw_json();
std::wstring apps = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"disabled_apps").GetNamedString(L"value").c_str();
auto excludedUppercase = apps;
CharUpperBuffW(excludedUppercase.data(), static_cast<DWORD>(excludedUppercase.length()));
std::wstring_view view(excludedUppercase);
view = left_trim<wchar_t>(trim<wchar_t>(view));
m_excludedApps.clear();
while (!view.empty())
{
auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
m_excludedApps.emplace_back(view.substr(0, pos));
view.remove_prefix(pos);
view = left_trim<wchar_t>(trim<wchar_t>(view));
}
if (m_excludedApps.empty())
{
return false;
}
if (HWND foregroundApp{ GetForegroundWindow() })
{
auto processPath = get_process_path(foregroundApp);
CharUpperBuffW(processPath.data(), static_cast<DWORD>(processPath.length()));
return check_excluded_app(foregroundApp, processPath, m_excludedApps);
}
return false;
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
extern "C"
{
std::vector<std::wstring> m_excludedApps;
__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide();
}

View File

@@ -58,7 +58,7 @@ namespace Microsoft.Interop.Tests
ClientPipe.Start();
// Test can be flaky as the pipes are still being set up and we end up receiving no message. Wait for a bit to avoid that.
Thread.Sleep(100);
Thread.Sleep(500);
ClientPipe.Send(testString);

View File

@@ -12,3 +12,29 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <Windows.h>
#include <Endpointvolume.h>
#include <vector>
#include <UIAutomation.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <Shobjidl.h>
#include <Shlwapi.h>
#include <string>
#include <algorithm>
#include <chrono>
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>
#include <stdexcept>
#include <tuple>
#include <unordered_set>
#include <filesystem>
#include <common/utils/excluded_apps.h>
#include <common/utils/process_path.h>
#include <../SettingsAPI/settings_objects.h>

View File

@@ -165,6 +165,7 @@ namespace CommonSharedConstants
const wchar_t SETTINGS_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
const wchar_t POWER_DISPLAY_SEND_SETTINGS_TELEMETRY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsTelemetryEvent-8c4f2a1d-5e3b-7f9c-1a6d-3b8e5f2c9a7d";
const wchar_t HOTKEY_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-HotkeyUpdatedEvent-9d5f3a2b-7e1c-4b8a-6f3d-2a9e5c7b1d4f";
const wchar_t POWER_DISPLAY_AUTO_DISABLE_EVENT[] = L"Local\\PowerToysPowerDisplay-AutoDisableEvent-1a7bd9af-d2e0-4e57-8879-0e1c353994d0";
const wchar_t RESCAN_POWER_DISPLAY_MONITORS_EVENT[] = L"Local\\PowerToysPowerDisplay-RescanMonitorsEvent-7f3e8c5a-1d4b-4a9e-bc6f-5d8a2b9e3c4f";
// IPC Messages used in PowerDisplay (Named Pipe communication)

View File

@@ -0,0 +1,246 @@
#include "pch.h"
#include "tasklist_positions.h"
// Tried my hardest adapting this to C#, but FindWindowW didn't work properly in C#. ~Noraa Junker
static winrt::com_ptr<IUIAutomation> automation;
static winrt::com_ptr<IUIAutomationElement> element;
static winrt::com_ptr<IUIAutomationCondition> true_condition;
extern "C"
{
HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor)
{
POINT pt;
if (!GetCursorPos(&pt))
return nullptr;
// Find the primary taskbar
HWND primaryTaskbar = FindWindowW(L"Shell_TrayWnd", nullptr);
if (primaryTaskbar)
{
MONITORINFO mi = { sizeof(mi) };
if (GetWindowRect(primaryTaskbar, &mi.rcMonitor))
{
HMONITOR primaryMonitor = MonitorFromRect(&mi.rcMonitor, MONITOR_DEFAULTTONEAREST);
if (primaryMonitor == monitor)
return primaryTaskbar;
}
}
// Find the secondary taskbar(s)
HWND secondaryTaskbar = nullptr;
while ((secondaryTaskbar = FindWindowExW(nullptr, secondaryTaskbar, L"Shell_SecondaryTrayWnd", nullptr)) != nullptr)
{
MONITORINFO mi = { sizeof(mi) };
RECT rc;
if (GetWindowRect(secondaryTaskbar, &rc))
{
HMONITOR taskbarMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
if (monitor == taskbarMonitor)
return secondaryTaskbar;
}
}
return nullptr;
}
void update(HMONITOR monitor)
{
// Get HWND of the tasklist for the monitor under the cursor
auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor);
if (!taskbar_hwnd)
return;
wchar_t class_name[64] = {};
GetClassNameW(taskbar_hwnd, class_name, 64);
HWND tasklist_hwnd = nullptr;
if (wcscmp(class_name, L"Shell_TrayWnd") == 0)
{
// Primary taskbar structure
tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"ReBarWindow32", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskSwWClass", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskListWClass", nullptr);
if (!tasklist_hwnd)
return;
}
else if (wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0)
{
// Secondary taskbar structure
HWND worker_hwnd = FindWindowExW(taskbar_hwnd, 0, L"WorkerW", nullptr);
if (!worker_hwnd)
return;
tasklist_hwnd = FindWindowExW(worker_hwnd, 0, L"MSTaskListWClass", nullptr);
if (!tasklist_hwnd)
return;
}
else
{
// Unknown taskbar type
return;
}
if (!automation)
{
winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IUIAutomation,
automation.put_void()));
winrt::check_hresult(automation->CreateTrueCondition(true_condition.put()));
}
element = nullptr;
winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put()));
}
void update_new(HMONITOR monitor)
{
// Get HWND of the tasklist for the monitor under the cursor
auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor);
if (!taskbar_hwnd)
return;
wchar_t class_name[64] = {};
GetClassNameW(taskbar_hwnd, class_name, 64);
HWND tasklist_hwnd = nullptr;
if (wcscmp(class_name, L"Shell_TrayWnd") == 0 || wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0)
{
// Primary taskbar structure
tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"Windows.UI.Composition.DesktopWindowContentBridge", nullptr);
if (!tasklist_hwnd)
return;
tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"Windows.UI.Input.InputSite.WindowClass", nullptr);
if (!tasklist_hwnd)
return;
}
else
{
// Unknown taskbar type
return;
}
if (!automation)
{
winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IUIAutomation,
automation.put_void()));
winrt::check_hresult(automation->CreateTrueCondition(true_condition.put()));
}
winrt::com_ptr<IUIAutomationElement> tempElement;
element = nullptr;
winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, tempElement.put()));
winrt::check_hresult(
tempElement->FindFirst(TreeScope_Children, true_condition.get(), element.put()));
}
bool update_buttons(std::vector<TasklistButton>& buttons)
{
if (!automation || !element)
{
return false;
}
winrt::com_ptr<IUIAutomationElementArray> elements;
if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0)
return false;
if (!elements)
return false;
int count;
if (elements->get_Length(&count) < 0)
return false;
winrt::com_ptr<IUIAutomationElement> child;
std::vector<TasklistButton> found_buttons;
found_buttons.reserve(count);
for (int i = 0; i < count; ++i)
{
child = nullptr;
if (elements->GetElement(i, child.put()) < 0)
return false;
TasklistButton button = {};
if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0)
{
if (var_rect.vt == (VT_R8 | VT_ARRAY))
{
LONG pos;
double value;
pos = 0;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.x = static_cast<long>(value);
pos = 1;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.y = static_cast<long>(value);
pos = 2;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.width = static_cast<long>(value);
pos = 3;
SafeArrayGetElement(var_rect.parray, &pos, &value);
button.height = static_cast<long>(value);
}
VariantClear(&var_rect);
}
else
{
return false;
}
if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0)
{
wcsncpy_s(button.name, automation_id, _countof(button.name));
SysFreeString(automation_id);
if (wcsncmp(button.name, L"Appid:", wcslen(L"Appid:")) != 0)
{
continue;
}
}
found_buttons.push_back(button);
}
// assign keynums
buttons.clear();
for (auto& button : found_buttons)
{
if (buttons.empty())
{
button.keynum = 1;
buttons.push_back(std::move(button));
}
else
{
if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row
break;
if (wcsncmp(button.name, buttons.back().name, _countof(button.name)) == 0)
continue; // skip buttons from the same app
button.keynum = buttons.back().keynum + 1;
buttons.push_back(std::move(button));
if (buttons.back().keynum == 10)
break; // no more than 10 buttons
}
}
return true;
}
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size)
{
update(monitor);
static std::vector<TasklistButton> buttons;
update_buttons(buttons);
*size = static_cast<int>(buttons.size());
if (*size == 0)
{
// After a certain Windows update, the old method stopped working, try the new one
update_new(monitor);
update_buttons(buttons);
*size = static_cast<int>(buttons.size());
}
return buttons.data();
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
struct TasklistButton
{
wchar_t name[256];
int x;
int y;
int width;
int height;
int keynum;
};
extern "C"
{
// Helper to get the taskbar HWND for the monitor under the cursor
HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor);
bool update_buttons(std::vector<TasklistButton>& buttons);
__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size);
}

View File

@@ -70,9 +70,6 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\logging\logging.vcxproj">
<Project>{7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f}</Project>
</ProjectReference>
<ProjectReference Include="..\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>

View File

@@ -0,0 +1,736 @@
// 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.
#include "pch.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <common/updating/configBackup.h>
#include <common/updating/updateLifecycle.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace fs = std::filesystem;
namespace UpdatingUnitTests
{
// Helper to create a temp directory for test isolation.
// Each instance gets a unique subdirectory to prevent test interference.
class TempDir
{
public:
TempDir()
{
wchar_t tempPath[MAX_PATH + 1];
GetTempPathW(MAX_PATH, tempPath);
static std::atomic<int> counter{0};
m_path = fs::path(tempPath) / (L"PowerToysUpdateTests_" + std::to_wstring(counter++));
// Ensure clean state
std::error_code ec;
fs::remove_all(m_path, ec);
fs::create_directories(m_path, ec);
}
~TempDir()
{
std::error_code ec;
fs::remove_all(m_path, ec);
}
const fs::path& path() const { return m_path; }
// Write a file with the given content
void WriteFile(const fs::path& relativePath, const std::string& content)
{
auto fullPath = m_path / relativePath;
fs::create_directories(fullPath.parent_path());
std::ofstream file(fullPath, std::ios::binary);
file.write(content.data(), content.size());
}
// Write a file with raw bytes (including null bytes for corruption testing)
void WriteFileBytes(const fs::path& relativePath, const std::vector<char>& bytes)
{
auto fullPath = m_path / relativePath;
fs::create_directories(fullPath.parent_path());
std::ofstream file(fullPath, std::ios::binary);
file.write(bytes.data(), bytes.size());
}
// Read file content as string
std::string ReadFile(const fs::path& relativePath)
{
auto fullPath = m_path / relativePath;
std::ifstream file(fullPath, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
}
bool FileExists(const fs::path& relativePath)
{
return fs::exists(m_path / relativePath);
}
private:
fs::path m_path;
};
TEST_CLASS(IsJsonFileCorruptedTests)
{
public:
// Tests IsJsonFileCorrupted: valid JSON with no null bytes returns false.
// Covers: configBackup.h IsJsonFileCorrupted — happy path, full file scan.
TEST_METHOD(CleanJsonFileIsNotCorrupted)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark","startup":true})");
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
}
// Tests IsJsonFileCorrupted: zero-length file returns false (empty is not corrupted).
// Covers: configBackup.h IsJsonFileCorrupted — file.read returns 0 bytes immediately.
TEST_METHOD(EmptyFileIsNotCorrupted)
{
TempDir dir;
dir.WriteFile(L"empty.json", "");
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"empty.json"));
}
// Tests IsJsonFileCorrupted: file containing embedded null bytes returns true.
// Covers: configBackup.h IsJsonFileCorrupted — null byte detection within buffer.
TEST_METHOD(FileWithNullBytesIsCorrupted)
{
TempDir dir;
std::vector<char> corrupted = { '{', '"', 'a', '"', ':', '\0', '\0', '\0', '}' };
dir.WriteFileBytes(L"corrupted.json", corrupted);
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"corrupted.json"));
}
// Tests IsJsonFileCorrupted: file entirely filled with 0x00 bytes returns true.
// Reproduces the exact bug from #46179 where installer zeroed out JSON files.
// Covers: configBackup.h IsJsonFileCorrupted — first byte is null.
TEST_METHOD(FileFilledWithNullBytesIsCorrupted)
{
TempDir dir;
std::vector<char> allNulls(1024, '\0');
dir.WriteFileBytes(L"workspaces.json", allNulls);
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"workspaces.json"));
}
// Tests IsJsonFileCorrupted: path that does not exist returns false.
// Covers: configBackup.h IsJsonFileCorrupted — file.is_open() check.
TEST_METHOD(NonExistentFileIsNotCorrupted)
{
TempDir dir;
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"missing.json"));
}
// Tests IsJsonFileCorrupted: file larger than the 4096-byte read chunk
// with no null bytes returns false.
// Covers: configBackup.h IsJsonFileCorrupted — multi-chunk while loop.
TEST_METHOD(LargeCleanFileIsNotCorrupted)
{
TempDir dir;
std::string largeContent(8192, 'x');
dir.WriteFile(L"large.json", largeContent);
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"large.json"));
}
// Tests IsJsonFileCorrupted: null byte placed after the first 4096-byte
// chunk boundary is still detected.
// Covers: configBackup.h IsJsonFileCorrupted — second chunk scan.
TEST_METHOD(NullByteAtEndOfLargeFileIsDetected)
{
TempDir dir;
std::string content(5000, 'x');
content[4999] = '\0';
std::vector<char> bytes(content.begin(), content.end());
dir.WriteFileBytes(L"sneaky.json", bytes);
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"sneaky.json"));
}
};
TEST_CLASS(BackupConfigFilesTests)
{
public:
// Tests BackupConfigFiles: root-level .json files are copied to ConfigBackup.
// Covers: configBackup.h BackupConfigFiles — root directory_iterator,
// is_regular_file && extension == ".json" branch.
// Setup: Two root-level JSON files.
TEST_METHOD(BackupCopiesRootJsonFiles)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
dir.WriteFile(L"UpdateState.json", R"({"state":0})");
updating::BackupConfigFiles(dir.path());
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\UpdateState.json"));
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
}
// Tests BackupConfigFiles: .json files inside module subdirectories are
// copied to ConfigBackup/<module>/.
// Covers: configBackup.h BackupConfigFiles — is_directory branch,
// module directory_iterator with extension filter.
// Setup: Root JSON + two module directories with JSON files.
TEST_METHOD(BackupCopiesModuleJsonFiles)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[]})");
updating::BackupConfigFiles(dir.path());
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
Assert::AreEqual(std::string(R"({"zones":[]})"),
dir.ReadFile(L"ConfigBackup\\FancyZones\\settings.json"));
}
// Tests BackupConfigFiles: non-.json files at root level are not copied.
// Covers: configBackup.h BackupConfigFiles — extension filter excludes .log.
// Setup: One JSON file + one .log file at root.
TEST_METHOD(BackupSkipsNonJsonFiles)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
dir.WriteFile(L"debug.log", "log data");
updating::BackupConfigFiles(dir.path());
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\debug.log"));
}
// Tests BackupConfigFiles: the "Updates" directory is explicitly skipped.
// Covers: configBackup.h BackupConfigFiles — dirName == L"Updates" continue.
// Setup: Root JSON + Updates directory containing a file.
TEST_METHOD(BackupSkipsUpdatesDirectory)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
dir.WriteFile(L"Updates\\installer.exe", "fake exe");
updating::BackupConfigFiles(dir.path());
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\Updates"));
}
// Tests BackupConfigFiles: running backup twice overwrites the previous
// backup with current file content.
// Covers: configBackup.h BackupConfigFiles — fs::remove_all(backupDir) +
// copy_options::overwrite_existing.
// Setup: Backup, modify original, backup again.
TEST_METHOD(BackupOverwritesPreviousBackup)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"version":1})");
updating::BackupConfigFiles(dir.path());
// Update the original
dir.WriteFile(L"settings.json", R"({"version":2})");
updating::BackupConfigFiles(dir.path());
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"ConfigBackup\\settings.json"));
}
// Tests BackupConfigFiles: non-.json files inside module subdirectories
// (e.g., FancyZones/zones.dat) should NOT be backed up.
// Covers: configBackup.h BackupConfigFiles — extension filter in module loop.
TEST_METHOD(BackupSkipsNonJsonFilesInModuleDirs)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({})");
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[]})");
dir.WriteFile(L"FancyZones\\zones.dat", "binary data");
updating::BackupConfigFiles(dir.path());
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\FancyZones\\zones.dat"));
}
// Tests BackupConfigFiles: empty root directory with no files produces
// an empty ConfigBackup dir without errors.
// Covers: configBackup.h BackupConfigFiles — empty directory_iterator.
TEST_METHOD(BackupEmptyRootDirSucceeds)
{
TempDir dir;
// Root dir exists but has no files
updating::BackupConfigFiles(dir.path());
Assert::IsTrue(dir.FileExists(L"ConfigBackup"));
}
};
TEST_CLASS(RestoreCorruptedConfigsTests)
{
public:
// Tests RestoreCorruptedConfigs: corrupted root-level JSON file is restored
// from the good backup copy.
// Covers: configBackup.h RestoreCorruptedConfigs — root file restore branch,
// fs::exists + IsJsonFileCorrupted + backup integrity check.
// Setup: Good file -> backup -> corrupt original -> restore.
TEST_METHOD(RestoreFixesCorruptedRootFile)
{
TempDir dir;
const std::string goodContent = R"({"theme":"dark"})";
dir.WriteFile(L"settings.json", goodContent);
// Backup
updating::BackupConfigFiles(dir.path());
// Corrupt the original
std::vector<char> corrupted(goodContent.size(), '\0');
dir.WriteFileBytes(L"settings.json", corrupted);
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
// Restore
updating::RestoreCorruptedConfigs(dir.path());
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
Assert::AreEqual(goodContent, dir.ReadFile(L"settings.json"));
}
// Tests RestoreCorruptedConfigs: corrupted module-level JSON file is restored
// from the good backup copy.
// Covers: configBackup.h RestoreCorruptedConfigs — module directory branch,
// moduleBackupEntry restore with integrity check.
// Setup: Module file + root file -> backup -> corrupt module file -> restore.
TEST_METHOD(RestoreFixesCorruptedModuleFile)
{
TempDir dir;
const std::string goodContent = R"({"workspaces":[]})";
dir.WriteFile(L"Workspaces\\workspaces.json", goodContent);
dir.WriteFile(L"settings.json", R"({})");
updating::BackupConfigFiles(dir.path());
// Corrupt the module file
std::vector<char> corrupted(goodContent.size(), '\0');
dir.WriteFileBytes(L"Workspaces\\workspaces.json", corrupted);
updating::RestoreCorruptedConfigs(dir.path());
Assert::AreEqual(goodContent, dir.ReadFile(L"Workspaces\\workspaces.json"));
}
// Tests RestoreCorruptedConfigs: clean (non-corrupted) files are NOT
// overwritten by backup — preserves user changes made after backup.
// Covers: configBackup.h RestoreCorruptedConfigs — IsJsonFileCorrupted
// returns false, copy_file is skipped.
// Setup: File -> backup -> modify (but keep valid) -> restore.
TEST_METHOD(RestoreLeavesCleanFilesUntouched)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"version":1})");
updating::BackupConfigFiles(dir.path());
// Modify original (but keep it clean JSON)
dir.WriteFile(L"settings.json", R"({"version":2})");
updating::RestoreCorruptedConfigs(dir.path());
// Should NOT have been restored since it's not corrupted
Assert::AreEqual(std::string(R"({"version":2})"), dir.ReadFile(L"settings.json"));
}
// Tests RestoreCorruptedConfigs: when no ConfigBackup directory exists,
// restore silently does nothing (no crash, no data loss).
// Covers: configBackup.h RestoreCorruptedConfigs — !fs::exists(backupDir)
// early return.
// Setup: File with no prior backup.
TEST_METHOD(RestoreHandlesMissingBackupDirectory)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
// No backup was created - restore should silently do nothing
updating::RestoreCorruptedConfigs(dir.path());
Assert::AreEqual(std::string(R"({"theme":"dark"})"), dir.ReadFile(L"settings.json"));
}
// Tests RestoreCorruptedConfigs: end-to-end scenario with multiple modules,
// some corrupted and some clean, verifying selective restore.
// Covers: configBackup.h RestoreCorruptedConfigs — both root and module
// branches, selective restore based on corruption status.
// Setup: 4 modules -> backup -> corrupt 2 -> restore -> verify all 4.
TEST_METHOD(FullBackupAndRestoreRoundTrip)
{
TempDir dir;
// Set up a realistic config structure
dir.WriteFile(L"settings.json", R"({"startup":true,"theme":"dark"})");
dir.WriteFile(L"FancyZones\\settings.json", R"({"zones":[{"id":1}]})");
dir.WriteFile(L"Workspaces\\workspaces.json", R"({"workspaces":[{"name":"dev"}]})");
dir.WriteFile(L"KeyboardManager\\default.json", R"({"remaps":[]})");
// Backup
updating::BackupConfigFiles(dir.path());
// Corrupt some files (simulating #46179 scenario)
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(100, '\0'));
dir.WriteFileBytes(L"settings.json", std::vector<char>(50, '\0'));
// Leave FancyZones and KBM clean
// Restore
updating::RestoreCorruptedConfigs(dir.path());
// Corrupted files should be restored
Assert::AreEqual(std::string(R"({"startup":true,"theme":"dark"})"), dir.ReadFile(L"settings.json"));
Assert::AreEqual(std::string(R"({"workspaces":[{"name":"dev"}]})"), dir.ReadFile(L"Workspaces\\workspaces.json"));
// Clean files should be unchanged
Assert::AreEqual(std::string(R"({"zones":[{"id":1}]})"), dir.ReadFile(L"FancyZones\\settings.json"));
Assert::AreEqual(std::string(R"({"remaps":[]})"), dir.ReadFile(L"KeyboardManager\\default.json"));
}
// Tests RestoreCorruptedConfigs: when the original file has been deleted
// (not corrupted), restore should NOT recreate it from backup. The installer
// may have intentionally removed obsolete config files.
// Covers: configBackup.h RestoreCorruptedConfigs — fs::exists guard.
TEST_METHOD(RestoreSkipsDeletedOriginals)
{
TempDir dir;
dir.WriteFile(L"obsolete.json", R"({"old":true})");
updating::BackupConfigFiles(dir.path());
// Installer deletes the file
std::error_code ec;
fs::remove(dir.path() / L"obsolete.json", ec);
updating::RestoreCorruptedConfigs(dir.path());
// Should NOT be recreated
Assert::IsFalse(dir.FileExists(L"obsolete.json"));
}
// Tests RestoreCorruptedConfigs: when the backup file itself is corrupted
// (e.g., disk error during backup), restore should NOT copy corrupted
// backup over the original — that would make things worse.
// Covers: configBackup.h RestoreCorruptedConfigs — backup integrity check (B2 fix).
TEST_METHOD(RestoreSkipsCorruptedBackup)
{
TempDir dir;
dir.WriteFile(L"settings.json", R"({"theme":"dark"})");
updating::BackupConfigFiles(dir.path());
// Corrupt BOTH the original AND the backup
std::vector<char> nulls(50, '\0');
dir.WriteFileBytes(L"settings.json", nulls);
dir.WriteFileBytes(L"ConfigBackup\\settings.json", nulls);
updating::RestoreCorruptedConfigs(dir.path());
// Original should still be corrupted — we don't restore from bad backup
Assert::IsTrue(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
}
};
// Simulates what actually happens during a PowerToys upgrade:
// 1. User has settings from normal use
// 2. Updater backs up before install (Stage 1)
// 3. Installer runs and corrupts some files (simulated)
// 4. Updater restores corrupted files (Stage 2)
// 5. PT relaunches and finds working configs
TEST_CLASS(UpgradeSimulationTests)
{
public:
// Tests full upgrade simulation: backup -> installer corrupts files -> restore.
// Verifies that corrupted files are restored and clean files are untouched.
// Covers: configBackup.h BackupConfigFiles + RestoreCorruptedConfigs —
// end-to-end with 5 modules, 2 corrupted, 3 clean.
// Setup: Realistic config structure with multiple modules.
TEST_METHOD(SimulateUpgradeWithCorruption)
{
TempDir dir;
// === User's real config state before upgrade ===
dir.WriteFile(L"settings.json",
R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})");
dir.WriteFile(L"FancyZones\\settings.json",
R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})");
dir.WriteFile(L"Workspaces\\workspaces.json",
R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})");
dir.WriteFile(L"KeyboardManager\\default.json",
R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})");
dir.WriteFile(L"MouseWithoutBorders\\settings.json",
R"({"machineKey":"abc123","connectToAll":true})");
// Non-JSON files that should be left alone
dir.WriteFile(L"update.log", "2026-04-11 update started");
// === Stage 1: Backup before killing PT ===
updating::BackupConfigFiles(dir.path());
// Verify backup was created correctly
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\settings.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\FancyZones\\settings.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\Workspaces\\workspaces.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\KeyboardManager\\default.json"));
Assert::IsTrue(dir.FileExists(L"ConfigBackup\\MouseWithoutBorders\\settings.json"));
Assert::IsFalse(dir.FileExists(L"ConfigBackup\\update.log"));
// === Installer runs: some files get corrupted (the #46179 scenario) ===
// Workspaces JSON filled with null bytes
dir.WriteFileBytes(L"Workspaces\\workspaces.json", std::vector<char>(512, '\0'));
// Main settings partially corrupted (null bytes injected)
std::vector<char> partialCorrupt = { '{', '"', 's', '\0', '\0', '\0', '\0', '}' };
dir.WriteFileBytes(L"settings.json", partialCorrupt);
// FancyZones, KBM, and MWB survive the install fine
// (this is realistic - not all files get corrupted)
// === Stage 2: Restore after install completes ===
updating::RestoreCorruptedConfigs(dir.path());
// === Verify: PT relaunches and finds working configs ===
// Corrupted files should be restored from backup
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"settings.json"));
Assert::IsFalse(updating::IsJsonFileCorrupted(dir.path() / L"Workspaces\\workspaces.json"));
Assert::AreEqual(
std::string(R"({"startup":true,"theme":"dark","run_elevated":false,"download_updates_automatically":true})"),
dir.ReadFile(L"settings.json"));
Assert::AreEqual(
std::string(R"({"workspaces":[{"name":"dev","apps":["code","terminal"]}]})"),
dir.ReadFile(L"Workspaces\\workspaces.json"));
// Clean files should be untouched (not overwritten with backup)
Assert::AreEqual(
std::string(R"({"zones":[{"id":1,"rect":{"x":0,"y":0,"w":960,"h":1080}}]})"),
dir.ReadFile(L"FancyZones\\settings.json"));
Assert::AreEqual(
std::string(R"({"remapKeys":{"inProcess":[{"original":"0x41","new":"0x42"}]}})"),
dir.ReadFile(L"KeyboardManager\\default.json"));
Assert::AreEqual(
std::string(R"({"machineKey":"abc123","connectToAll":true})"),
dir.ReadFile(L"MouseWithoutBorders\\settings.json"));
}
// Tests upgrade from an old version that has fewer modules than the new version.
// Verifies that new module configs (created by the installer) are not touched
// by restore, while corrupted old configs are restored.
// Covers: configBackup.h RestoreCorruptedConfigs — module dir in root that
// has no corresponding backup entry.
// Setup: Old version with 1 module -> backup -> new installer adds module -> corrupt old -> restore.
TEST_METHOD(SimulateUpgradeFromVeryOldVersion)
{
TempDir dir;
// Old version had fewer modules - only settings.json
dir.WriteFile(L"settings.json", R"({"theme":"dark","powertoys_version":"v0.60.0"})");
// Backup
updating::BackupConfigFiles(dir.path());
// New installer creates new module dirs that didn't exist before
dir.WriteFile(L"NewModule\\settings.json", R"({"enabled":true})");
// Old settings get corrupted during upgrade
dir.WriteFileBytes(L"settings.json", std::vector<char>(100, '\0'));
// Restore
updating::RestoreCorruptedConfigs(dir.path());
// Old settings restored
Assert::AreEqual(
std::string(R"({"theme":"dark","powertoys_version":"v0.60.0"})"),
dir.ReadFile(L"settings.json"));
// New module settings untouched (no backup existed for them)
Assert::AreEqual(
std::string(R"({"enabled":true})"),
dir.ReadFile(L"NewModule\\settings.json"));
}
};
// Tests for the update lifecycle: argument passing between Stage 1 and Stage 2,
// relaunch path construction, and the handoff that was broken in #42004/#43011/#44071.
TEST_CLASS(UpdateLifecycleTests)
{
public:
// Tests BuildStage2Arguments: output contains the stage 2 flag, installer path,
// and install directory — all three components needed for Stage 2.
// Covers: updateLifecycle.h BuildStage2Arguments — concatenation logic.
// Setup: Typical paths with spaces (Program Files).
TEST_METHOD(BuildStage2ArgumentsContainsInstallerAndInstallDir)
{
const auto args = updating::BuildStage2Arguments(
L"-update_now_stage_2",
L"C:\\Users\\test\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-x64.exe",
L"C:\\Program Files\\PowerToys");
// Must contain the stage 2 flag
Assert::IsTrue(args.find(L"-update_now_stage_2") != std::wstring::npos);
// Must contain the installer path (quoted)
Assert::IsTrue(args.find(L"powertoyssetup-x64.exe") != std::wstring::npos);
// Must contain the install directory (quoted) — this was MISSING before our fix
Assert::IsTrue(args.find(L"C:\\Program Files\\PowerToys") != std::wstring::npos);
}
// Tests BuildStage2Arguments: both paths are wrapped in double quotes to
// survive CommandLineToArgvW parsing when paths contain spaces.
// Covers: updateLifecycle.h BuildStage2Arguments — quote wrapping.
// Setup: Installer path with spaces.
TEST_METHOD(BuildStage2ArgumentsQuotesBothPaths)
{
const auto args = updating::BuildStage2Arguments(
L"-update_now_stage_2",
L"C:\\path with spaces\\installer.exe",
L"C:\\Program Files\\PowerToys");
// Count quotes — should have 4 (open/close for each path)
size_t quoteCount = std::count(args.begin(), args.end(), L'"');
Assert::AreEqual(size_t{ 4 }, quoteCount);
}
// Tests BuildPowerToysExePath: appends "PowerToys.exe" to the install dir.
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path / operator.
// Setup: Standard install path without trailing backslash.
TEST_METHOD(BuildPowerToysExePathAppendsExeName)
{
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys");
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
}
// Tests BuildPowerToysExePath: trailing backslash does not produce double
// backslash (e.g., "...PowerToys\\PowerToys.exe").
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path normalizes separators.
// Setup: Install path with trailing backslash.
TEST_METHOD(BuildPowerToysExePathHandlesTrailingBackslash)
{
const auto path = updating::BuildPowerToysExePath(L"C:\\Program Files\\PowerToys\\");
Assert::AreEqual(std::wstring(L"C:\\Program Files\\PowerToys\\PowerToys.exe"), path);
}
// Tests BuildPowerToysExePath: empty string produces just "PowerToys.exe".
// Covers: updateLifecycle.h BuildPowerToysExePath — fs::path with empty input.
// Setup: Empty install directory string.
TEST_METHOD(BuildPowerToysExePathHandlesEmptyString)
{
const auto path = updating::BuildPowerToysExePath(L"");
Assert::AreEqual(std::wstring(L"PowerToys.exe"), path);
}
// Tests CanRelaunchAfterUpdate: returns true when Stage 2 receives
// the install directory (argCount >= 4), false otherwise.
// This is the gate that prevents relaunch when using an old Stage 1
// that didn't pass the install dir (#42004/#43011/#44071).
// Covers: updateLifecycle.h CanRelaunchAfterUpdate.
TEST_METHOD(CanRelaunchReflectsArgCount)
{
// Old Stage 1 (pre-fix): only passed action + installer = 3 args
Assert::IsFalse(updating::CanRelaunchAfterUpdate(0));
Assert::IsFalse(updating::CanRelaunchAfterUpdate(1));
Assert::IsFalse(updating::CanRelaunchAfterUpdate(2));
Assert::IsFalse(updating::CanRelaunchAfterUpdate(3));
// New Stage 1 (post-fix): passes action + installer + installDir = 4 args
Assert::IsTrue(updating::CanRelaunchAfterUpdate(4));
Assert::IsTrue(updating::CanRelaunchAfterUpdate(5));
}
// Tests BuildStage2Arguments + CommandLineToArgvW round-trip: the exact
// scenario where Stage 1 builds args and Windows parses them in Stage 2.
// Verifies quoting is correct so paths with spaces survive the round trip.
// Covers: updateLifecycle.h BuildStage2Arguments — quote correctness.
// Setup: Realistic paths with spaces and version numbers.
TEST_METHOD(Stage2ArgumentsCanBeRoundTrippedThroughCommandLineToArgvW)
{
const std::wstring installerPath = L"C:\\Users\\test user\\AppData\\Local\\PowerToys\\Updates\\powertoyssetup-0.86.0-x64.exe";
const std::wstring installDir = L"C:\\Program Files\\PowerToys";
const auto args = updating::BuildStage2Arguments(L"-update_now_stage_2", installerPath, installDir);
// Simulate what Windows does: prepend a fake exe name and parse
std::wstring commandLine = L"PowerToys.Update.exe " + args;
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(commandLine.c_str(), &argc);
Assert::IsNotNull(argv);
Assert::AreEqual(4, argc);
Assert::AreEqual(std::wstring(L"-update_now_stage_2"), std::wstring(argv[1]));
Assert::AreEqual(installerPath, std::wstring(argv[2]));
Assert::AreEqual(installDir, std::wstring(argv[3]));
LocalFree(argv);
}
};
// Tests for IsSafeDownloadedInstallerFilename: the updater reads
// downloadedInstallerFilename from the persisted UpdateState.json and must only
// accept a plain filename so it never looks outside the Updates folder when the
// cached state is stale, corrupted, or otherwise unexpected.
TEST_CLASS(IsSafeDownloadedInstallerFilenameTests)
{
public:
// Normal installer asset names are bare filenames and must be accepted,
// so the regular update flow does not regress.
TEST_METHOD(NormalInstallerFilenamesAreAccepted)
{
Assert::IsTrue(updating::IsSafeDownloadedInstallerFilename(L"PowerToysSetup-0.95.0-x64.exe"));
Assert::IsTrue(updating::IsSafeDownloadedInstallerFilename(L"PowerToysUserSetup-0.95.0-arm64.exe"));
Assert::IsTrue(updating::IsSafeDownloadedInstallerFilename(L"PowerToysSetup-1.0.0-x64.msi"));
}
// Empty values must be rejected (no installer to run).
TEST_METHOD(EmptyFilenameIsRejected)
{
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L""));
}
// Relative parent-directory components must be rejected.
TEST_METHOD(ParentDirectoryComponentsAreRejected)
{
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"..\\..\\setup.msi"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"../../setup.exe"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L".."));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"."));
}
// Any directory component (even without "..") must be rejected — the value
// must be a single bare filename.
TEST_METHOD(NestedPathComponentsAreRejected)
{
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"sub\\setup.exe"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"sub/setup.exe"));
}
// Absolute paths, drive-relative paths and UNC paths must be rejected,
// because fs::path's operator/ would let them replace the Updates directory.
TEST_METHOD(AbsoluteAndUncPathsAreRejected)
{
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"C:\\setup.msi"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"C:setup.msi"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"\\setup.msi"));
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"\\\\server\\share\\setup.exe"));
}
// A bare filename that merely contains a ".." substring is rejected as a
// conservative measure (real asset names never contain "..").
TEST_METHOD(EmbeddedDotDotSubstringIsRejected)
{
Assert::IsFalse(updating::IsSafeDownloadedInstallerFilename(L"setup..name.exe"));
}
};
}

View File

@@ -0,0 +1,45 @@
<?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" />
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UpdatingUnitTests</RootNamespace>
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
<ProjectName>Updating.UnitTests</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\tests\UpdatingUnitTests\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\..\;..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="UpdatingTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -0,0 +1,5 @@
// 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.
#include "pch.h"

View File

@@ -0,0 +1,17 @@
// 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.
#ifndef PCH_H
#define PCH_H
#include <atomic>
#include <Windows.h>
// Suppressing 26466 - Don't use static_cast downcasts - in CppUnitTest.h
#pragma warning(push)
#pragma warning(disable : 26466)
#include "CppUnitTest.h"
#pragma warning(pop)
#endif //PCH_H

View File

@@ -0,0 +1,228 @@
// 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.
#pragma once
#include <filesystem>
#include <fstream>
#include <string>
namespace updating
{
namespace fs = std::filesystem;
struct BackupResult
{
int filesBackedUp{ 0 };
int errors{ 0 };
};
struct RestoreResult
{
int filesRestored{ 0 };
int filesChecked{ 0 };
int errors{ 0 };
};
// Check if a JSON file is corrupted (contains null bytes, as seen in #46179)
inline bool IsJsonFileCorrupted(const fs::path& filePath)
{
try
{
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open())
{
return false;
}
constexpr size_t c_readChunkSize{ 4096 };
char buffer[c_readChunkSize];
while (file.read(buffer, c_readChunkSize) || file.gcount() > 0)
{
const auto bytesRead = file.gcount();
for (std::streamsize i = 0; i < bytesRead; ++i)
{
if (buffer[i] == '\0')
{
return true;
}
}
}
return false;
}
catch (...)
{
return true;
}
}
// Backup all JSON config files before update to protect against corruption (#46179)
inline BackupResult BackupConfigFiles(const fs::path& rootPath)
{
BackupResult result{};
try
{
const fs::path backupDir = rootPath / L"ConfigBackup";
std::error_code ec;
fs::remove_all(backupDir, ec);
fs::create_directories(backupDir, ec);
if (ec)
{
result.errors++;
return result;
}
for (const auto& entry : fs::directory_iterator(rootPath, ec))
{
if (ec)
{
result.errors++;
break;
}
if (entry.is_regular_file() && entry.path().extension() == L".json")
{
std::error_code copyEc;
fs::copy_file(entry.path(), backupDir / entry.path().filename(), fs::copy_options::overwrite_existing, copyEc);
if (copyEc)
{
result.errors++;
}
else
{
result.filesBackedUp++;
}
}
else if (entry.is_directory())
{
const auto dirName = entry.path().filename().wstring();
if (dirName == L"ConfigBackup" || dirName == L"Updates")
{
continue;
}
const auto moduleBackup = backupDir / entry.path().filename();
fs::create_directories(moduleBackup, ec);
std::error_code moduleEc;
for (const auto& moduleEntry : fs::directory_iterator(entry.path(), moduleEc))
{
if (moduleEc)
{
result.errors++;
break;
}
if (moduleEntry.is_regular_file() && moduleEntry.path().extension() == L".json")
{
std::error_code copyEc;
fs::copy_file(moduleEntry.path(), moduleBackup / moduleEntry.path().filename(), fs::copy_options::overwrite_existing, copyEc);
if (copyEc)
{
result.errors++;
}
else
{
result.filesBackedUp++;
}
}
}
}
}
}
catch (...)
{
result.errors++;
}
return result;
}
// Restore JSON configs from backup if corruption is detected after update.
// Cleans up the backup directory afterward.
inline RestoreResult RestoreCorruptedConfigs(const fs::path& rootPath)
{
RestoreResult result{};
try
{
const fs::path backupDir = rootPath / L"ConfigBackup";
if (!fs::exists(backupDir))
{
return result;
}
std::error_code ec;
for (const auto& backupEntry : fs::directory_iterator(backupDir, ec))
{
if (ec)
{
result.errors++;
break;
}
if (backupEntry.is_regular_file() && backupEntry.path().extension() == L".json")
{
const auto originalPath = rootPath / backupEntry.path().filename();
result.filesChecked++;
if (fs::exists(originalPath) && IsJsonFileCorrupted(originalPath) && !IsJsonFileCorrupted(backupEntry.path()))
{
std::error_code copyEc;
fs::copy_file(backupEntry.path(), originalPath, fs::copy_options::overwrite_existing, copyEc);
if (copyEc)
{
result.errors++;
}
else
{
result.filesRestored++;
}
}
}
else if (backupEntry.is_directory())
{
const auto moduleDir = rootPath / backupEntry.path().filename();
std::error_code moduleEc;
for (const auto& moduleBackupEntry : fs::directory_iterator(backupEntry.path(), moduleEc))
{
if (moduleEc)
{
result.errors++;
break;
}
if (moduleBackupEntry.is_regular_file() && moduleBackupEntry.path().extension() == L".json")
{
const auto originalModulePath = moduleDir / moduleBackupEntry.path().filename();
result.filesChecked++;
if (fs::exists(originalModulePath) && IsJsonFileCorrupted(originalModulePath) && !IsJsonFileCorrupted(moduleBackupEntry.path()))
{
std::error_code copyEc;
fs::copy_file(moduleBackupEntry.path(), originalModulePath, fs::copy_options::overwrite_existing, copyEc);
if (copyEc)
{
result.errors++;
}
else
{
result.filesRestored++;
}
}
}
}
}
}
// Clean up backup directory after restore check
fs::remove_all(backupDir, ec);
}
catch (...)
{
result.errors++;
}
return result;
}
}

View File

@@ -24,8 +24,6 @@
#include <regex>
#include <charconv>
#include <expected.hpp>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.ApplicationModel.h>

View File

@@ -0,0 +1,86 @@
// 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.
#pragma once
#include <filesystem>
#include <string>
namespace updating
{
namespace fs = std::filesystem;
// Build the command-line arguments for Stage 2.
// Stage 1 passes the installer path and the PT install directory
// so Stage 2 can run the installer and relaunch PowerToys afterward.
// Note: paths containing embedded double-quote characters are not supported.
// This is safe because install paths come from get_module_folderpath().
inline std::wstring BuildStage2Arguments(
const std::wstring& stage2Flag,
const fs::path& installerPath,
const fs::path& installDir)
{
std::wstring arguments{ stage2Flag };
arguments += L" \"";
arguments += installerPath.c_str();
arguments += L"\" \"";
arguments += installDir.c_str();
arguments += L"\"";
return arguments;
}
// Build the full path to PowerToys.exe from the install directory.
// Used by Stage 2 to relaunch PT after a successful update.
inline std::wstring BuildPowerToysExePath(const std::wstring& installDir)
{
return (std::filesystem::path(installDir) / L"PowerToys.exe").wstring();
}
// Determine whether Stage 2 has enough information to relaunch PT.
// Returns true if the install directory argument was provided.
inline bool CanRelaunchAfterUpdate(int argCount)
{
// args[0]=exe, args[1]=action, args[2]=installer, args[3]=installDir
return argCount >= 4;
}
// Returns true when the value read from UpdateState.json is a plain installer
// filename that can be combined with the pending-updates directory. UpdateState.json
// is persisted state that may be stale, corrupted, or otherwise unexpected, so the
// cached filename could contain path separators or an absolute/drive-relative path.
// Only a single bare filename (the form produced by the download step) is accepted;
// anything else is rejected so the updater never looks outside the Updates folder.
inline bool IsSafeDownloadedInstallerFilename(const std::wstring& filename)
{
if (filename.empty())
{
return false;
}
// Reject any path separators or parent-directory tokens outright. Installer
// asset filenames never contain these.
if (filename.find(L'/') != std::wstring::npos ||
filename.find(L'\\') != std::wstring::npos ||
filename.find(L"..") != std::wstring::npos)
{
return false;
}
const fs::path candidate{ filename };
// Must be a single path component: no drive/root and no directory portion.
if (candidate.has_root_name() || candidate.has_root_directory() || candidate.has_parent_path())
{
return false;
}
const auto name = candidate.filename().wstring();
if (name != filename || name == L"." || name == L"..")
{
return false;
}
return true;
}
}

View File

@@ -87,11 +87,7 @@ namespace updating
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
{
#if USE_STD_EXPECTED
co_return std::unexpected(LOCAL_BUILD_ERROR);
#else
co_return nonstd::make_unexpected(LOCAL_BUILD_ERROR);
#endif
}
try
@@ -143,11 +139,7 @@ namespace updating
catch (...)
{
}
#if USE_STD_EXPECTED
co_return std::unexpected(NETWORK_ERROR);
#else
co_return nonstd::make_unexpected(NETWORK_ERROR);
#endif
}
#pragma warning(pop)

View File

@@ -5,14 +5,7 @@
#include <filesystem>
#include <variant>
#include <winrt/Windows.Foundation.h>
//#if __MSVC_VERSION__ >= 1933 // MSVC begin to support std::unexpected in 19.33
#if __has_include(<expected> ) // use the same way with excepted-lite to detect std::unexcepted, as using it as backup
#include <expected>
#define USE_STD_EXPECTED 1
#else
#include <expected.hpp>
#define USE_STD_EXPECTED 0
#endif
#include <common/version/helper.h>
#include <wil/coroutine.h>
@@ -31,12 +24,7 @@ namespace updating
std::wstring installer_filename;
};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
#if USE_STD_EXPECTED
using github_version_result = std::expected<github_version_info, std::wstring>;
#else
using github_version_result = nonstd::expected<github_version_info, std::wstring>;
#endif
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);

View File

@@ -9,7 +9,6 @@
<ProjectName>ApplicationUpdate</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\deps\expected.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>

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