moooyo
90d · built 2026-08-09
90-day totals
- Commits
- 32
- Grow
- 21.3
- Maintenance
- 5.4
- Fixes
- 2.7
- Total ETV
- 29.4
Where this dev ranks
Percentile against the global top-100 leaderboard (all-time totals).
- By commits
- Top 100 %
- By Growth share
- Top 10 %
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).
↑+100.0 %
vs 7 prior
↑+52.9 pp
recent vs prior
↓-5.9 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.
- 11.5ETVfeat(powerdisplay): add CLI for monitor control (#48632) ## Summary of the Pull Request Adds `PowerToys.PowerDisplay.Cli.exe`, a scriptable interface for controlling monitors through the running PowerDisplay process. It supports `list`, `get`, `set`, `up`, `down`, `capabilities`, `profiles`, and `apply-profile`. The CLI communicates over an authenticated, per-session named pipe; PowerDisplay remains responsible for DDC/CI and WMI access. ## PR Checklist - [x] Closes: #48713 - [x] **Communication:** Discussed with core contributors - [x] **Tests:** Added/updated and all pass - [x] **Localization:** Core errors are localizable; some help and output text remains English-only - [ ] **Dev docs:** N/A; built-in CLI help is the command reference - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetupVNext/Resources.wxs) - [x] **YML for CI pipeline:** N/A; test assemblies are auto-discovered - [x] **YML for signed pipeline:** N/A; signing is driven by `ESRPSigning_core.json` - [ ] **Documentation updated:** N/A ## Detailed Description of the Pull Request / Additional comments - Adds an AOT-compatible CLI and shared request/response contracts. - Uses a secured named-pipe server in PowerDisplay, with stable exit codes and a bounded request timeout. - Supports saved profiles by their existing stable profile IDs. - Adds solution, signing, installer, and unit-test project integration. ## Validation Steps Performed - PowerDisplay Lib, Contracts, CLI, and IPC unit-test suites pass. - Native AOT publish completes without analyzer warnings. - Manually validated the CLI on two DDC/CI monitors, including the PowerDisplay-unavailable path. - Rebuilt the PowerDisplay GUI after the shared-library changes. --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 5c9c93d5 · 2026-07-16
- 2.8ETV[PowerDisplay] Add stable profile IDs (#49175) ## Summary of the Pull Request Gives every saved PowerDisplay profile a stable, auto-incrementing integer ID and makes the app address profiles by that ID instead of by name. Duplicate profile names are allowed, renames preserve identity, and LightSwitch stores stable profile references. > Split out of the PowerDisplay CLI branch (#48632). CLI-specific contracts and commands remain in that stacked PR. ## PR Checklist - [x] **Closes:** N/A - split from #48632. - [x] **Communication:** Discussed with core contributors. - [x] **Tests:** Added and passing in `PowerDisplay.Lib.UnitTests`. - [x] **Localization:** The composed profile label uses a shared localized format resource. - [x] **New binaries:** None. - [x] **Documentation updated:** `doc/devdocs/modules/powerdisplay/design.md`. ## Implementation ### Profile model and persistence - `PowerDisplayProfile.Id` is the stable JSON `id`; `0` means unassigned. - `PowerDisplayProfiles.NextId` is monotonic and IDs are never reused. - `SetProfile` assigns IDs to new profiles and replaces existing profiles by ID. - Duplicate names are supported; name lookup remains only for migration of legacy references. - `ProfileStore` serializes cross-process load/modify/save operations with a named mutex and atomically replaces `profiles.json`. - Production callers use asynchronous `ProfileHelper` APIs. ### Migration and application - Initial PowerDisplay discovery assigns missing profile IDs and migrates legacy monitor IDs. - LightSwitch legacy name references are reconciled to IDs and written back to the current typed settings schema. - Native LightSwitch publishes pure light/dark theme events; PowerDisplay exclusively validates profile enablement and stable IDs. - Settings UI and Named Pipe ApplyProfile actions send invariant positive profile IDs. - PowerDisplay validates the ID, loads the current profile, and applies its monitor settings. ### Settings UI - Create, edit, apply, and delete operations use stable IDs. - LightSwitch selectors store profile IDs and keep legacy name fields only for migration. - Profile lists use a localized name-and-ID label so duplicate names remain distinguishable. ## Accepted Trade-offs - Profile ID migration remains dependent on the initial monitor discovery; a failed or delayed discovery can temporarily hide legacy ID-less profiles. - The one-time PowerDisplay LightSwitch migration rewrites the complete current typed settings object and does not add a new cross-process settings transaction. ## Validation - Built the affected x64 Debug projects with the repository build scripts. - `PowerDisplay.Lib.UnitTests`: 186 passed, 0 failed. --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 2b8e6247 · 2026-07-16
- 2.0ETVPowerDisplay: Fall back to persisted VCP values when a monitor read fails (#49445) ## Summary of the Pull Request On a monitor whose DDC/CI engine answers intermittently, every discovery pass starts from nothing. A panel that reported its brightness a minute ago can lose that control — or drop out of the flyout entirely — because one pass happened to fail. This persists every range-valid VCP value read off a monitor, keyed by its canonical DevicePath. In Maximum compatibility mode a later discovery falls back to that value when the hardware will not answer. Scope is intermittent failure, not permanent failure: the cache can only replay a value the hardware answered at least once, so a panel that never reads a code successfully sees no change. This partially addresses #49342. ## PR Checklist - [x] Closes: #49342 - [ ] **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 — this PR adds none - [ ] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places — none added, so no signing JSON, installer WXS or CI YML change is required - [ ] **Documentation updated** ## Detailed Description of the Pull Request / Additional comments ### What is stored `MonitorStateManager` implements `IKnownGoodVcpStore`, so the cache rides in the existing `monitor_state.json` next to the user's saved brightness rather than in a new file. Each entry is a `KnownGoodVcpFeature`: code, current, maximum, and when it was last read. Only range-valid observations are stored, so the common `current=0 / max=0` garbage reply never enters — it fails `VcpFeatureValue.IsValid`. Writes are not gated on Maximum compatibility mode, only reads are. A monitor that reads cleanly today can start failing after a cable or dock change, and a lazily populated cache would be empty on exactly the first pass that needs it. ### How a cached value is used `VcpDiscoveryEvidence.Reconcile` gains the cache as a third source alongside the parsed capabilities string and this pass's probe: | this pass | cache | result | | --- | --- | --- | | read succeeded | — | live value wins, cache refreshed | | replied, range unusable | hit | cached value applied, `MonitorReadFlags` left clear | | no reply | hit | cached value applied, `MonitorReadFlags` left clear | | code never probed (caps parsed) | hit | value applied only after one live read is attempted | The last row matters: on the caps-parsed path nothing has confirmed the cached value this pass, so the hardware is asked first. On the probe path it has already been asked, and re-reading would be pure I2C noise. `MonitorReadFlags` stays clear for anything the hardware did not answer, so a cached value never masquerades as an observation — which #49577 depends on, since it made the restore path write whenever the flag is unset. One consequence is worth naming: the flyout draws a slider at the cached position while `powerdisplay get` reports that setting as unknown, because `MonitorDtoProjector` gates on `supported && read`. ### Keeping the cache current `RefreshKnownGoodAfterWrite` restamps an entry after a successful `SetVCPFeature`, so a slider move cannot leave the cache holding the pre-write value. It refreshes only an entry a real read established, and only when the value was scaled against the maximum that entry holds — a monitor whose discovery read failed still carries a placeholder max, and writing that back would mis-scale every later write. `RemoveKnownGoodFeatures` clears the cache for monitors a settings reconciliation observably dropped, leaving the user's saved values alone. Cleanup is driven by an observed drop, never by absence from the rebuilt list: a missing or corrupt `settings.json` yields a defaults object indistinguishable from a real one, and pruning by absence would wipe every monitor not connected at that instant. A re-observation that changes nothing refreshes the in-memory timestamp but does not mark the file dirty, so a discovery pass no longer rewrites `monitor_state.json` for a moved timestamp alone. ## Validation Steps Performed - `PowerDisplay.Lib`, `PowerDisplay.Lib.UnitTests` and `PowerDisplay` built for x64 Debug with VS MSBuild — 0 errors, 0 warnings; `PowerDisplay.Lib.UnitTests.dll` under `vstest.console.exe`: **301 passed, 0 failed** - **Affected-hardware validation on the AOC Q27G3XMN is still pending.** That monitor, or an equivalent controllable DDC/CI setup, was not available locally. The paths this PR changes are reachable only on hardware whose capabilities string is unusable or whose VCP reads fail intermittently, so this is the main outstanding risk. --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Copilot-Session: 6ea38c04-6f68-4c42-91d9-8a03b49bdd81github.com-microsoft-PowerToys · ba2e89c4 · 2026-08-03
- 1.9ETVPowerDisplay: Adjust brightness by scrolling over the tray icon (#49446) ## Summary of the Pull Request Scrolling the mouse wheel over the Power Display tray icon adjusts brightness, without opening the flyout. - New **Tray icon mouse wheel** setting: `Off` / `Primary display` / `All displays`, defaulting to **`Off`**. It is scoped to the tray icon — the flyout sliders accept wheel input regardless, as they always have. The existing **Mouse wheel increment** setting supplies the per-notch step. - **Off by default.** The gesture consumes a wheel notch that would otherwise reach the window under the pointer, and acting on it installs a system-wide `WH_MOUSE_LL` hook. Neither is something an existing installation should acquire silently on upgrade. With the setting `Off` no hook is ever installed and no notch is ever consumed, so this PR changes no existing behaviour until the user opts in: 1958 insertions, 2 deletions, and both deletions are refactors of lines this feature reuses. - **No feedback UI.** Brightness is self-evidencing — you scroll and the screen changes — so the display itself is the feedback. The notification icon is untouched: same tooltip, same text, same legacy notification-icon protocol. ## PR Checklist - [x] Closes: #49410 - [ ] **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 - [ ] **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 No new binaries or projects — everything lands in existing assemblies. Communication is unchecked because #49410 is still Needs-Triage. ## Detailed Description of the Pull Request / Additional comments ### Why a low-level hook The Shell does not forward `WM_MOUSEWHEEL` to a notification icon's callback window under any `NOTIFYICON_VERSION`, and a click-through overlay placed over the icon cannot receive wheel input either. `TrayIconMouseWheelListener` therefore installs a `WH_MOUSE_LL` hook — but only transiently, and only when it will act on the result: - Nothing is installed at all while the setting is `Off`, which is the default. - Installed in `EnsureHook()` when the UI thread confirms the pointer is inside the rectangle from `Shell_NotifyIconGetRect` **and** `CanAdjustBrightnessFromTrayWheel` says some monitor can accept a brightness write. - Removed in `DisarmCore()` as soon as either condition stops holding, the pointer leaves the rectangle, or the mode changes. - A notch is consumed (the hook proc returns non-zero) only while armed and only for points inside the armed rectangle, so a wheel event Power Display will not act on still reaches the window under the cursor. The hook runs on a dedicated background thread with its own message loop; the proc itself only enqueues a sample and posts a drain request. Deltas are marshalled to the UI thread in batches, and `WheelDeltaAccumulator` folds high-resolution deltas (precision wheels, touchpads) into whole notches. Each sample carries the hover generation it was captured under, so samples from a hover the UI thread has already retired are discarded rather than applied late. ### Hover detection The Shell sends `WM_MOUSEMOVE` to the icon's callback window while the pointer is over it. `TrayIconService.HandleTrayMouseMove` resolves the rectangle with `Shell_NotifyIconGetRect` and caches it for a second, because that message repeats for every pixel of travel. `TrayIconService` gains nothing else: no protocol change, no new hover UI, no polling. The rest of the file — and `MainWindow.xaml` — is untouched. ### Linked brightness While linked brightness is on, a notch has to move the whole group, so it goes through `MainViewModel.LinkedBrightness` rather than the individual monitor setters. The new master value is taken from the planner's value for the monitor the wheel named, **not** from the current master. The master is positional only — `SeedInitialLinkedBrightness` takes it from the lowest-numbered linked monitor and never writes hardware, and every monitor-list rebuild re-seeds it — so it can sit arbitrarily far from the monitor the wheel is aimed at. Stepping it relative to itself would apply a wrong-sized or wrong-signed change, and a master already clamped at 0/100 would swallow the notch while writing nothing at all. The setting description calls out that linked brightness widens the scope, so `Primary display` is not literally a single display while it is on. ### What is deliberately not here An earlier revision of this PR showed the target and percentage in a custom overlay as you scrolled. Doing that meant the standard Shell tooltip would not do (it cannot be shown on demand), which meant an own window, which meant suppressing the Shell tooltip so the two did not collide, which meant `NOTIFYICON_VERSION_4`, which changed the callback packing and made the app responsible for all hover text — including for keyboard and touch users, who never reach a cursor-anchored overlay and would have been left with no visible tooltip at all. That chain was about half the diff, for a readout that adds little on top of watching the screen change. It is gone. If a readout is wanted later it can be argued on its own merits, separately from this feature. The same revision also gated the flyout sliders on this setting. That bundled two unrelated things behind one switch — turning off tray scrolling would also have stopped the contrast and volume sliders responding to the wheel — so the setting is now scoped to the tray icon and named accordingly. An earlier revision also routed the tray **Exit** action through `Shutdown()`. That fixes a pre-existing teardown leak which has nothing to do with this feature, so it now lives in #49580 and is out of scope here. This branch does not depend on it: the hook thread is a background thread and the process is ending either way. ## Validation Steps Performed - Unit tests: `PowerDisplay.Lib.UnitTests` 215 passed, `Settings.UI.UnitTests` 165 passed. - Builds: `PowerDisplay` and Settings UI, x64 Debug, no warnings. - Automated coverage is in `PowerDisplay.Lib.UnitTests`: target selection per mode, wheel accumulation including negative deltas, partial notches and direction reversal, half-open rectangle containment, and settings serialization and round-trip for the new mode, including that a settings file predating the feature loads as `Off`. `Settings.UI.UnitTests` covers the view-model index mapping and pins the enum values to the ComboBox item order. - The Win32 glue in `TrayIconService` and `TrayIconMouseWheelListener` is not unit tested. Manual passes performed: scrolling over the icon in both modes, the icon parked in the notification overflow, high-resolution wheel input, brightness boundaries, live monitor refresh while hovering, tray icon hidden and re-enabled, Explorer restart, the context menu and left-click, `Off` stopping tray scrolling while the flyout sliders keep working, and confirming a notch that Power Display will not act on still reaches the window under the cursor. Not verified, needing hardware this branch has not been run on: - Multiple taskbars, where the tray icon is on a secondary display and `Primary display` mode adjusts a monitor the user may not be looking at. - Mixed-DPI setups, for the `Shell_NotifyIconGetRect` rectangle and the hook's physical-pixel hit test. --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Copilot-Session: 5d7f36fe-d175-4aa9-a3c7-b370d952d1d3github.com-microsoft-PowerToys · 8f634024 · 2026-07-31
- 1.2ETV[PowerDisplay] Pre-classify internal/external displays at discovery (#47740) <!-- 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 an explicit Phase 0 classification step in `MonitorManager` that uses `DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY` (from `QueryDisplayConfig`) to label every connected display as **internal** (built-in) or **external** before any controller runs. Each controller is then dispatched a strictly-scoped target list: - **WMI controller** → only internal displays - **DDC/CI controller** → only external displays Two practical wins: 1. **Performance** — DDC/CI's ~4-second I2C capabilities probe (per monitor) is now skipped entirely for internal laptop panels, which never respond to DDC/CI in the first place. On a typical laptop with one built-in panel + one external monitor, discovery is noticeably faster. 2. **Layering** — `WmiController` no longer reaches into the `Drivers/DDC/` namespace to call `DdcCiNative.GetAllMonitorDisplayInfo()`. Both controllers receive their input from `MonitorManager` via a single `QueryDisplayConfig` call. Strict classification is enforced: a display classified as internal but not returned by `WmiMonitorBrightness` is dropped + logged (Warning), with **no fallback to DDC/CI**. This is a deliberate design choice — the spec discusses the trade-off in detail. Adds a Phase 0 classification log (Info level) so misclassifications are diagnosable from logs alone: ``` [DisplayClassification] Found 2 displays: [Path 1] \\.\DISPLAY1 / "Built-in display": OutputTechnology=0x80000000 → Internal [Path 2] \\.\DISPLAY2 / "Dell U2723QE": OutputTechnology=10 → External [DisplayClassification] Summary: 1 internal, 1 external ``` The classification rule (in `DisplayClassifier.IsInternal`) is deliberately conservative — misclassifying an external display as internal would silently drop it from DDC/CI discovery with no fallback, so we err on the side of external: - **Internal**: the bare `INTERNAL` flag (`0x80000000`) alone, the `INTERNAL` flag combined with a documented embedded subtype (`DISPLAYPORT_EMBEDDED` 11 or `UDI_EMBEDDED` 13), or one of those embedded subtypes on its own - **External**: everything else, including the `INTERNAL` flag combined with an undocumented subtype (HDMI, DP_EXTERNAL, MIRACAST, etc.) - LVDS (6) is intentionally **not** classified internal — the [Microsoft docs](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ne-wingdi-displayconfig_video_output_technology) describe it only as a connector type, not as an internal-display marker <!-- 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: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>github.com-microsoft-PowerToys · 3796b244 · 2026-05-12
- 1.0ETVPowerDisplay: Pace and retry the maximum-compatibility VCP probe (#49579) ## Summary of the Pull Request In Maximum compatibility mode, when a monitor's capabilities string is missing or unparsable, discovery falls back to probing each continuous VCP code directly. That probe issues **one** `GetVCPFeatureAndVCPFeatureReply` per code, back to back, and treats any failure as final. On a panel whose DDC/CI engine answers intermittently, a single transient I2C fault permanently drops that control for the whole discovery pass — and if every code happens to fault, the monitor disappears from the flyout entirely. This replaces the probe with `VcpFeatureProbeService`: - **paced** — 100 ms between transactions, instead of hammering the I2C bus back to back - **retried** — up to 3 attempts, but only for failures another attempt can plausibly get past - **classified** — `DdcErrorClassifier` decides what "transient" means, so the retry budget is not burned on a definitive `DDCCI_VCP_NOT_SUPPORTED` or on a dead physical-monitor handle - **aborted early** — a handle-class error stops the remaining codes rather than issuing more requests against a handle already known to be invalid Extracted from #49445, which bundles this with a persisted discovery cache and a discovery restructure it does not depend on. This piece stands alone and addresses one of the root causes in #49342 by itself. ## PR Checklist - [ ] Closes: #xxx — partially addresses #49342; the remaining causes are in #49445 - [ ] **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 — this PR adds none - [ ] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places — none added; no new project, so no signing JSON, installer WXS or CI YML change is required - [ ] **Documentation updated** ## Detailed Description of the Pull Request / Additional comments ### What is and is not retried `DdcErrorClassifier` names the DDC/CI error codes after `winerror.h` and splits them into two sets. `DdcErrorClassifierTests` pins both the membership of each set **and** the numeric value of every constant against `winerror.h`, so a typo cannot move production and tests together and leave the suite green. Retried — framing, arbitration and timing faults on the I2C bus: `I2C_ERROR_TRANSMITTING_DATA`, `I2C_ERROR_RECEIVING_DATA`, `DDCCI_INVALID_DATA`, `MCA_INTERNAL_ERROR`, `DDCCI_INVALID_MESSAGE_COMMAND`, `DDCCI_INVALID_MESSAGE_LENGTH`, `DDCCI_INVALID_MESSAGE_CHECKSUM`, `DDCCI_CURRENT_CURRENT_VALUE_GREATER_THAN_MAXIMUM_VALUE`, `ERROR_TIMEOUT`. Not retried, each for a stated reason recorded on the predicate: `DDCCI_VCP_NOT_SUPPORTED` is the device's final answer; `I2C_NOT_SUPPORTED` and `I2C_DEVICE_DOES_NOT_EXIST` are permanent bus-level facts; `MCA_INVALID_CAPABILITIES_STRING` belongs to the capabilities path, not to a VCP read; and the two handle-class codes must abort rather than retry. ### Behaviour preserved `FetchCapabilitiesWithFallbackAsync` keeps its signature and still returns `(string, VcpCapabilities?)`, so nothing outside the probe changes. `BuildCapabilitiesFromProbe` synthesizes the same shape `DdcCiNative.ProbeSupportedVcpFeatures` used to, and decides membership the same way: a code counts as supported when the device *replied*, not when the value was usable. A reply proves the opcode is implemented even if the reported range cannot scale a percentage — an unimplemented code fails with `DDCCI_VCP_NOT_SUPPORTED` instead. The set of probed codes moves from a private array in `DdcCiNative` to `NativeConstants.ContinuousVcpCodes`, where the follow-up work in #49445 also needs it. ### Cost The probe only runs in Maximum compatibility mode, and only when the capabilities string is already unusable — so this adds no I2C traffic to a monitor that parses normally. For a monitor that does reach it, the worst case grows from 3 transactions to 9 plus 900 ms of pacing, and it is bounded: a definitive refusal stops after one attempt, and a handle-class error stops the whole probe. ### What is deliberately left out The probe's values are still discarded — `BuildMonitorFromPhysical` re-reads each code immediately afterwards. Reusing them needs a carrier for the observed value, which is `VcpDiscoveryEvidence` in #49445. `VcpFeatureProbeService` already returns everything that needs (`VcpProbeObservation` carries the value, the attempt count and the last error); this PR simply does not consume it yet. ## Validation Steps Performed - built `PowerDisplay.Lib.UnitTests` for x64 Debug with VS MSBuild — 0 errors, 0 warnings - ran `PowerDisplay.Lib.UnitTests.dll` with `vstest.console.exe`: **223 passed, 0 failed** (186 on `main` + 37 added here) - `VcpFeatureProbeServiceTests` drives the pacing, the retry budget, the transient/definitive split, cancellation before and during the inter-transaction delay, a throwing native read, and that reads run off the caller's thread — all through an injected reader and an injected delay, so no hardware is needed - no hardware validation performed: reaching this path needs a panel whose capabilities string is unusable **and** whose VCP reads fail intermittently --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>github.com-microsoft-PowerToys · bc2d09ab · 2026-07-30
- 1.0ETV[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>github.com-microsoft-PowerToys · 8e74eb2b · 2026-05-20
- 0.9ETV[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>github.com-microsoft-PowerToys · 32ad98a0 · 2026-06-22
- 0.9ETV[Quick Accent] Migrate UI to WinUI 3 (#48891) ## Summary of the Pull Request Migrates the **Quick Accent (PowerAccent)** module's UI from WPF (`System.Windows.*`) to **WinUI 3 (Windows App SDK)**, following the pattern used by other migrated modules (ImageResizer, PowerDisplay). The accent selector is now a self-contained WinUI 3 app (`PowerToys.PowerAccent.exe`) shipped under `WinUI3Apps`, and `PowerAccent.Core` is UI-framework-agnostic. demo: https://github.com/user-attachments/assets/400c33ee-0fc0-491e-841b-a546438edf91 ## PR Checklist - [x] Closes: #48889 - [x] **Communication:** Tracked task (#48889) agreed with core contributors - [x] **Tests:** Added/updated and all pass — new `PowerAccent.Core.UnitTests` (21 tests for the positioning / DPI math); existing `PowerAccent.Common.UnitTests` unaffected - [x] **Localization:** No new localizable end-user strings — new accessibility metadata uses non-localized `AutomationProperties.AutomationId`, and the window title is the brand name `"Quick Accent"` (literal, matching ColorPicker) - [x] **Dev docs:** Updated `doc/devdocs/modules/quickaccent.md` - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json): the `WinUI3Apps\` PowerAccent payloads are listed in `ESRPSigning_core.json` - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs): no manual `Product.wxs` entry — the self-contained `WinUI3Apps` output (exe + `.pri` + Windows App SDK runtime) is harvested by the `WinUI3ApplicationsFiles` glob (same as ImageResizer) - [x] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml): no change needed — `PowerAccent.Core.UnitTests` is discovered by the existing `**\*UnitTest*.dll` VSTest glob - [x] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml): covered by `ESRPSigning_core.json`; no `release.yml` change needed - [ ] **Documentation updated:** N/A — internal UI-framework migration with no user-facing behavior change ## Detailed Description of the Pull Request / Additional comments The migration spans three areas (tracked in #48889): **UI (WPF → WinUI 3)** - `PowerAccent.UI` is now a WinUI 3 app shell (custom `Program.Main`, `WindowsPackageType=None`, `WindowsAppSDKSelfContained=true`). - The accent selector is a non-activating `TransparentWindow` overlay shown with `SW_SHOWNA` (never steals focus). It is made always-on-top only while shown — the WinUIEx `WindowEx.IsAlwaysOnTop` property is toggled `true` on show / `false` on hide in `OnChangeDisplay` (matching the WPF original's `Topmost = isActive`), so the dormant, never-destroyed overlay does not pin a discrete GPU awake on hybrid-graphics laptops (issue #34849 / PR #41044). - The accent "pill" selection visual is reproduced with `VisualStateManager` (WinUI 3 has no `Style.Triggers`). - **WinUI 3 gotcha:** x:Bind on a Window-rooted XAML initializes only on `Window.Activated`, which never fires for this `SW_SHOWNA` overlay — so the selector calls `Bindings.Update()` after `InitializeComponent()`; without it the `ListView` renders empty. - **Theme:** the long-lived, never-activated process follows the system app theme automatically — `App.xaml` leaves `Application.RequestedTheme` unset, so WinUI re-resolves the `{ThemeResource}` brushes (and retints the acrylic) on a live light/dark switch with no manual `ThemeListener` needed. - **Layout parity with the WPF original:** the bar width hugs its content (`itemCount × 48`, clamped to the monitor width — computed, not measured, to avoid a racy `ListView` measure), and each cell pins `MinWidth=48` (WinUI's `ListViewItem` defaults to 88, which would otherwise leave wide gaps). - **Accessibility:** UIA window name + `AutomationId`s on the character list and description. **Dependency** - `PowerAccent.Core` no longer depends on WPF — it raises events and takes an injected UI-thread marshaller. - WinForms `SendKeys` → `SendInput` (CsWin32 P/Invoke); WPF-UI (Lepo) removed; language data moved to the UI-/WinRT-agnostic `PowerAccent.Common`. - MVVM via CommunityToolkit.Mvvm with `[ObservableProperty]` **partial properties** (WinRT-correct, clears MVVMTK0045). **CI / Build / Installer** - Signing config, WinUI3Apps glob harvest, and the new unit-test project — see the checklist above. ## Validation Steps Performed - **Build:** `x64 Debug` builds with **0 warnings / 0 errors**. - **Unit tests:** `PowerAccent.Core.UnitTests` — **21/21 pass** (9 anchor positions × DPI 1.0/1.5/2.0, the offset and negative-origin monitors, caret centering + edge clamping + flip-below). - **XamlStyler:** `PowerAccentXAML/MainWindow.xaml` passes the passive format check (CI mode). - **Manual (single monitor, Top-center, light theme):** - Accent popup appears with the full accent list rendered. - Bar hugs the characters and is centered; cell spacing matches the WPF original. - Switching the system theme (light/dark) is followed live by the popup. - With `show_description` enabled, the description row is wide (≥600px) and readable, with the accent bar centered above it. - **Remaining manual validation** (tracked in #48889): multi-monitor, per-monitor DPI, all 9 `toolbar_position` values, and high-contrast theme. --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>github.com-microsoft-PowerToys · 9039451e · 2026-07-03
- 0.9ETVPowerDisplay: Reuse the values the max-compatibility probe already read (#49596) ## Summary of the Pull Request In Maximum compatibility mode, when a monitor's capabilities string is unusable, discovery probes each continuous VCP code directly to find out which ones the panel implements — and then **throws the values away**. `BuildMonitorFromPhysical` immediately re-reads every one of those codes. That doubles the I2C traffic on exactly the hardware that cannot take it, and the re-read is the one whose result the user actually sees: a panel that answered the probe a moment ago but fails the re-read shows its brightness slider parked at the never-read default instead of where the panel really is. This makes the probe's values survive into the build stage. Extracted from #49445, which bundles it with a persisted discovery cache it does not depend on. ## PR Checklist - [ ] Closes: #xxx — partially addresses #49342; the remaining cause is in #49445 - [ ] **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 — this PR adds none - [ ] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places — none added; no new project, so no signing JSON, installer WXS or CI YML change is required - [ ] **Documentation updated** ## Detailed Description of the Pull Request / Additional comments ### What the re-read costs Worth being precise about, because it is not the slider's *existence*: | decided by | set from | affected by a failed re-read | | --- | --- | --- | | slider visible (`MonitorViewModel.ShowBrightness`) | `Monitor.Capabilities`, via `UpdateMonitorCapabilitiesFromVcp` before the initializer runs | no | | slider position (`Monitor.CurrentBrightness`) | the read | yes — stays at the never-read default | | `powerdisplay get` reporting a live reading (`Monitor.ReadValues`) | the read | yes — reported as unknown | | relative `powerdisplay adjust` (`AdjustCommandExecutor`) | `Monitor.ReadValues` | yes — no before-value to adjust from | So the flyout keeps the control either way; what the second read decides is whether it is pointed anywhere real, and whether the CLI will admit to a value. Halving the transactions on a bus that is both slow and, on this hardware, unreliable is the other half of the win. ### The seam `FetchCapabilitiesWithFallbackAsync` used to return `(string capsString, VcpCapabilities? caps)` — capabilities only, no values. It now returns a `VcpDiscoveryEvidence`, which carries the same two things plus the values the probe already read and a flag for a handle that died mid-probe. `VcpDiscoveryEvidence.Reconcile` folds the probe observations into the parsed capabilities in one place: | observation | capabilities | value carried | | --- | --- | --- | | read succeeded | code marked supported | yes | | device replied, range unusable (e.g. `max=0`) | code marked supported | no — the initializer still owes it a read | | no reply | unchanged | no | | handle-class failure | everything discarded | — | The second row is why membership keys off `Replied` rather than the value being usable: an unimplemented code fails with `DDCCI_VCP_NOT_SUPPORTED` and never sets the flag, so a reply proves the opcode exists even when the reported range cannot scale a percentage. That is the same rule `BuildCapabilitiesFromProbe` used before this PR; it just moves next to the value handling. Like that method, `Reconcile` iterates the observations rather than `NativeConstants.ContinuousVcpCodes`, which `VcpFeatureProbeService` only takes as the default for its constructor-injected sweep list. That keeps a widened sweep from silently dropping a code that answered, but it is not sufficient on its own: the carried value is consumed only for codes `ContinuousVcpInitializer` walks, so widening the sweep still needs a matching edit there. The comment and `Reconcile_ProbedCodeOutsideTheDefaultSweepIsStillHonoured` both say so rather than claiming the seam alone covers it. On the normal path nothing changes: the probe only runs when the caps string is unusable, so `live` is empty and `Reconcile` is a pass-through. `Reconcile_ParsedCapabilitiesSurviveWhenNoProbeRan` pins that. ### Continuous-VCP initialization moves out of the controller `DdcCiController` carried six near-identical `Initialize*` methods. The three percent-scaled ones become **`ContinuousVcpInitializer`** — brightness, contrast, volume. It skips any code the evidence already has a value for, and returns `false` when a read fails with a handle-class error, because `Monitor.Handle` is captured once per discovery pass and never refreshed: a monitor kept alive on a dead handle would send every later read and write into the void. It reads through the `IVcpFeatureReader` seam introduced in #49579, so it is testable without hardware. The three discrete-enum ones — color preset, input source, power mode — stay in `DdcCiController`, unchanged. The probe sweeps only `NativeConstants.ContinuousVcpCodes`, so no discrete value is ever carried across the seam and extracting them would be a refactor this change does not need; see *What is deliberately left out*. Only the continuous stage discards the monitor. That is a policy choice, not a property of the stage: losing a whole display because `0xD6` answered badly is worse than showing it without a power control. `DdcCiController.TryGetVcpFeature` therefore still has three discovery callers plus `GetVcpFeatureAsync`, the runtime refresh path. ### Behaviour change outside Maximum compatibility mode **A handle-class error during continuous VCP initialization now discards the monitor.** Before, the failure was logged, the read flag left unset, and the monitor kept — so its handle reached `PhysicalMonitorHandleManager` and every later operation went to a handle already known to be dead. The cost is that the monitor stays out of the flyout until a rediscovery: `DisplayChangeWatcher` schedules one for device-arrival/removal and console-display-state notifications, and the flyout's Refresh button forces one on demand. Note this check is not on every path. A caps string that parses but advertises none of `0x10`/`0x12`/`0x62` leaves `ContinuousVcpInitializer` nothing to read and suppresses the probe, so such a monitor is still published with a handle no VCP read has exercised — and its first VCP read then happens in the discrete stage, which never discards. ### What is deliberately left out **Extracting the discrete-VCP initialization.** The probe sweeps only the continuous codes, so no discrete value is ever reused and moving `0x14`/`0x60`/`0xD6` out of the controller would be a drive-by refactor with no bearing on this change. It is worth doing on its own, where the added test coverage can be reviewed for what it is. **Remembering a probe value across discoveries.** A probe value is only useful for the pass that produced it. Carrying one forward — so a later failing pass can still show the control — is the persisted known-good cache in #49445, a much larger change with an open design question attached. This PR is complete without it. ## Validation Steps Performed - built `PowerDisplay.Lib.UnitTests` and `PowerDisplay` for x64 Debug with VS MSBuild — 0 errors, 0 warnings - ran `PowerDisplay.Lib.UnitTests.dll` with `vstest.console.exe`: **240 passed, 0 failed** — 16 of those cases are added here (8 in `ContinuousVcpInitializerTests`, 7 in `VcpDiscoveryEvidenceTests`, 1 in `DdcErrorClassifierTests`) - `VcpDiscoveryEvidenceTests` pins each row of the table above, plus that a probed code outside the default sweep is still honoured - `ContinuousVcpInitializerTests` pins that a probed code is never re-read (the reader is primed with a failure it must not reach), that a handle-class error stops the remaining codes, and that a feature-level refusal does not. `Initialize_EveryContinuousCodeIsReadAndApplied` walks the whole `ContinuousVcpCodes` array with a distinct range and percentage per feature, so a code added to that array without an arm in both `IsSupported` and `ApplyValue` fails rather than being silently skipped or silently discarded — checked by mutation: removing either volume switch arm fails that test and `Initialize_ProbedVolumeIsAppliedWithoutReadingAgain` - not covered by tests: the `DdcCiController` side of the contract — that `evidence.IsPhysicalMonitorUnavailable` skips the monitor and releases the physical, and that a `false` from `ContinuousVcpInitializer` does the same. That layer takes no injectable dependencies today - no hardware validation performed: this path is reachable only on a panel whose capabilities string is unusable, which needs an incomplete or unreliable DDC/CI implementation --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>github.com-microsoft-PowerToys · 3cb3bdcd · 2026-07-31
- 0.8ETVfeat(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>github.com-microsoft-PowerToys · f117bfc6 · 2026-05-21
- 0.8ETV[PowerDisplay] Add max compatibility mode setting (#47875) <!-- 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 1. Adds an opt-in Max compatibility mode in PowerDisplay's Advanced settings. When enabled, DDC discovery probes monitors that don't advertise capabilities, picking up displays that would otherwise be skipped. 2. Toggling the setting triggers an immediate rescan via a new RescanPowerDisplayMonitorsEvent IPC event from Settings to PowerDisplay. 3. Hides the brightness slider on monitors that lack VCP 0x10. Also fixed animation issue #47868 <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #47878 <!-- - [ ] 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: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>github.com-microsoft-PowerToys · c5d17913 · 2026-05-14
- 0.8ETV[Quick Accent] Fix the accent bar's blank first frame and its under-measured width (#49633) ## Summary of the Pull Request Two defects in the WinUI 3 Quick Accent selector. They look unrelated but share a root: the overlay owns no layout of its own, so `MainWindow` sizes and shows it by hand — and both halves of that hand-rolled logic rest on an assumption that does not hold. * **#49489** — the bar appears blank, too wide and clipped on the right for a few frames, then snaps into place. A hidden WinUI 3 window renders nothing, so `ShowWindow(SW_SHOWNA)` puts the HWND on screen before the freshly rebuilt accent list has ever been laid out. * **#49488** — the window is sized narrower than its own content whenever a glyph is wider than the 48 DIP cell, so the list silently scrolls inside it and the trailing accents are pushed against the right edge. test result: https://github.com/user-attachments/assets/80d57aa8-5030-46c3-9ec8-6ed05ed00f03 ## PR Checklist - [x] Closes: #49489 - [x] Closes: #49488 - [x] **Communication:** bug fixes for two open, triaged issues in an existing module; no new feature surface - [x] **Tests:** added — 11 cases in `PowerAccent.Core.UnitTests` covering the new `Calculation.GetToolbarWidth`; the existing positioning/DPI suites are unaffected - [x] **Localization:** no new end-user-facing strings - [ ] **Dev docs:** N/A — the *Toolbar Sizing and Reveal* section was dropped from this PR; the reasoning lives in the code comments and in the commit messages instead - [x] **New binaries:** none — no new projects or outputs, so no `ESRPSigning_core.json`, `Product.wxs`, CI or release YML changes are needed - [ ] **Documentation updated:** N/A — internal rendering/layout fix with no user-facing behavior change beyond the bugs going away ## Detailed Description of the Pull Request / Additional comments ### #49489 — the blank, over-wide, clipped first frame The window is never actually repositioned or resized. Measuring the two frames in the issue shows the same HWND rect in both: identical left edge, and the bad frame's hard right cut sits exactly where the good frame's rounded corner plus its 24 DIP margin ends. What changes is the **content** — it is composed once from a stale layout, then re-laid-out. Two ordering problems produced that stale composition: 1. `TransientSurface` is `Collapsed` while hidden and is only flipped to `Visible` from the `Showing` event, which `TransparentWindow.RaiseShow` raises **after** `ShowWindow(SW_SHOWNA)`. The accent bar's subtree therefore provably has not been measured or arranged at the moment the HWND becomes visible. 2. The hide path called `ViewModel.Characters.Clear()` synchronously right after `Hide()` — but `Hide()` only *queues* the dismissal, so the still-visible window rendered an empty bar at the old width. That empty card is exactly what the next summon put back on screen. The fix mirrors what the WPF implementation did for the same symptom in #46593 (render off screen, then `SetWindowPos` into view), adapted to WinUI 3 where a hidden window does not render at all: * Show the bar with `Selector.Opacity = 0`, lay it out, and unveil it once `CompositionTarget.Rendering` confirms a couple of frames have elapsed. `Opacity = 0` still renders (unlike `Visibility.Collapsed`), which is exactly what is needed here. A 150 ms timeout backs it up — not because frames stop arriving (attaching a `Rendering` handler forces the UI thread to run every frame) but because the tick cadence carries no guarantee and can stop for a locked or fully occluded session; on that path the bar simply appears the way it used to, so it can never get stuck invisible. * Size the bar **twice** per summon: once before `Show`, and again after the first real layout pass. The first measurement runs while the surface is still `Collapsed` and, on the first summon of the process, before its template has ever been applied, so it can report less than the items need. The correction happens while the bar is still transparent, so it is never seen as a resize. * Leave the characters in the list on hide. The next summon clears and refills them anyway, and not clearing them removes the blank-bar frame at the source. * A generation counter drops a pending reveal when the summon is dismissed or superseded before its frame lands, and arming a new summon detaches the previous one's per-frame handler so it cannot unveil the new bar ahead of its own layout pass. ### #49488 — width derived from the item count instead of measured `MainWindow` computed the window width as `Characters.Count * 48`, while the XAML cell is `MinWidth="48"` — a *minimum*, not a fixed width. `ListViewItem` → `Grid MinWidth=48` with a `ContentPresenter Margin=12`, so a cell is `max(48, glyphWidth + 24)`: any glyph wider than 24 DIP (₹, ‰, ﷼, ៛, CJK fallbacks) grows its cell. With **All languages** selected, R and P each carry ~20 characters and the accumulated error is enough for the real content to overflow the window. The ListView's `ScrollViewer` (`HorizontalScrollMode="Enabled"`, `HorizontalScrollBarVisibility="Hidden"`) then absorbs the overflow invisibly, and `ScrollIntoView` starts scrolling a bar that should not scroll at all. The pre-migration WPF window used `SizeToContent="WidthAndHeight"` and only set `MaxWidth`, so the layout system measured the same item template and the window simply grew — which is why this never showed up before. `AppWindow` has no `SizeToContent` equivalent, and the migration replaced it with a constant model. Now: * `SelectorControl.MeasureContentWidthDip()` measures the list against an unbounded width and returns what the items actually need. Measuring explicitly, rather than reading a stale `DesiredSize`, addresses the concern recorded in the original comment: the bar is rebuilt on every summon while the window is still hidden, so no layout pass has run for the new items yet. * `Calculation.GetToolbarWidth()` — a pure function, hence the unit tests — floors that measurement at `itemCount * minItemWidth` (every cell is at least the minimum, so a list that could not be measured reports 0 and safely falls back to the old estimate instead of collapsing the bar), applies the description row's minimum width, and clamps to the display's usable width so long character sets still scroll on purpose. * The clamp's lower bound is `minItemWidth + chromeWidth` — one cell plus the space around it, the narrowest bar that can still draw a glyph — and its upper bound is `Math.Max(minItemWidth + chromeWidth, maxWidth)`, because a display narrower than that floor would otherwise invert the bounds and make `Math.Clamp` throw. `DescriptionMinWidthDip = 648` masked this bug whenever the Unicode description row was on and the character set short, which is likely why #49402 (description-row width) did not surface it. ## Validation Steps Performed * `PowerAccent.Core`, `PowerAccent.UI` and `PowerAccent.Core.UnitTests` build clean (Debug|x64). * `PowerAccent.Core.UnitTests`: 32/32 pass, including the 11 `GetToolbarWidth` cases — narrow glyphs hug the item count, wide glyphs win over the count estimate (the #49488 regression guard), the measurement winning by a single DIP, a partly realized list keeping the item-count floor, an unmeasured list falling back to the estimate, over-long content clamping to the display maximum, the description row widening a short bar but not a long one, the description minimum losing to a narrower display, and both ends of the clamp. The lower clamp bound was verified by mutation: rewriting it to `Math.Clamp(width, 0, ...)` fails only `GetToolbarWidth_EmptyList_FallsBackToOneCellPlusChrome`. --------- Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>github.com-microsoft-PowerToys · 10236ee8 · 2026-08-04
- 0.5ETVPowerDisplay: Funnel both monitor-state saves through one locked write (#49629) ## Summary of the Pull Request `MonitorStateManager` wrote `monitor_state.json` from two independent paths: the debounced save used `File.WriteAllTextAsync`, `Dispose` used `File.WriteAllText`. Both open the path with `FileShare.Read`, so disposing while a debounced save had already passed its delay left the two racing for the same handle — the loser was denied at `CreateFile` and its payload was dropped whole, into a catch that only logged. Both paths now go through one method that serializes and writes under a single lock, and the file is published by rename instead of being written in place. ## 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 - [x] **Tests:** Added/updated and all pass - [x] **Localization:** All end-user-facing strings can be localized — this PR adds none - [ ] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places — none added, so no signing JSON, installer WXS or CI YML change is required - [ ] **Documentation updated** ## Detailed Description of the Pull Request / Additional comments ### The collision `Dispose` disposes the debouncer before flushing, which cancels a pending `Task.Delay` but cannot reach a save that has already passed it and stopped observing the token. That save is inside `File.WriteAllTextAsync` when `Dispose` reaches its own `File.WriteAllText`. Both open with `FileMode.Create, FileAccess.Write, FileShare.Read`, and Windows checks sharing in both directions, so the second open fails with `ERROR_SHARING_VIOLATION`. The loser never gets a handle and writes zero bytes — a torn or interleaved file was never reachable, only a dropped write. ### Why the flush could actually be lost A bare collision costs only a redundant write: both paths serialize the same live `_states`, and `UpdateMonitorParameter` mutates it synchronously before arming the debounce, so whichever writer wins already has the user's latest value. The case that loses data is narrower. The debounced save built its JSON *before* the `await`, so: 1. the debounced save snapshots `{brightness: 60}` and opens the file; 2. the user moves a slider to 70 — `_states` is updated, a new debounce is armed; 3. the user quits; `Dispose` snapshots `{brightness: 70}` and is denied at `CreateFile`; 4. the async write completes, publishing 60. 70 is gone. The window is the few hundred microseconds the async write holds the handle, but it is exactly the window in which the user is quitting. ### The fix Both paths call `WriteStateFile`, which takes `_writeLock` and does the serialize-and-write inside it. `BuildStateJson` runs under the lock too, so the last snapshot built is the one that lands — a writer that queues behind another re-snapshots rather than replaying a stale payload. The write is synchronous on both paths. `Dispose` has to flush without awaiting, the file is a few hundred bytes, and the async API was the only reason there were two write paths to collide in the first place. That is also why the guard is a plain `lock` rather than a `SemaphoreSlim`: with neither caller async there is no blocking-on-an-async-method concern left to design around. `Dispose` keeps its ordering — dispose the debouncer so nothing new is scheduled, then flush if the state was dirty. ### Publishing by rename The bytes go to a temp file and are renamed in, matching `CrashDetectionScope` and `ProfileStore` in the same module. An in-place write truncates at `CreateFile` before it writes anything, and two exit paths never reach `Dispose` at all — `App.OnLaunched` registers `TerminatePowerDisplayEvent` and the runner-exit watchdog, and both call `Environment.Exit(0)` outright. An interrupted write there would leave a zero-byte file, which `LoadStateFromDisk`'s catch turns into "no saved state for *any* monitor" — total loss rather than the last change. There is deliberately no `Flush(flushToDisk: true)` to go with it. The threat here is process death, which the page cache survives, not power loss; and this flush runs on the UI thread at shutdown, where a `FlushFileBuffers` on a busy disk is the one change in this area a user could actually feel. ### The dirty flag `SaveStateToDisk` cleared `_isDirty` *after* the write. A change landing between the snapshot and that assignment set the bit and had it immediately cleared; `Dispose` then read `wasDirty == false`, cancelled the debounce that change had scheduled, and skipped the flush — losing exactly the last change this path exists to preserve. It is now cleared before the snapshot and re-marked if the write throws, so a change that lands mid-write either rides along in the snapshot or stays dirty. ### Testability `MonitorStateManager` gains an internal constructor taking a state file path, so tests can drive it against a temp directory instead of the real LocalAppData location. ## Validation Steps Performed - `PowerDisplay.Lib` and `PowerDisplay.Lib.UnitTests` built for x64 Debug with VS MSBuild; `PowerDisplay.Lib.UnitTests.dll` under `vstest.console.exe`: **273 passed, 0 failed** (4 of them in `MonitorStateSaveTests`) - Removing `lock (_writeLock)` fails `ConcurrentWrites_DoNotCollideOnTheStateFile` with the same "used by another process" `IOException` the collision produces, so the test pins the guard rather than the shape of the code - `FailedWrite_LeavesTheExistingStateFileIntact` occupies the temp path with a directory so the write fails at exactly the point an in-place `File.WriteAllText` would already have truncated the published file; against the previous in-place write the same test fails, since that write would succeed and replace the file - No end-to-end manual check was performed. The window is a few hundred microseconds wide and only opens when a debounced save is mid-write at the exact moment `Dispose` runs, so reproducing it by hand is unreliable — the concurrency test drives the same contention deterministically instead ### Known gap, not addressed here `TerminatePowerDisplayEvent` and the runner-exit watchdog call `Environment.Exit(0)` without ever running `Dispose`, so on those paths up to a full `SaveDebounceMs` (2 s) of changes is dropped unconditionally — no race required. That is a larger loss surface than the one this PR closes, and the module already has the machinery to fix it (`CrashDetectionScope` subscribes to `AppDomain.ProcessExit` through an `IProcessExitHook` seam for precisely these paths). Left out to keep this PR to one logical change; happy to open a follow-up issue. Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>github.com-microsoft-PowerToys · f4b3dcde · 2026-08-03
- 0.4ETV[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>github.com-microsoft-PowerToys · 4edfcee8 · 2026-05-22
- 0.4ETV[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>github.com-microsoft-PowerToys · cd5027fa · 2026-05-28
- 0.4ETV[PowerDisplay] Rescan monitors on display wake (#47876) <!-- 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 1. Subscribes to GUID_CONSOLE_DISPLAY_STATE so PowerDisplay rescans monitors when the console display wakes from sleep — previously, woken monitors stayed unrecognized until the user manually re-triggered discovery. 2. Locks the PowerDisplay UI immediately on wake to block stale interactions before the rescan completes. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #47951 <!-- - [ ] 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: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>github.com-microsoft-PowerToys · 38882fd3 · 2026-05-18
- 0.3ETV[PowerDisplay] Add configurable mouse wheel increment for slider controls (#49002) ## Summary of the Pull Request Adds a Settings option that controls how much the PowerDisplay flyout sliders (brightness, contrast, volume) change per mouse-wheel notch. The value is chosen from a **preset dropdown** (`1, 2, 5, 10, 15, 20, 25`) and defaults to **5**, preserving today's behavior. Previously the per-notch step was hardcoded as `helpers:SliderExtensions.MouseWheelChange="5"` in four places in the flyout. <img width="1282" height="620" alt="image" src="https://github.com/user-attachments/assets/3b299a47-eb7b-4b53-b3dc-0540fbb25bfc" /> ## PR Checklist - [x] Closes: #48805 - [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 — _added `MouseWheelIncrementSettingsTests` to the existing `PowerDisplay.Lib.UnitTests`; passing via `vstest.console.exe`._ - [x] **Localization:** All end-user-facing strings can be localized — _new `PowerDisplay_MouseWheelIncrement.Header`/`.Description` in `en-us/Resources.resw`, surfaced via `x:Uid`._ - [ ] **Dev docs:** Added/updated — _N/A: small settings addition, no behavioral/architecture docs affected._ - [ ] **New binaries:** Added on the required places — _N/A: no new binaries or projects (the unit test was added to an existing test project)._ - [ ] [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:** _N/A._ ## Detailed Description of the Pull Request / Additional comments A single `int MouseWheelIncrement` is added to `PowerDisplayProperties` and persisted to `settings.json` under `mouse_wheel_increment`; it is edited in the Settings UI and read by the flyout app. The value applies uniformly to all four flyout sliders. **Data model** — `src/settings-ui/Settings.UI.Library/PowerDisplayProperties.cs` - `MouseWheelIncrement` (`int`, `[JsonPropertyName("mouse_wheel_increment")]`, default `5` set in the constructor, mirroring `MonitorRefreshDelay`). Old `settings.json` without the key deserializes to `5` (no migration). The whole type is already registered with the source-generated JSON contexts, so the new property serializes on both the Settings and flyout sides with no context change. **Settings UI (write side)** — `src/settings-ui/Settings.UI/...` - `PowerDisplayViewModel`: `MouseWheelIncrement` (get/set via `SetSettingsProperty`, calls `SignalSettingsUpdated()` so an open flyout updates live) and `MouseWheelIncrementOptions` = `{ 1, 2, 5, 10, 15, 20, 25 }`. - `PowerDisplayPage.xaml`: a `ComboBox` `SettingsCard` in the flyout-settings expander, immediately after "Monitor refresh delay", matching that card's markup. - `Strings/en-us/Resources.resw`: `Mouse wheel increment` + description. **Flyout (read side)** — `src/modules/powerdisplay/PowerDisplay/...` - `MainViewModel`: `[ObservableProperty] int MouseWheelIncrement` (default 5), loaded from settings in `LoadUIDisplaySettings()` (runs at startup and on the settings-updated IPC event, so the all-displays slider updates live). - `MonitorViewModel`: a read-only proxy `MouseWheelIncrement => _mainViewModel?.MouseWheelIncrement ?? 5` plus `RefreshMouseWheelIncrement()`, called from `ApplySettingsFromUI`'s per-monitor loop so the per-monitor sliders update live. - `MainWindow.xaml`: the four sliders' `SliderExtensions.MouseWheelChange` now bind to the setting — the all-displays slider to `ViewModel.MouseWheelIncrement`, the three per-monitor sliders (brightness/contrast/volume, inside the `MonitorViewModel` `DataTemplate`) to `MouseWheelIncrement`. ## Validation Steps Performed **Automated** - Unit tests (`PowerDisplay.Lib.UnitTests/MouseWheelIncrementSettingsTests.cs`), run via `vstest.console.exe` — passing: - default value is `5`; - legacy `settings.json` missing the key deserializes to `5` (no migration); - round-trip preserves a non-default value; - serialization emits the `mouse_wheel_increment` snake_case key. - `PowerToys.Settings` and the PowerDisplay flyout app both compile clean (the XAML compiler validates the new `x:Bind` bindings). - Confirmed the property flows through the source-generated JSON contexts (whole-type `[JsonSerializable(typeof(PowerDisplaySettings))]`) on both the Settings and flyout sides. **Pending (manual, on-device — reason this PR is a draft)** - [ ] Settings: the dropdown shows `5` on a fresh profile; changing it writes the new `mouse_wheel_increment` value to `settings.json`. - [ ] Flyout: scrolling each slider (all-displays brightness, per-monitor brightness/contrast/volume) steps by the selected value, including live update while the flyout is open. --------- Co-authored-by: Yu Leng <yuleng@microsoft.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>github.com-microsoft-PowerToys · 28a9bbe8 · 2026-06-29
- 0.2ETV[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>github.com-microsoft-PowerToys · 273d735a · 2026-05-25
- 0.2ETV[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>github.com-microsoft-PowerToys · d221f84d · 2026-06-18