Repository navigation
feat(brightness): control external monitors through DDC/CI - #1832
Conversation
|
Addresses the two review findings on eythaann#1832: - A poll or scan could read a value, release `DDCCI_LOCK`, lose the race against a slider write, and then publish the stale value on top of the fresh one. `DDCCI_LOCK` now covers both the device transaction and the update of the shared state, and the write path re-resolves the panel under the lock so a rescan cannot swap its handle mid-flight. - A cloned/mirrored display group shares one `HMONITOR` but yields several `PHYSICAL_MONITOR` handles; only the first was kept. Each panel is now its own entry (`<stable id>` for the first, `<stable id>#<index>` for the rest) with its own handle, which is held open for the lifetime of the entry instead of being reopened per call. Re-tested on two BenQ ZOWIE XL: both sliders drive their panel, an external change is picked up on the next poll, values read back over dxva2 match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
|
@z1pp090 hi, I'm ready to merge this, but please fix the conflicts. |
Desktops with external displays had no brightness control at all: the WMI provider only covers internal panels. This wires up the dormant DDC/CI helpers in `windows_api::monitor::brightness` as a second backend of `BrightnessManager`. - Every physical monitor is probed with `GetMonitorBrightness` on a background thread, and only monitors that answer with a sane range (`min < max`, `current` inside it) are exposed. `GetMonitorCapabilities` is deliberately not used as a gate: some monitors that do support brightness fail it (BenQ ZOWIE XL), and Microsoft documents that others report capabilities they do not have. - Values are normalised to a 0-100 percentage for the UI regardless of the raw range, so the existing `MonitorBrightness` type and the quick-settings / flyout widgets work unchanged. - DDC/CI has no change events, so known monitors are polled every 5 s to pick up changes made from the monitor OSD; `MonitorManager` events trigger a rescan on hotplug. All DDC/CI transactions are serialised behind one lock. - `PHYSICAL_MONITOR` handles are now released with `DestroyPhysicalMonitor` (they were leaked before). - The WMI path is unchanged; `set_brightness` dispatches by instance name. Tested on Windows 11 with two BenQ ZOWIE XL over DisplayPort: both sliders drive the panels (verified reading back over dxva2 from another process), and an external change is reflected in the UI on the next poll. Closes eythaann#1826. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
Addresses the two review findings on eythaann#1832: - A poll or scan could read a value, release `DDCCI_LOCK`, lose the race against a slider write, and then publish the stale value on top of the fresh one. `DDCCI_LOCK` now covers both the device transaction and the update of the shared state, and the write path re-resolves the panel under the lock so a rescan cannot swap its handle mid-flight. - A cloned/mirrored display group shares one `HMONITOR` but yields several `PHYSICAL_MONITOR` handles; only the first was kept. Each panel is now its own entry (`<stable id>` for the first, `<stable id>#<index>` for the rest) with its own handle, which is held open for the lifetime of the entry instead of being reopened per call. Re-tested on two BenQ ZOWIE XL: both sliders drive their panel, an external change is picked up on the next poll, values read back over dxva2 match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01StkifHpSLWH4J5TvuP56ur
01340b7 to
fb7f77f
Compare
|
Rebased onto current master ( |
Closes #1826. Follows up on your comment there: this wires the dormant DDC/CI code in
windows_api/monitor/brightness.rsintoBrightnessManageras a second backend next to WMI.What changes
GetMonitorBrightnesson a background thread. A monitor is exposed only if it answers with a sane range (min < max,currentinside it).GetMonitorCapabilitiesis intentionally not used as a gate: my BenQ ZOWIE XL panels fail it yet answer brightness fine, and Microsoft warns that other monitors report capabilities they don't have. Writes are clamped to the range the monitor itself reported, which is the mitigation for the "could be dangerous" note in the docs.MonitorBrightness, quick-settings and the flyout work unchanged.instance_nameis the monitor's stable id for DDC/CI entries;set_brightnessdispatches by instance name (DDC/CI first, then WMI).MonitorManagerevents (ViewAdded/ViewRemoved/ViewsChanged) trigger a full rescan for hotplug. A failed read keeps the last value; it never drops the monitor by itself.DDCCI_LOCK), so polling and slider writes never overlap on the I2C bus.PHYSICAL_MONITORhandles are now released withDestroyPhysicalMonitor(both the previously dead code andget_physical_monitorscallers were leaking them).WmiMonitorBrightness → MonitorBrightnessmapping into the manager.Tested
Windows 11 25H2, desktop, two BenQ ZOWIE XL over DisplayPort, no WMI brightness on this machine:
min 0 / current 100 / max 100), two sliders in quick-settings.cargo test, clippy, rustfmt clean.Not tested (no hardware / scenario available): monitor hotplug at runtime, sleep/resume, laptops that have both an internal WMI panel and an external DDC/CI monitor, monitors whose raw range is not 0-100, and monitors that time out on DDC/CI (they would just be skipped at scan, but a monitor that answers the probe and later hangs would block the worker thread for the dxva2 timeout).
Follow-up ideas (not in this PR)
MonitorBrightnessand regenerated bindings.🤖 Generated with Claude Code