feat(tools): display-disturb learns the bench moves the field sessions needed
ci / rust-arm64 (push) Successful in 9m0s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m3s
android / android (push) Successful in 18m29s
ci / bench (push) Successful in 5m27s
decky / build-publish (push) Successful in 28s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 19s
arch / build-publish (push) Successful in 19m47s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 20s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 14s
ci / rust (push) Failing after 20m23s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 28s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
deb / build-publish (push) Successful in 9m55s
deb / build-publish-client-arm64 (push) Successful in 8m33s
deb / build-publish-host (push) Successful in 12m56s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m54s
docker / build-push-arm64cross (push) Successful in 9s
docker / deploy-docs (push) Successful in 15s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m10s
apple / swift (push) Successful in 5m28s
apple / screenshots (push) Successful in 23m31s
ci / rust-arm64 (push) Successful in 9m0s
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m3s
android / android (push) Successful in 18m29s
ci / bench (push) Successful in 5m27s
decky / build-publish (push) Successful in 28s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 19s
arch / build-publish (push) Successful in 19m47s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 20s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 14s
ci / rust (push) Failing after 20m23s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 28s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
deb / build-publish (push) Successful in 9m55s
deb / build-publish-client-arm64 (push) Successful in 8m33s
deb / build-publish-host (push) Successful in 12m56s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m54s
docker / build-push-arm64cross (push) Successful in 9s
docker / deploy-docs (push) Successful in 15s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m10s
apple / swift (push) Successful in 5m28s
apple / screenshots (push) Successful in 23m31s
Three upgrades from the on-glass rounds on .173/.221: - `ddc-open` timing: the handle-ACQUISITION cost per monitor is reported even when it yields no physical handles — the poller's first contact with a virtual display, exactly what the DDC-fail-fast driver work changes; the old code silently skipped handle-less monitors, which on a streaming host in exclusive topology is every monitor there is. - Monitors are labeled by GDI device name (\\.\DISPLAYn) so a virtual display and a panel are tellable apart in the correlation log. - `extend` mode: one SDC_TOPOLOGY_EXTEND poke — re-activates every attachable display, i.e. the exact 'non-managed display re-activated' event the exclusive watchdog evicts. One shot, not a loop: trigger a reassert round, observe the recovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,14 +40,15 @@ mod win {
|
||||
Devices::Display::{
|
||||
CapabilitiesRequestAndCapabilitiesReply, DestroyPhysicalMonitor,
|
||||
GetCapabilitiesStringLength, GetNumberOfPhysicalMonitorsFromHMONITOR,
|
||||
GetPhysicalMonitorsFromHMONITOR, GetVCPFeatureAndVCPFeatureReply, PHYSICAL_MONITOR,
|
||||
GetPhysicalMonitorsFromHMONITOR, GetVCPFeatureAndVCPFeatureReply, SetDisplayConfig,
|
||||
PHYSICAL_MONITOR, SDC_APPLY, SDC_TOPOLOGY_EXTEND,
|
||||
},
|
||||
Foundation::{HANDLE, LPARAM, RECT},
|
||||
Graphics::Gdi::{
|
||||
ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplayMonitors,
|
||||
EnumDisplaySettingsW, CDS_RESET, DEVMODEW, DISPLAY_DEVICEW,
|
||||
EnumDisplaySettingsW, GetMonitorInfoW, CDS_RESET, DEVMODEW, DISPLAY_DEVICEW,
|
||||
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, DISPLAY_DEVICE_MIRRORING_DRIVER,
|
||||
DISP_CHANGE_SUCCESSFUL, ENUM_CURRENT_SETTINGS, HDC, HMONITOR,
|
||||
DISP_CHANGE_SUCCESSFUL, ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFOEXW,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -78,9 +79,9 @@ mod win {
|
||||
fn parse_args() -> Args {
|
||||
let argv: Vec<String> = std::env::args().collect();
|
||||
let mode = argv.get(1).cloned().unwrap_or_default();
|
||||
if !matches!(mode.as_str(), "ddc" | "modeset") {
|
||||
if !matches!(mode.as_str(), "ddc" | "modeset" | "extend") {
|
||||
eprintln!(
|
||||
"usage: display-disturb <ddc|modeset> [--interval-ms N] [--caps] [--vcp 0xNN]"
|
||||
"usage: display-disturb <ddc|modeset|extend> [--interval-ms N] [--caps] [--vcp 0xNN]"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
@@ -125,21 +126,47 @@ mod win {
|
||||
);
|
||||
match args.mode.as_str() {
|
||||
"ddc" => ddc_loop(&args),
|
||||
"extend" => extend_once(),
|
||||
_ => modeset_loop(&args),
|
||||
}
|
||||
}
|
||||
|
||||
/// One `SetDisplayConfig(SDC_TOPOLOGY_EXTEND)` poke — re-activates every attachable display
|
||||
/// from the CCD database, i.e. exactly the "a non-managed display re-activated after the
|
||||
/// verified isolate" event the exclusive-topology watchdog exists to evict. Fired ONCE (not a
|
||||
/// loop): the point is to trigger one reassert round and observe the recovery — one
|
||||
/// `reassert-recover` trace, ONE ring recreate, stream alive.
|
||||
fn extend_once() -> ! {
|
||||
let t = Instant::now();
|
||||
// SAFETY: the no-buffers topology form of SetDisplayConfig; flags request the stored
|
||||
// EXTEND topology be applied — a pure CCD database operation with no pointers involved.
|
||||
let rc = unsafe { SetDisplayConfig(None, None, SDC_TOPOLOGY_EXTEND | SDC_APPLY) };
|
||||
report("topology-extend", "all", t.elapsed(), rc == 0);
|
||||
std::process::exit(if rc == 0 { 0 } else { 1 });
|
||||
}
|
||||
|
||||
// ---- Class 2: the DDC hammer ----
|
||||
|
||||
/// Collect the desktop's HMONITORs (the DDC handles hang off them).
|
||||
fn monitors() -> Vec<HMONITOR> {
|
||||
/// Collect the desktop's HMONITORs (the DDC handles hang off them), labeled by GDI device
|
||||
/// name (`\\.\DISPLAYn`) so a virtual display and a panel are tellable apart in the output.
|
||||
fn monitors() -> Vec<(HMONITOR, String)> {
|
||||
unsafe extern "system" fn cb(mon: HMONITOR, _dc: HDC, _rc: *mut RECT, out: LPARAM) -> BOOL {
|
||||
// SAFETY: `out.0` is the `&mut Vec<HMONITOR>` this enumeration call passed in below,
|
||||
// alive for the whole synchronous enumeration.
|
||||
unsafe { &mut *(out.0 as *mut Vec<HMONITOR>) }.push(mon);
|
||||
let mut info = MONITORINFOEXW::default();
|
||||
info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
|
||||
// SAFETY: `mon` is the live enumeration handle; `info.cbSize` is stamped for the EX
|
||||
// variant so the device-name field is filled.
|
||||
let name = if unsafe { GetMonitorInfoW(mon, &mut info.monitorInfo) }.as_bool() {
|
||||
let d = info.szDevice;
|
||||
String::from_utf16_lossy(&d[..d.iter().position(|&c| c == 0).unwrap_or(d.len())])
|
||||
} else {
|
||||
"?".into()
|
||||
};
|
||||
// SAFETY: `out.0` is the `&mut Vec<(HMONITOR, String)>` this enumeration call passed
|
||||
// in below, alive for the whole synchronous enumeration.
|
||||
unsafe { &mut *(out.0 as *mut Vec<(HMONITOR, String)>) }.push((mon, name));
|
||||
BOOL(1)
|
||||
}
|
||||
let mut v: Vec<HMONITOR> = Vec::new();
|
||||
let mut v: Vec<(HMONITOR, String)> = Vec::new();
|
||||
// SAFETY: null dc/clip = enumerate all display monitors; `cb` only touches the Vec whose
|
||||
// address rides in LPARAM for the duration of this synchronous call.
|
||||
let _ =
|
||||
@@ -147,18 +174,26 @@ mod win {
|
||||
v
|
||||
}
|
||||
|
||||
/// The physical (DDC-capable) monitors behind one HMONITOR, with their descriptions.
|
||||
fn physical_monitors(mon: HMONITOR) -> Vec<(HANDLE, String)> {
|
||||
/// The physical (DDC-capable) monitors behind one HMONITOR, with their descriptions. The
|
||||
/// handle-ACQUISITION timing is itself reported (`ddc-open`): it is the poller's first contact
|
||||
/// with every monitor — including a virtual one that then yields no handles — and on a
|
||||
/// streaming host in exclusive topology the virtual monitor is the only one there is, so the
|
||||
/// cost of this failing path is exactly what the DDC-fail-fast driver work changes.
|
||||
fn physical_monitors(mon: HMONITOR, label: &str) -> Vec<(HANDLE, String)> {
|
||||
let t = Instant::now();
|
||||
let mut count = 0u32;
|
||||
// SAFETY: valid HMONITOR from enumeration; `count` is a valid out-param.
|
||||
if unsafe { GetNumberOfPhysicalMonitorsFromHMONITOR(mon, &mut count) }.is_err()
|
||||
|| count == 0
|
||||
{
|
||||
report("ddc-open", label, t.elapsed(), false);
|
||||
return Vec::new();
|
||||
}
|
||||
let mut phys = vec![PHYSICAL_MONITOR::default(); count as usize];
|
||||
// SAFETY: `phys` holds exactly `count` entries as the API requires.
|
||||
if unsafe { GetPhysicalMonitorsFromHMONITOR(mon, &mut phys) }.is_err() {
|
||||
let got = unsafe { GetPhysicalMonitorsFromHMONITOR(mon, &mut phys) }.is_ok();
|
||||
report("ddc-open", label, t.elapsed(), got);
|
||||
if !got {
|
||||
return Vec::new();
|
||||
}
|
||||
phys.iter()
|
||||
@@ -175,10 +210,11 @@ mod win {
|
||||
}
|
||||
|
||||
fn ddc_loop(args: &Args) -> ! {
|
||||
let mut warned_none = false;
|
||||
loop {
|
||||
let mut any = false;
|
||||
for mon in monitors() {
|
||||
for (h, desc) in physical_monitors(mon) {
|
||||
for (mon, dev) in monitors() {
|
||||
for (h, desc) in physical_monitors(mon, &dev) {
|
||||
any = true;
|
||||
let label = if desc.is_empty() {
|
||||
"monitor".into()
|
||||
@@ -220,12 +256,13 @@ mod win {
|
||||
let _ = unsafe { DestroyPhysicalMonitor(h) };
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
if !any && !warned_none {
|
||||
warned_none = true;
|
||||
eprintln!(
|
||||
"no DDC-capable (physical) monitor found — the ddc mode needs a physical \
|
||||
display; virtual displays expose no DDC handle"
|
||||
"no DDC-capable (physical) monitor yielded handles — continuing anyway: the \
|
||||
ddc-open timing against handle-less monitors (virtual displays included) IS \
|
||||
the measurement on a streaming host"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
std::thread::sleep(args.interval);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user