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::{
|
Devices::Display::{
|
||||||
CapabilitiesRequestAndCapabilitiesReply, DestroyPhysicalMonitor,
|
CapabilitiesRequestAndCapabilitiesReply, DestroyPhysicalMonitor,
|
||||||
GetCapabilitiesStringLength, GetNumberOfPhysicalMonitorsFromHMONITOR,
|
GetCapabilitiesStringLength, GetNumberOfPhysicalMonitorsFromHMONITOR,
|
||||||
GetPhysicalMonitorsFromHMONITOR, GetVCPFeatureAndVCPFeatureReply, PHYSICAL_MONITOR,
|
GetPhysicalMonitorsFromHMONITOR, GetVCPFeatureAndVCPFeatureReply, SetDisplayConfig,
|
||||||
|
PHYSICAL_MONITOR, SDC_APPLY, SDC_TOPOLOGY_EXTEND,
|
||||||
},
|
},
|
||||||
Foundation::{HANDLE, LPARAM, RECT},
|
Foundation::{HANDLE, LPARAM, RECT},
|
||||||
Graphics::Gdi::{
|
Graphics::Gdi::{
|
||||||
ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplayMonitors,
|
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,
|
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 {
|
fn parse_args() -> Args {
|
||||||
let argv: Vec<String> = std::env::args().collect();
|
let argv: Vec<String> = std::env::args().collect();
|
||||||
let mode = argv.get(1).cloned().unwrap_or_default();
|
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!(
|
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);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
@@ -125,21 +126,47 @@ mod win {
|
|||||||
);
|
);
|
||||||
match args.mode.as_str() {
|
match args.mode.as_str() {
|
||||||
"ddc" => ddc_loop(&args),
|
"ddc" => ddc_loop(&args),
|
||||||
|
"extend" => extend_once(),
|
||||||
_ => modeset_loop(&args),
|
_ => 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 ----
|
// ---- Class 2: the DDC hammer ----
|
||||||
|
|
||||||
/// Collect the desktop's HMONITORs (the DDC handles hang off them).
|
/// Collect the desktop's HMONITORs (the DDC handles hang off them), labeled by GDI device
|
||||||
fn monitors() -> Vec<HMONITOR> {
|
/// 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 {
|
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,
|
let mut info = MONITORINFOEXW::default();
|
||||||
// alive for the whole synchronous enumeration.
|
info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
|
||||||
unsafe { &mut *(out.0 as *mut Vec<HMONITOR>) }.push(mon);
|
// 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)
|
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
|
// 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.
|
// address rides in LPARAM for the duration of this synchronous call.
|
||||||
let _ =
|
let _ =
|
||||||
@@ -147,18 +174,26 @@ mod win {
|
|||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The physical (DDC-capable) monitors behind one HMONITOR, with their descriptions.
|
/// The physical (DDC-capable) monitors behind one HMONITOR, with their descriptions. The
|
||||||
fn physical_monitors(mon: HMONITOR) -> Vec<(HANDLE, String)> {
|
/// 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;
|
let mut count = 0u32;
|
||||||
// SAFETY: valid HMONITOR from enumeration; `count` is a valid out-param.
|
// SAFETY: valid HMONITOR from enumeration; `count` is a valid out-param.
|
||||||
if unsafe { GetNumberOfPhysicalMonitorsFromHMONITOR(mon, &mut count) }.is_err()
|
if unsafe { GetNumberOfPhysicalMonitorsFromHMONITOR(mon, &mut count) }.is_err()
|
||||||
|| count == 0
|
|| count == 0
|
||||||
{
|
{
|
||||||
|
report("ddc-open", label, t.elapsed(), false);
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let mut phys = vec![PHYSICAL_MONITOR::default(); count as usize];
|
let mut phys = vec![PHYSICAL_MONITOR::default(); count as usize];
|
||||||
// SAFETY: `phys` holds exactly `count` entries as the API requires.
|
// 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();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
phys.iter()
|
phys.iter()
|
||||||
@@ -175,10 +210,11 @@ mod win {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ddc_loop(args: &Args) -> ! {
|
fn ddc_loop(args: &Args) -> ! {
|
||||||
|
let mut warned_none = false;
|
||||||
loop {
|
loop {
|
||||||
let mut any = false;
|
let mut any = false;
|
||||||
for mon in monitors() {
|
for (mon, dev) in monitors() {
|
||||||
for (h, desc) in physical_monitors(mon) {
|
for (h, desc) in physical_monitors(mon, &dev) {
|
||||||
any = true;
|
any = true;
|
||||||
let label = if desc.is_empty() {
|
let label = if desc.is_empty() {
|
||||||
"monitor".into()
|
"monitor".into()
|
||||||
@@ -220,12 +256,13 @@ mod win {
|
|||||||
let _ = unsafe { DestroyPhysicalMonitor(h) };
|
let _ = unsafe { DestroyPhysicalMonitor(h) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !any {
|
if !any && !warned_none {
|
||||||
|
warned_none = true;
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"no DDC-capable (physical) monitor found — the ddc mode needs a physical \
|
"no DDC-capable (physical) monitor yielded handles — continuing anyway: the \
|
||||||
display; virtual displays expose no DDC handle"
|
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);
|
std::thread::sleep(args.interval);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user