refactor(host/W6.2): extract virtual-display orchestration into the pf-vdisplay crate
vdisplay.rs + vdisplay/* (the per-compositor Linux backends — KWin zkde-screencast,
wlroots swaymsg, Mutter RemoteDesktop, Hyprland — and the Windows IddCx/pf-vdisplay
driver backend, behind one VirtualDisplay trait; the mode-conflict admission
registry, the display policy/identity/custom-preset state, and the session-env /
gamescope routing) move into crates/pf-vdisplay (plan §W6). The DDC/CI panel-power
control (used only here) and the KWin zkde protocol XML move with it. This
completes the host-crate decomposition: capture, encode, inject, and vdisplay are
now four subsystem crates over the shared leaves, and punktfunk-host is the
orchestrator (serve/supervisor + native + gamestream + mgmt).
Coupling breaks (all down-only, cargo-tree acyclic):
- capture::dxgi identity -> pf_frame::dxgi; win_display/monitor_devnode/
console_session_mismatch -> pf-win-display leaf; can_open_another_session ->
pf-encode (the NVENC session-budget admission gate — acyclic peer edge).
- The registry's DisplayCreated/DisplayReleased emits into the host SSE event bus
invert to a leaf hook: pf-vdisplay emits a neutral DisplayEvent to a
host-registered DISPLAY_EVENT_SINK, so it never reaches the orchestrator's
events module.
- The IddCx driver module is renamed pf_vdisplay -> driver (its old name collided
with the crate name through the host's `mod vdisplay` shim glob).
The host keeps `mod vdisplay { pub use pf_vdisplay::* }` so every crate::vdisplay::*
path (serve/mgmt/native/the capture FrameChannelSender seam) is unchanged; the
heavy deps (wayland/ashpd/tokio + the zkde protocol) moved with the crate.
Co-authored: a fail-closed IOCTL-reply-length security fix (reject short/zeroed
pf-vdisplay driver replies before trusting protocol_version/target_id/wudf_pid/luid,
security-review 2026-07-17) rides this commit in the moved driver module.
Verified: Linux clippy -D warnings (pf-vdisplay + host nvenc,vulkan-encode,pyrowave
--all-targets) + pf-vdisplay 63/63 + host 167/167 tests; Windows clippy -D warnings
(pf-vdisplay --all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,201 +0,0 @@
|
||||
//! DDC/CI monitor panel power control — the EXPERIMENTAL `ddc_power_off` display-policy axis.
|
||||
//!
|
||||
//! DDC/CI is the VESA command channel to the monitor itself: an I²C bus inside the video cable
|
||||
//! (dedicated pins on VGA/DVI/HDMI, tunneled over the AUX channel on DisplayPort) whose MCCS
|
||||
//! "VCP codes" expose the monitor's OSD knobs to software. VCP 0xD6 is the power mode; we command
|
||||
//! `0x04` (DPMS off — panel + backlight dark, firmware still listening) and never `0x05`
|
||||
//! (power-button off — many monitors kill their DDC controller in that state and need a physical
|
||||
//! button press to come back).
|
||||
//!
|
||||
//! Why: the "periodic double-jolt while the virtual display is the SOLE active display" stutter
|
||||
//! class (Apollo #179/#358/#368/#563/#776 and our own field report). When an `Exclusive` isolate
|
||||
//! deactivates the physical monitor, its link drops and the monitor falls into its no-signal flow:
|
||||
//! standby with periodic auto-input-scan / link probing that the GPU driver services with
|
||||
//! display-subsystem stalls at a seconds-scale cadence. A panel commanded off over DDC/CI believes
|
||||
//! it has an owner and (on cooperating firmware) stops probing. This is deliberately shipped as an
|
||||
//! experiment: whether it helps discriminates *who initiates* the churn — monitor firmware (DDC-off
|
||||
//! fixes it) vs. the driver servicing a dark head regardless (only a driven link fixes it, i.e.
|
||||
//! topology `primary`/`extend`).
|
||||
//!
|
||||
//! Everything here is best-effort and warn-and-continue: monitors without DDC/CI support (or with
|
||||
//! it disabled in the OSD), docks/KVMs that don't pass the channel through, and laptop-internal
|
||||
//! panels (ACPI backlight, no DDC) all simply probe as unsupported and are skipped. Each DDC
|
||||
//! transaction can block for tens of ms — callers run at session acquire/teardown, never on the
|
||||
//! frame path.
|
||||
|
||||
use windows::Win32::Devices::Display::{
|
||||
DestroyPhysicalMonitors, GetNumberOfPhysicalMonitorsFromHMONITOR,
|
||||
GetPhysicalMonitorsFromHMONITOR, GetVCPFeatureAndVCPFeatureReply, SetVCPFeature,
|
||||
PHYSICAL_MONITOR,
|
||||
};
|
||||
use windows::Win32::Foundation::LPARAM;
|
||||
use windows::Win32::Graphics::Gdi::{
|
||||
EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITORINFOEXW,
|
||||
};
|
||||
|
||||
/// MCCS VCP code 0xD6 — display power mode.
|
||||
const VCP_POWER_MODE: u8 = 0xD6;
|
||||
/// VCP 0xD6 value: on.
|
||||
const POWER_ON: u32 = 0x01;
|
||||
/// VCP 0xD6 value: DPMS off (dark panel, DDC controller stays responsive). Deliberately NOT 0x05.
|
||||
const POWER_OFF: u32 = 0x04;
|
||||
|
||||
/// One active display: its HMONITOR and GDI device name (`\\.\DISPLAYn`).
|
||||
struct ActiveMonitor {
|
||||
hmon: HMONITOR,
|
||||
device: String,
|
||||
}
|
||||
|
||||
/// Enumerate the active displays (HMONITOR + GDI name). HMONITORs are only valid while a display
|
||||
/// is part of the desktop — which is exactly why the off-command must run BEFORE a CCD isolate
|
||||
/// and the on-command AFTER the restore.
|
||||
fn active_monitors() -> Vec<ActiveMonitor> {
|
||||
unsafe extern "system" fn collect(
|
||||
hmon: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_rect: *mut windows::Win32::Foundation::RECT,
|
||||
data: LPARAM,
|
||||
) -> windows::core::BOOL {
|
||||
// SAFETY: `data` is the `&mut Vec<ActiveMonitor>` passed by `active_monitors` below,
|
||||
// valid for the duration of the synchronous EnumDisplayMonitors call that invokes us.
|
||||
let out = unsafe { &mut *(data.0 as *mut Vec<ActiveMonitor>) };
|
||||
let mut info = MONITORINFOEXW::default();
|
||||
info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
|
||||
// SAFETY: `hmon` is the live monitor handle the enumeration just handed us; `info` is a
|
||||
// properly-sized MONITORINFOEXW local whose cbSize is set, which GetMonitorInfoW requires
|
||||
// to safely write the extended (szDevice) variant.
|
||||
if unsafe { GetMonitorInfoW(hmon, &mut info.monitorInfo) }.as_bool() {
|
||||
let len = info
|
||||
.szDevice
|
||||
.iter()
|
||||
.position(|&c| c == 0)
|
||||
.unwrap_or(info.szDevice.len());
|
||||
out.push(ActiveMonitor {
|
||||
hmon,
|
||||
device: String::from_utf16_lossy(&info.szDevice[..len]),
|
||||
});
|
||||
}
|
||||
true.into() // keep enumerating
|
||||
}
|
||||
|
||||
let mut out: Vec<ActiveMonitor> = Vec::new();
|
||||
// SAFETY: `collect` matches MONITORENUMPROC; `&mut out` outlives the synchronous enumeration
|
||||
// and is only dereferenced inside the callback (single-threaded — user32 invokes it inline).
|
||||
let _ = unsafe {
|
||||
EnumDisplayMonitors(
|
||||
None,
|
||||
None,
|
||||
Some(collect),
|
||||
LPARAM(&mut out as *mut Vec<ActiveMonitor> as isize),
|
||||
)
|
||||
};
|
||||
out
|
||||
}
|
||||
|
||||
/// Apply `value` to VCP 0xD6 on every physical monitor behind `hmon` that answers a 0xD6 probe.
|
||||
/// Returns how many panels acknowledged the set. `device` is for the log lines only.
|
||||
fn set_power(hmon: HMONITOR, device: &str, value: u32) -> u32 {
|
||||
let mut n = 0u32;
|
||||
// SAFETY: `hmon` is a live monitor handle from the enumeration; `n` is a valid out-param.
|
||||
if unsafe { GetNumberOfPhysicalMonitorsFromHMONITOR(hmon, &mut n) }.is_err() || n == 0 {
|
||||
return 0;
|
||||
}
|
||||
let mut phys = vec![PHYSICAL_MONITOR::default(); n as usize];
|
||||
// SAFETY: `phys` is sized to exactly the count the API just reported for this handle.
|
||||
if unsafe { GetPhysicalMonitorsFromHMONITOR(hmon, &mut phys) }.is_err() {
|
||||
return 0;
|
||||
}
|
||||
let mut acked = 0u32;
|
||||
for p in &phys {
|
||||
// PHYSICAL_MONITOR is `packed(1)` (dxva2 header pragma) — copy the fields OUT by value
|
||||
// before touching them; a reference into a packed field is rejected (E0793, UB).
|
||||
let handle = p.hPhysicalMonitor;
|
||||
let desc_raw = p.szPhysicalMonitorDescription;
|
||||
let len = desc_raw
|
||||
.iter()
|
||||
.position(|&c| c == 0)
|
||||
.unwrap_or(desc_raw.len());
|
||||
let desc = String::from_utf16_lossy(&desc_raw[..len]);
|
||||
// Probe first: a monitor without DDC/CI (or with it disabled in the OSD, or behind a
|
||||
// dock/KVM that drops the channel) fails here and is skipped — never blind-write to a
|
||||
// bus we can't read.
|
||||
let (mut current, mut max) = (0u32, 0u32);
|
||||
// SAFETY: `handle` is the live physical-monitor handle (valid until
|
||||
// DestroyPhysicalMonitors below); the value pointers are valid locals ('None' for the
|
||||
// code-type out-param we don't need).
|
||||
let probe = unsafe {
|
||||
GetVCPFeatureAndVCPFeatureReply(
|
||||
handle,
|
||||
VCP_POWER_MODE,
|
||||
None,
|
||||
&mut current,
|
||||
Some(&mut max),
|
||||
)
|
||||
};
|
||||
if probe == 0 {
|
||||
tracing::debug!(
|
||||
device,
|
||||
monitor = desc,
|
||||
"DDC/CI: no reply to the power-mode (0xD6) probe — skipping (no DDC/CI, \
|
||||
disabled in the OSD, or not passed through)"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// SAFETY: as the probe above — same live physical-monitor handle, plain value args.
|
||||
let set = unsafe { SetVCPFeature(handle, VCP_POWER_MODE, value) };
|
||||
if set == 0 {
|
||||
tracing::warn!(
|
||||
device,
|
||||
monitor = desc,
|
||||
value,
|
||||
"DDC/CI: power-mode set failed after a successful probe"
|
||||
);
|
||||
} else {
|
||||
tracing::info!(
|
||||
device,
|
||||
monitor = desc,
|
||||
from = current,
|
||||
to = value,
|
||||
"DDC/CI: panel power mode commanded"
|
||||
);
|
||||
acked += 1;
|
||||
}
|
||||
}
|
||||
// SAFETY: `phys` holds exactly the handles GetPhysicalMonitorsFromHMONITOR opened for us;
|
||||
// each is destroyed once, here.
|
||||
if let Err(e) = unsafe { DestroyPhysicalMonitors(&phys) } {
|
||||
tracing::debug!(device, "DDC/CI: DestroyPhysicalMonitors failed: {e}");
|
||||
}
|
||||
acked
|
||||
}
|
||||
|
||||
/// Command every physical panel EXCEPT `exclude_gdi` (the virtual display) off via DDC/CI
|
||||
/// (VCP 0xD6 → DPMS off). Call while the physical displays are still ACTIVE — i.e. immediately
|
||||
/// before the `Exclusive` CCD isolate. Returns how many panels acknowledged.
|
||||
pub fn panel_off_except(exclude_gdi: &str) -> u32 {
|
||||
let mut acked = 0;
|
||||
for m in active_monitors() {
|
||||
if m.device.eq_ignore_ascii_case(exclude_gdi) {
|
||||
continue;
|
||||
}
|
||||
acked += set_power(m.hmon, &m.device, POWER_OFF);
|
||||
}
|
||||
if acked == 0 {
|
||||
tracing::debug!(
|
||||
"DDC/CI: no physical panel accepted the DPMS-off command \
|
||||
(no DDC/CI-capable panel besides the virtual display)"
|
||||
);
|
||||
}
|
||||
acked
|
||||
}
|
||||
|
||||
/// Best-effort wake: command ON to every physical panel that answers. Call AFTER the CCD restore
|
||||
/// has re-activated the physical paths — the returning signal alone wakes DPMS-off panels on most
|
||||
/// firmware; this is the belt-and-braces for the rest.
|
||||
pub fn panel_on_all() -> u32 {
|
||||
let mut acked = 0;
|
||||
for m in active_monitors() {
|
||||
acked += set_power(m.hmon, &m.device, POWER_ON);
|
||||
}
|
||||
acked
|
||||
}
|
||||
Reference in New Issue
Block a user