Clint Rutkas
90d · built 2026-08-09
90-day totals
- Commits
- 36
- Grow
- 4.9
- Maintenance
- 1.1
- Fixes
- 6.9
- Total ETV
- 12.9
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
↓-25.0 %
vs 20 prior
↑+18.5 pp
recent vs prior
↓-11.8 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 20 by ETV in the 90-day window.
- 3.0ETVNew module: AltWindowCycle (#48281) ## Summary of the Pull Request Introduces a new utility: AltWindowCycle to quickly switch between windows from the same process using Alt + `. In release notes give @wzhudev coauthor credits as he also had an earlier PR It works like Alt + Tab, but scoped to the app you’re already in. Perfect for juggling multiple browser windows, terminals, or editor instances. https://github.com/user-attachments/assets/cd42f6af-fa5d-4f08-8f68-3c4e75c16d94 <img width="1835" height="971" alt="image" src="https://github.com/user-attachments/assets/adea59cb-6c8d-4b44-87e2-0a792c4c0b4f" /> ## PR Checklist - [x] Closes: https://github.com/microsoft/PowerToys/issues/278 - [ ] **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 This PR adds AltWindowCycle (in-proc module + Settings integration), then addresses follow-up check-spelling feedback without changing runtime behavior: - allow-list update for `ROOTOWNER` - comment text adjustment for forbidden-pattern compliance - local identifier rename (`wpx` → `whitePx`) for spelling compliance ## Validation Steps Performed - Verified `ROOTOWNER` is present in `.github/actions/spell-check/allow/code.txt` - Verified `wpx` is removed and updated occurrences in `src/modules/AltWindowCycle/AltWindowCycle.cpp` - Ran targeted diff/verification for both updated files - Ran final validation (code review + CodeQL trivial-change path) - Ran secret scan for changed files --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Clint Rutkas <crutkas@users.noreply.github.com> Copilot-Session: dd5080ea-5001-4efb-87f8-1e7218e10a4egithub.com-microsoft-PowerToys · bb99c30e · 2026-07-31
- 1.3ETVFix PT Run ThreadPool worker leak from stale query cancellation (#48394) ## Summary Fixes a ThreadPool worker leak in PowerToys Run that can eventually surface as `System.OutOfMemoryException` from `Thread.StartInternal` after rapid typing and repeated stale-query cancellation. Related: #36041 and duplicate reports #45704, #36587, #39942, #20264, and #8878. ## Root cause `MainViewModel.QueryResults` stored the active cancellation token in a mutable field. When a new query replaced that field, older workers could observe the new, non-cancelled token instead of the token belonging to their own query. The previous `CancellationTokenSource` was also disposed while its consumers could still be running. As stale queries accumulated, they continued invoking plugins and consuming ThreadPool workers until the process could no longer create another worker thread. ## Changes - Adds `QuerySession`, which owns one captured token and the complete task lifetime for a query. Superseded sessions are cancelled immediately and their token sources are disposed only after their work completes. - Uses a suspended session start so query state is published before workers can return results. - Adds generation checks before scheduling and applying work so superseded queries cannot enqueue stale plugin tasks or update current results. - Adds a per-plugin execution gate. Calls to the same plugin do not overlap, while unrelated plugins can execute independently; cancelled waiters do not occupy ThreadPool workers. - Preserves legacy `IResultUpdated` compatibility by correlating generation-0 events using `RawQuery`. - Preserves the original two-phase query contract: all non-delayed plugin queries complete and their results are applied before delayed queries start. Delayed queries remain globally parallel, and `noInitialResults` is computed from the complete non-delayed phase. - Cancels and performs a bounded wait for the active query during shutdown. ## Tests `Wox.Test`: **142/142 passing** locally. Coverage includes: - token ownership, cancellation, deferred disposal, shutdown timeout, and suspended session startup; - current-query generation matching and legacy generation-0 compatibility; - per-plugin execution gating and queued latest-query behavior; - deterministic verification that delayed queries cannot start until every non-delayed query completes. ## Manual validation 1. Hold a key in PowerToys Run for 10–15 seconds and confirm the PowerToys Run process thread count stabilizes instead of growing monotonically. 2. Exercise normal Calculator, file, web, and indexer queries. 3. Enable search query tuning and waiting for slow results; confirm results appear and final sorting completes. 4. Start a slow query and type again before it completes; only the newest query should update results. 5. Exit PowerToys with a query in flight; shutdown should complete cleanly without orphaned processes. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Copilot-Session: 54e1bb28-edae-496b-8211-0e1592ddc985github.com-microsoft-PowerToys · 6d89ade9 · 2026-07-28
- 1.0ETVFix 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>github.com-microsoft-PowerToys · f02b66c8 · 2026-05-22
- 0.9ETVAdd Peek.Common unit tests (MathHelper, PathHelper) (#49105) ## Summary Adds a **Peek.Common.UnitTests** project (MSTest) with unit coverage for Peek.Common.Helpers: - **MathHelper.Modulo** — positive/zero results, negative-dividend wrap-around, large values, and the new non-positive-divisor guard. - **MathHelper.NumberOfDigits** — single/multi-digit, negative, and 9/10 & 99/100 boundary values. - **PathHelper.IsUncPath** — standard UNC, subfolders, dotted-server and IP hosts, plus negatives: drive-letter, relative, empty, HTTP URL, ile:// URI, single backslash, and null. Also adds a small correctness guard to MathHelper.Modulo: a non-positive divisor now throws ArgumentOutOfRangeException instead of silently throwing DivideByZeroException (b == 0) or returning a misleading result (b < 0). Registers the test project in `PowerToys.slnx` (ARM64 + x64). **37 tests pass** locally (x64 Debug). ## Context This is a clean, **tests-only split of #46684** (the Peek.Common portion), intentionally **without** the bundled global dependency bump from that PR. The PowerAccent.Core portion of #46684 was shipped separately in #49104. ## Test coverage | Area | Tests | |------|-------| | MathHelper.Modulo / NumberOfDigits | included | | PathHelper.IsUncPath | included | No production behavior changes beyond the Modulo argument guard, which is covered by the new tests. Co-authored-by: Clint Rutkas <crutkas@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 0d335ffb · 2026-07-28
- 0.9ETV[Keyboard Manager] Fix stuck modifiers and dropped key-to-text remaps (#48571) ## Summary Fixes stuck modifier keys and silently-dropped remaps on Keyboard Manager's **single key → text** path, and adds unit coverage (including a mockable injection-failure seam). ## What this changes 1. **Insert a dummy key event before releasing held modifiers.** Releasing a lone Win or Alt key-up otherwise triggers the Start Menu / menu bar. The dummy key absorbs it so the release is inert. The dummy + releases are only injected when a modifier is actually held. 2. **Accept `WM_SYSKEYDOWN` as well as `WM_KEYDOWN`.** While Alt is held the system delivers `WM_SYSKEYDOWN`, so the previous `WM_KEYDOWN`-only guard silently dropped the remap whenever Alt was down. 3. **Route `Helpers::SendTextInput` through `InputInterface`** instead of calling Win32 `SendInput` directly. Besides making the path mockable, this stops the existing unit tests from injecting real keystrokes into the OS during a test run. Text is still flushed per character to preserve the existing batching workaround. 4. **Never re-press released modifiers.** Once a modifier key-up is injected, `GetAsyncKeyState` reports it as up, so re-pressing risks leaving it stuck if the user let go during injection. Leaving it released is always safe. ## Testing - New `MockedInput` failure seam (`SetSendVirtualInputShouldFail`). - `RemappedKey_ShouldPassOriginalKeyThrough_WhenInjectionFails` — verifies the original key is passed through when injection fails (the core stuck-key behavior, previously untestable because the mock always succeeded). - `HandleSingleKeyToTextRemapEvent_ShouldFireAndReleaseAlt_WhenAltIsHeld` — covers fix #2 by asserting the remap still fires (and releases the held Alt) when the key arrives as `WM_SYSKEYDOWN`. - Full Keyboard Manager engine suite: **98/98 passing**, Release x64, 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>github.com-microsoft-PowerToys · a864d421 · 2026-06-25
- 0.7ETVDev/crutkas/ripple v2.1 + spelling allow-list follow-up (#48232) ## Summary of the Pull Request Adds a follow-up metadata fix to the existing Mouse Highlighter ripple v2.1 work by allowing the term `xhair` in repo spell-check configuration. ## 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 - Added `xhair` to `.github/actions/spell-check/expect.txt`. - This addresses spelling feedback on MouseHighlighter ripple/crosshair code without changing runtime behavior. - No functional changes to Mouse Highlighter logic were made in this follow-up commit. ## Validation Steps Performed - Verified the only content change is the new `xhair` entry in spell-check expected words. - Ran secret scanning on changed file (`.github/actions/spell-check/expect.txt`) with no findings. - Ran parallel validation: - Code Review: no issues. - CodeQL: skipped as trivial metadata-only change. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 6dd1ce5d · 2026-06-26
- 0.6ETV[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>github.com-microsoft-PowerToys · 968a7ac4 · 2026-06-23
- 0.5ETVFix runner APPLICATION_HANG_QUIESCE: handle WM_ENDSESSION and skip blocking shutdown cleanup (#48363) ## Summary The runner WndProc (`tray_icon_window_proc`) does not handle `WM_QUERYENDSESSION` / `WM_ENDSESSION`, **and** its `WM_DESTROY` teardown performs blocking cross-process cleanup. Both contribute to the Watson failure `APPLICATION_HANG_QUIESCE_cfffffff_PowerToys.exe!run_message_loop` on OS shutdown, sign-out, or restart: 1. Without a `WM_ENDSESSION` handler, `DefWindowProc` returns `0` without posting a quit message, so `run_message_loop` stays parked in `GetMessageW` until the OS quiesce timeout (~5 s) force-terminates the process. 2. Even once teardown starts, `WM_DESTROY` calls `close_settings_window()`, which blocks up to 1.5 s on `WaitForSingleObject` against `PowerToys.Settings.exe` (`src/runner/settings_window.cpp:712`), plus `Shell_NotifyIcon(NIM_DELETE)` during Explorer teardown. The Windows [shutdown guidance](https://learn.microsoft.com/windows/win32/shutdown/shutting-down) is explicit that handlers must not block. This PR fixes both issues for the always-on runner. Rollout to module-owned windows is intentionally separate and tracked in #49539. > Supersedes #48378 (same Watson bucket) by combining its no-blocking-cleanup fix with a reusable helper and unit tests. The cleanup-skip insight is credited to @yeelam-gordon. Related (same failure class, different binary): #41260. ## Root cause `src/runner/tray_icon.cpp` → `tray_icon_window_proc` had no case for `WM_QUERYENDSESSION` / `WM_ENDSESSION`, and `WM_DESTROY` unconditionally ran cross-process cleanup. On a full Windows session end, the OS delivers `WM_ENDSESSION` to child applications and reaps them independently, so the runner's waits consume the quiesce budget without helping shutdown complete. ## Fix ### 1. Explicitly stateless helper in `src/common/utils/window.h` `handle_stateless_session_end_message`: - `WM_QUERYENDSESSION` → returns `TRUE`. The name makes clear that this helper is only for processes with no unsaved user state. - `WM_ENDSESSION(TRUE)` → calls `DestroyWindow(window)`, driving the existing `WM_DESTROY → PostQuitMessage(0)` path so `run_message_loop` unwinds. - `WM_ENDSESSION(FALSE)` → leaves the window alone because another application cancelled shutdown. - The optional `out_system_session_ending` flag is set only when the full Windows session is ending. `ENDSESSION_CLOSEAPP` still closes the runner but leaves the flag false so Restart Manager requests retain normal child-process cleanup. Stateful modules must implement their own save/permission behavior rather than adopt this helper. `tray_icon_window_proc` calls it at the top of dispatch and returns immediately when the message is handled. ### 2. Skip blocking cleanup only for a full Windows session end `WM_DESTROY` branches on `g_system_session_ending`: - **User-initiated close or Restart Manager `ENDSESSION_CLOSEAPP`:** unchanged full cleanup (`Shell_NotifyIcon(NIM_DELETE)`, `close_settings_window()`, and `QuickAccessHost::stop()`). - **Full OS shutdown, sign-out, or restart:** posts `WM_QUIT` without waiting on child processes the OS is already reaping in parallel. ### Scope and follow-up This PR intentionally fixes the highest-volume contributor: the always-on runner. Native module processes with their own windows/message loops require module-specific review before adopting the pattern; that inventory and rollout is tracked in #49539. ### Why not centralize handling inside `run_message_loop`? `WM_QUERYENDSESSION` / `WM_ENDSESSION` invoke the WndProc directly during `GetMessage`; they do not appear as a `MSG` returned to the loop. Handling must therefore live in, or be called from, each relevant WndProc. ## Tests 8 focused tests in `src/common/UnitTests-CommonUtils/Window.Tests.cpp`: | Test | Guards | |---|---| | `HandleStatelessSessionEndMessage_QueryEndSession_AllowsShutdown` | `WM_QUERYENDSESSION` returns `TRUE`. | | `HandleStatelessSessionEndMessage_EndSessionCancelled_DoesNotTearDown` | `WM_ENDSESSION(FALSE)` does not destroy the window. | | `HandleStatelessSessionEndMessage_EndSessionConfirmed_TearsDownAndExitsLoop` | `WM_ENDSESSION(TRUE)` destroys the window and exits before the longer timer fallback. | | `HandleStatelessSessionEndMessage_UnrelatedMessage_NotHandled` | Unrelated messages fall through untouched. | | `HandleStatelessSessionEndMessage_EndSessionConfirmed_SignalsSystemSessionEnding` | A full session end enables the no-wait teardown path. | | `HandleStatelessSessionEndMessage_CloseApp_DoesNotSignalSystemSessionEnding` | Restart Manager closes the window while retaining normal child cleanup. | | `HandleStatelessSessionEndMessage_EndSessionCancelled_DoesNotSignalSystemSessionEnding` | Cancelled shutdown does not flag teardown. | | `HandleStatelessSessionEndMessage_QueryEndSession_DoesNotSignalSystemSessionEnding` | The query phase does not flag teardown. | **Build:** `runner.vcxproj` and `UnitTests-CommonUtils.vcxproj` build clean (`x64|Release`). The 8 focused tests pass. ## Manual validation 1. Build PowerToys and start the runner. 2. Initiate a sign-off (`logoff`) or restart. 3. Confirm Event Viewer (`Windows Logs → Application`) shows no `Application Hang` event for `PowerToys.exe`. 4. Right-click tray → Exit: confirm Settings.exe and the Quick Access host shut down gracefully and no ghost tray icon remains. (#48378 additionally captured real logoff/restart runs showing `WM_ENDSESSION → WM_DESTROY` completing in 1–8 ms with no hang events—the same full-session path used here.) ## Quality checklist - [x] Linked work item: AB#55588441 - [x] Module follow-up: #49539 - [x] Cross-references #41260; supersedes #48378 - [x] Unit tests (8 in `Window.Tests.cpp`) - [x] No new binaries - [x] Localization: no end-user strings changed - [x] Shared helper documents its stateless contract Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8d70b986-081a-43dd-bbfd-7e6351baef7agithub.com-microsoft-PowerToys · d127511c · 2026-07-29
- 0.4ETV[Quick Accent] Isolate press-and-hold activation (#49701) ## Summary of the Pull Request Makes the **Press and hold the letter** activation method exclusive. Pressing a legacy trigger key (Space or either arrow) before the hold threshold now cancels that owner-letter gesture and passes the trigger through normally, instead of allowing the already-scheduled picker to appear later. Typing any different supported physical letter during the gesture also cancels it, preventing that intervening character from being replaced when the owner letter is released. Space and arrow navigation remains available after a genuine hold activation reaches its threshold. ## PR Checklist - [ ] Closes: N/A - [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: N/A ## Detailed Description of the Pull Request / Additional comments The native keyboard listener previously armed press-and-hold on owner-letter key-down and immediately queued a delayed managed render. Although pre-threshold Space/arrows were excluded from native trigger-key activation, they did not invalidate that pending render. Holding the owner letter after pressing Space therefore still displayed the picker and made both invocation systems feel enabled. This change adds an explicit native-to-managed cancellation event and a generation-based managed display state: - In `PressAndHold`, Space or either arrow before the snapshotted hold threshold cancels the current gesture and passes through without input injection. - Space/arrows at or after the threshold retain their intended picker navigation behavior. - Any different physical letter in Quick Accent's supported key set cancels the owner gesture before passing through, even when that letter has no mapping in the selected language. - Owner repeats and owner key-up are handled from active physical ownership rather than current language eligibility, so live language changes cannot leave stale state. - Activation mode, input time, and hold duration are atomically published and snapshotted once per owner gesture. The native listener passes the same delay snapshot to managed scheduling, so live settings changes apply to the next gesture instead of desynchronizing native interaction from picker visibility. - Character data is prepared before native navigation can become interactive, preventing accepted navigation from being dropped. - Legacy Space/arrow/Both acquisition behavior is preserved. The low-level hook has no clean deterministic native unit-test seam because its private handlers depend on Win32 keyboard state. Managed regression coverage exercises delayed-display cancellation, re-arming, generation invalidation, and delay snapshot preservation. ## Validation Steps Performed - Built `src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj` in `Debug|x64`, covering the native WinRT projection and managed Core/UI. - Built `src/modules/poweraccent/PowerAccentKeyboardService/PowerAccentKeyboardService.vcxproj` in `Debug|x64`. - Built and ran all `PowerAccent.Core.UnitTests`: **35 passed, 0 failed**. - Ran `git diff --check`. - Performed focused code reviews of pre-threshold trigger cancellation, intervening mapped/unmapped letters, live mode/duration snapshots, language changes, owner key-up balance, and post-threshold navigation. --------- Copilot-Session: cbd8418a-64cb-4c6c-8653-2f3f0a6ceb9egithub.com-microsoft-PowerToys · 9fcb8faa · 2026-08-07
- 0.4ETV[Always On Top] Render a solid border frame (#49698) <!-- 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 the Always On Top frame appearing mottled or translucent even when frame opacity is set to 100%. <!-- 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 - [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 The frame helper window is intentionally placed behind the tracked window. The previous Direct2D rendering used a centered stroke, so the tracked window occluded the stroke's inner half. With per-primitive antialiasing enabled, partial-coverage pixels became disproportionately visible in the remaining thin outer half, making a fully opaque frame look mottled. This change replaces the centered stroke with a filled, even-odd outer/inner geometry ring. It preserves configured opacity, the transparent interior, DPI-scaled frame thickness and corner radius, smooth rounded corners, and target-window occlusion while limiting antialiasing to the ring's actual contours. It also recreates render-target-bound brush resources when the HWND render target is recreated, clears stale resources on `D2DERR_RECREATE_TARGET`, and redraws when rectangle or corner geometry changes. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed - Built `src\modules\alwaysontop\AlwaysOnTop\AlwaysOnTop.vcxproj` for Debug x64 successfully. - Ran the freshly built `PowerToys.AlwaysOnTop.exe` with frame opacity 100%, thickness 4, and rounded corners enabled. - Pinned a controlled Win32 window and confirmed it received `WS_EX_TOPMOST`. - Confirmed the module created an `AlwaysOnTop_Border` HWND sized 946x627 behind the 960x630 target window. - Restored the temporary setting change and stopped the test processes. Residual limitation: the RDP input desktop was detached, so composed-screen pixel capture was unavailable. `winapp` could capture the layered border HWND only by flattening transparency to black, which is not trustworthy visual pixel evidence. An interactive-desktop visual check is still recommended. Copilot-Session: c2697877-8736-4e8d-add3-06ed2cec15b9github.com-microsoft-PowerToys · ddeb7f1b · 2026-08-07
- 0.4ETV[FancyZones] Harden three shutdown races in WorkArea / ZonesOverlay / OnThreadExecutor (#48473) ## Summary Four small shutdown-/teardown-race fixes in FancyZones that I spotted while reading through the work-area and overlay teardown sequence for an unrelated review. Each one is independently safe in the happy path, but in combination they can crash the FancyZones host process during display changes, monitor configuration changes, a settings toggle mid-drag, or normal exit. ## Issues fixed ### 1. `~ZonesOverlay` joins a non-joinable thread when the constructor early-returns `ZonesOverlay::ZonesOverlay` can return early in two places — if `GetClientRect` fails or if `CreateHwndRenderTarget` returns a failure HRESULT (both reachable in the wild during a display-driver TDR or when a monitor is disconnected mid-init). When that happens, `m_renderThread` is never started and stays default-constructed. The destructor unconditionally calls `m_renderThread.join()`, which on a non-joinable thread is undefined behavior (MSVC throws `std::system_error`); thrown from an implicit-noexcept destructor it calls `std::terminate()`. Fix: guard the wake-up-and-join sequence with `if (m_renderThread.joinable())`. ### 2. `~WorkArea` returns the HWND to the window pool before the renderer is torn down `WorkArea`'s explicit destructor body calls `windowPool.FreeZonesOverlayWindow(m_window)` first, and only afterwards does implicit member destruction run `~ZonesOverlay` (which joins the render thread). Between those two steps the HWND is back in the pool and immediately eligible for reuse by the next `NewZonesOverlayWindow` call, while the still-alive render thread is using `m_renderTarget` to draw into it. If the pool hands the same HWND to a freshly-built `ZonesOverlay`, two render targets target the same window concurrently. Fix: reset `m_zonesOverlay` (which joins the render thread) before returning the window to the pool. ### 3. `~OnThreadExecutor` writes `_shutdown_request` outside the mutex The destructor mutates the shutdown flag without holding `_task_mutex`, then calls `_task_cv.notify_one()`. The worker checks the same flag inside `_task_cv.wait(lock, predicate)`. The atomic does make the value visible eventually, but if the notify lands in the narrow window where the worker has just evaluated the predicate as false and is about to atomically release the lock and sleep, the wakeup can be missed and `_worker_thread.join()` hangs. Fix: take `_task_mutex` around the `_shutdown_request = true` write so it pairs correctly with the `cv.wait`. ### 4. `WindowMouseSnap` keeps a dangling `WorkArea*` across `WorkAreaConfiguration::Clear()` `FancyZones::UpdateWorkAreas()` rebuilds `m_workAreaConfiguration` whenever monitor state changes mid-session, and the `SpanZonesAcrossMonitors` settings toggle hits the same `Clear()`. If the user is mid-drag at the moment one of these runs, the `WindowMouseSnap` instance owned by `FancyZones` is still holding both a `const` reference to the map being cleared (`m_activeWorkAreas`) and a raw `WorkArea*` into one of the entries that's about to be destroyed (`m_currentWorkArea`). The next `WM_MOUSEMOVE` -> `MoveSizeUpdate()` then dereferences a freed pointer. `WindowMouseSnap`'s destructor only resets window transparency, so relying on it doesn't help; the snapper has to be torn down explicitly. Fix: call `FancyZones::MoveSizeEnd()` (which already tears down the snapper cleanly and is a no-op when the snapper is null) before each `m_workAreaConfiguration.Clear()` call on these paths. ## Risk Low. All four changes are localized to teardown / reconfiguration paths and only tighten existing destruction sequences — the steady-state behavior of `ZonesOverlay::Render`/`Show`/`Hide`, the work-area public API, `OnThreadExecutor::submit`/`cancel`, and `WindowMouseSnap` drag handling is unchanged. The `WorkArea` reordering is the most behavioral change; it now guarantees the render thread has stopped using the HWND before the pool can recycle it, which is what the existing implicit-member-destruction order already implied but couldn't enforce given the explicit destructor body. ## 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, but the C++ FancyZones modules involved are unchanged in their public surface and are exercised by existing unit tests in `FancyZonesTests` for the WorkArea code paths. --- ADO: https://microsoft.visualstudio.com/OS/_workitems/edit/54653316/ --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · ae9f241e · 2026-07-01
- 0.3ETVAdd press-and-hold activation mode to Quick Accent (#48937) ## Summary of the Pull Request Adds an opt-in **press-and-hold** activation mode to Quick Accent, like iOS / macOS: hold an accent-capable letter (e.g. `a`) and after a short, configurable delay the accent picker opens automatically — no separate trigger key (Space/arrows) required. This is purely additive. The existing trigger-key modes (`Left/Right arrow`, `Space`, `Both`) and all serialized settings values are unchanged. https://github.com/user-attachments/assets/faec298c-e42c-4fd1-84bd-6e74d1b481a0 ### What it does - Holding a letter types the base letter immediately, then arms the picker. After the **Hold duration** (default **500 ms**) the toolbar appears. - Navigate the options with the arrow keys / Space, then **release the letter** to insert the selected accent (it replaces the base letter). - A quick tap types just the letter. Holding and releasing without selecting leaves the base letter as-is. - `Ctrl` / `Alt` / `AltGr` / `Win` + letter combinations are left untouched, so shortcuts like `Ctrl+A` still work. ## PR Checklist - [ ] **Closes:** N/A — feature enhancement (happy to link a tracking issue if one is preferred) - [x] **Communication:** Discussed direction with maintainers; coordinated with #48891 (see below) - [ ] **Tests:** No automated tests added — the activation decision lives in the C++ low-level keyboard hook and isn't reachable from the existing managed unit-test project. Validated manually (steps below). Open to guidance on the preferred test surface. - [x] **Localization:** All new end-user strings are in `Settings.UI/Strings/en-us/Resources.resw` with translator comments. - [x] **Dev docs:** `doc/devdocs/modules/quickaccent.md` updated with the new mode. - [x] **New binaries:** None. - [x] **Documentation updated:** Dev docs updated; public Learn docs can follow once shipped. ## Detailed Description of the Pull Request / Additional comments - **`PowerAccentKeyboardService` (C++ hook):** - Append `PressAndHold` to the internal `PowerAccentActivationKey` enum (value `3`, appended to preserve serialized `0/1/2`). - Add a `holdDuration` setting and `UpdateHoldDuration(Int32)` to the WinRT projection (`.idl`). - In `OnKeyDown`, arm the picker on the held letter itself; the base letter still types on first press and auto-repeat is swallowed (reuses the existing `m_toolbarVisible` repeat guard from #36853). - In `OnKeyUp`, use the hold duration as the minimum-hold release threshold for this mode (trigger modes keep using `inputTime`). - Modifier guard: Ctrl/Alt/AltGr/Win do not arm the mode. - **Settings model (`Settings.UI.Library`):** append `PressAndHold` to `PowerAccentActivationKey`; add `hold_duration_ms` (`IntProperty`, default 500). Existing `settings.json` without the field falls back to the 500 ms default. - **`PowerAccent.Core`:** read and forward the hold duration to the hook, and use it as the popup delay when `PressAndHold` is active. - **Settings UI:** add the **"Press and hold the letter"** activation option and a **"Hold duration (ms)"** control that is shown only when that mode is selected. ### Enum sync note `PowerAccentActivationKey` exists in both the C++ hook and the managed settings library and is kept in sync by integer value. `PressAndHold` was **appended** (never reordered) so existing serialized settings (`0/1/2`) keep their meaning. ### Coordination with #48891 (Quick Accent WinUI migration) This lands as its own atomic change on `main`. The overlap with the in-progress WinUI migration (#48891) is tiny: only `PowerAccent.cs`'s mode-aware popup delay (a single `Task.Delay` line). The C++ hook, settings enum/model, and Settings UI are not touched by #48891, so it can rebase onto this with minimal effort. ## Validation Steps Performed - Built the full chain in `Debug|x64`: - `PowerAccent.UI.csproj` → rebuilds the C++ `PowerAccentKeyboardService` projection (incl. `UpdateHoldDuration`) + `PowerAccent.Core` + `Settings.UI.Library`. **0 errors.** - `PowerToys.Settings.csproj` → Settings UI XAML / ViewModel / `.resw` (XamlIndexBuilder search index regenerated). **0 errors.** - Manual trial of the running module (`PowerToys.PowerAccent.exe`) with `activation_key = 3`: - Hold `a` → base letter types immediately; picker opens after ~500 ms; arrows/Space navigate; releasing inserts the accent (replacing the base letter). - Quick tap → base letter only. Hold + release without selecting → base letter remains. - `Ctrl+A` / `Alt`+letter unaffected. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · af45c3ec · 2026-07-03
- 0.3ETV[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>github.com-microsoft-PowerToys · dd26d865 · 2026-06-23
- 0.3ETV[PowerAccent] Fix injection hygiene and reset state on hide (#48572) ## Summary Keeps Quick Accent-injected keys from retriggering centralized shortcuts and clears native keyboard-listener state whenever the toolbar closes. ## What this changes - Tags backspace, Unicode, and arrow `SendInput` events with `dwExtraInfo = 0x110`, mirroring `CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG`. - Uses the existing `SendArrowKey(bool)` implementation as the single arrow-injection path, preserving `KEYEVENTF_EXTENDEDKEY` on key-down and key-up. - Checks the number of events sent by every `SendInput` call and logs incomplete sends. - Adds `ForceReset()` to the keyboard service WinRT API and invokes it from the core hide path immediately before `OnChangeDisplay(false)`. - Keeps listener state non-atomic because the low-level hook is installed on the WinUI thread and its callbacks execute on that same thread, as documented by `MainWindow.RunOnUiThread`. ## Testing - Built `PowerAccent.Core.csproj` in Release x64, including `PowerAccentKeyboardService`. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 122a9176-ce18-437c-8af4-c39f83fb2fa6github.com-microsoft-PowerToys · ffc839af · 2026-07-31
- 0.3ETVGrab 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>github.com-microsoft-PowerToys · 459bd56f · 2026-06-20
- 0.3ETV[PowerToys] Guard TitleBar windows against an empty window title (startup fault) (#49069) ## Summary Guard PowerToys' WinUI windows against an empty native window title, so the WinUI `TitleBar` control can't read an empty title during startup and fault the process. This fixes a class of bugs like https://github.com/microsoft/PowerToys/issues/48547 ## Background Spotted while reading through the Environment Variables `MainWindow` startup path. The WinUI `TitleBar` control (used with `ExtendsContentIntoTitleBar`) reads the owning window's `AppWindow.Title` during a deferred layout pass (`OnApplyTemplate` → `UpdateTitle`). When the native window title is empty at that instant, the windowing layer can fault while resolving the title and terminate the process during startup. The native title ends up empty in two ways: 1. The title is computed from `ResourceLoader.GetString(...)`, which returns an **empty string** (it doesn't throw) when the resource map can't be resolved at runtime. 2. The window sets `AppWindow.Title` only *later*, not before the title bar's first layout. ## Windows fixed Every PowerToys window that hosts the `TitleBar` control: | Window | Fix | |---|---| | Environment Variables | Non-empty fallback for the resource-based title | | Hosts | Non-empty fallback for the resource-based title | | File Locksmith | Non-empty fallback for the resource-based title | | Shortcut Guide | Non-empty fallback for the resource-based title | | Settings — shortcut-conflict window | Non-empty fallback for the resource-based title | | Registry Preview | Set `AppWindow.Title` to the app name in the constructor (previously only set later in `UpdateWindowTitle`) | | Keyboard Manager Editor | No change — already sets a hardcoded non-empty `Title` | ## Risk Very low. The only behavior change is that a previously-empty title becomes a non-empty fallback; the normal (resource-resolved) paths are unchanged. ## Validation Each affected project builds clean (`x64 | Release`): EnvironmentVariables, Hosts, FileLocksmithUI, ShortcutGuide.Ui, RegistryPreview, PowerToys.Settings. ## Related Root cause write-up (windowing/WinUI side): microsoft/microsoft-ui-xaml#11214. --- ADO: https://microsoft.visualstudio.com/DefaultCollection/OS/_workitems/edit/62685601/ --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · de485945 · 2026-07-02
- 0.2ETV[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>github.com-microsoft-PowerToys · 4771f15b · 2026-06-23
- 0.2ETV[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>github.com-microsoft-PowerToys · ab494757 · 2026-06-18
- 0.1ETV[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>github.com-microsoft-PowerToys · 47335149 · 2026-06-24
- 0.1ETVAdd Runner C++ hotkey conflict unit test seed (#48352) Adds the C++ counterpart to #48346: a focused Runner native unit-test seed for core hotkey conflict behavior. Why this one: - Runner is core infrastructure rather than another C# module test. - It adds the missing native C++ test-project path for Runner. - The seed test is deterministic and covers in-app hotkey conflict detection. - It keeps the active rollout to two PRs: one C# module-services PR (#48346) and one C++ core/runner PR. Validation: - `tools\build\build.ps1 -Platform x64 -Configuration Debug -Path src\runner\UnitTests` - `vstest.console.exe x64\Debug\tests\Runner\Runner.UnitTests.dll /Tests:HasConflict_TwoModulesSameHotkey_InAppConflict` → 1 passed --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 021ca6ae · 2026-07-28