## 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>
Microsoft PowerToys
Microsoft PowerToys is a collection of utilities that help you customize Windows and streamline everyday tasks.
Installation · Documentation · Blog · Release notes
🔨 Utilities
PowerToys includes over 30 utilities to help you customize and optimize your Windows experience:
📦 Installation
For detailed installation instructions and system requirements, visit the installation docs.
But to get started quickly, choose one of the installation methods below:
Download the .exe file from GitHub
Go to the PowerToys GitHub releases, scroll down and select Assets to reveal the installation files, and choose the one that matches your architecture and install scope. For most devices, that would be x64 per-user.
WinGet
Download PowerToys from [WinGet](https://github.com/microsoft/winget-cli#installing-the-client). Updating PowerToys via winget will respect the current PowerToys installation scope. To install PowerToys, run the following command from the command line / PowerShell:
- User scope installer (default)
winget install Microsoft.PowerToys -s winget
- Machine-wide scope installer
winget install --scope machine Microsoft.PowerToys -s winget
Other methods
There are [community driven install methods](https://learn.microsoft.com/windows/powertoys/install#community-driven-install-tools) such as Chocolatey and Scoop. If these are your preferred install solutions, you can find the install instructions there.
✨ What's new?
To see what's new, check out the release notes.
🛣️ Roadmap
We are planning some nice new features and improvements for the next releases – a brand-new Shortcut Guide experience, ensuring it's easier to find and install Command Palette extensions and so much more! Stay tuned for v0.100!
❤️ PowerToys Community
The PowerToys team is extremely grateful to have the support of an amazing active community. The work you do is incredibly important. PowerToys wouldn't be nearly what it is today without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thank you and take time to recognize your work. Your contributions and feedback improve PowerToys month after month!
Contributing
This project welcomes contributions of all types. Besides coding features / bug fixes, other ways to assist include spec writing, design, documentation, and finding bugs. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows. We ask that before you start work on a feature that you would like to contribute, please read our Contributor's Guide. We would be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you grant us the rights to use your contribution and that you have permission to do so. For guidance on developing for PowerToys, please read the developer docs for a detailed breakdown. This includes how to setup your computer to compile.
Code of conduct
This project has adopted the Microsoft Open Source Code of Conduct.
Privacy statement
The application logs basic diagnostic data (telemetry). For more privacy information and what we collect, see our PowerToys Data and Privacy documentation.

