diff --git a/crates/pf-capture/src/windows/idd_push/stall.rs b/crates/pf-capture/src/windows/idd_push/stall.rs index 17d81430..3b054c95 100644 --- a/crates/pf-capture/src/windows/idd_push/stall.rs +++ b/crates/pf-capture/src/windows/idd_push/stall.rs @@ -115,7 +115,7 @@ impl StallWatch { delivered no frame for the gap; the present path stalled below capture" ); if let Some(period) = stall.metronomic { - let suspects = pf_win_display::display_events::connected_inactive_externals(); + let suspects = pf_win_display::display_events::connected_inactive_physicals(); let suspects = if suspects.is_empty() { "none".to_string() } else { @@ -151,10 +151,13 @@ impl StallWatch { display-poller software (the SteelSeries-GG/SignalRGB class — \ correlate 'slow display-descriptor poll' lines), or the DWM present \ clock (try a different refresh rate). If connected_inactive lists a \ - display, its standby probing is the prime suspect: unplug it at the \ - GPU, disable its OSD auto input scan (TVs: instant-on/quick-start + \ - CEC off), use an HPD-holding adapter/dummy, or keep it active while \ - streaming" + display, its standby servicing is the prime suspect. For a LAPTOP \ + PANEL (the exclusive isolate deactivated it — the dark-but-connected \ + head is itself the disturbance on hybrid laptops): keep it active \ + with `topology: primary`, or try the `pnp_disable_monitors` axis. \ + For an external display: unplug it at the GPU, disable its OSD auto \ + input scan (TVs: instant-on/quick-start + CEC off), use an \ + HPD-holding adapter/dummy, or keep it active while streaming" ); } } diff --git a/crates/pf-vdisplay/src/vdisplay/ddc.rs b/crates/pf-vdisplay/src/vdisplay/ddc.rs index 0a695164..a88bfe4f 100644 --- a/crates/pf-vdisplay/src/vdisplay/ddc.rs +++ b/crates/pf-vdisplay/src/vdisplay/ddc.rs @@ -181,9 +181,14 @@ pub fn panel_off_except(exclude_gdi: &str) -> u32 { 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)" + // INFO, not debug: the user opted into this axis, so "it did nothing" is an answer they + // asked for. The common case is a laptop — internal eDP/LVDS panels have NO DDC/CI at + // all (their brightness/power runs over the driver's own channel), so `ddc_power_off` + // is structurally a no-op for them (reporter feedback 2026-07-27). + tracing::info!( + "DDC/CI: no panel accepted the DPMS-off command — the ddc_power_off axis did \ + nothing on this display set (internal eDP/LVDS panels expose no DDC/CI; external \ + monitors may have it disabled in the OSD or dropped by a dock/KVM)" ); } acked diff --git a/crates/pf-win-display/src/display_events.rs b/crates/pf-win-display/src/display_events.rs index 05001b95..49def5fd 100644 --- a/crates/pf-win-display/src/display_events.rs +++ b/crates/pf-win-display/src/display_events.rs @@ -156,15 +156,27 @@ pub fn summarize(events: &[DisplayEvent]) -> String { out.join(", ") } -/// The prime suspects for link-probe disturbances, from the cached inventory: external physical -/// displays that are CONNECTED but not part of the desktop (standby TV / input-switched monitor). -/// Rendered as `" ()"`. Never blocks on the CCD lock. -pub fn connected_inactive_externals() -> Vec { +/// The prime suspects for link-probe/dark-head disturbances, from the cached inventory: PHYSICAL +/// displays — external connectors AND internal panels — that are CONNECTED but not part of the +/// desktop. External = the classic standby TV / input-switched monitor; internal = the laptop +/// panel the exclusive isolate deactivated, whose driver-level servicing produces the identical +/// ~2 s metronome on hybrid laptops (field A/B 2026-07-27: reporter's Legion, exclusive +/// 16.3 stalls/min vs primary 0 — the old external-only filter reported `none` there and steered +/// the diagnosis AWAY from the real cause for hours). Virtual/indirect targets stay excluded +/// (precision rule). Rendered as `" ()"`. Never blocks on the CCD lock. +pub fn connected_inactive_physicals() -> Vec { let st = state().lock().unwrap(); st.inventory .iter() - .filter(|t| t.external_physical && !t.active) - .map(|t| format!("{} ({})", t.friendly, t.tech)) + .filter(|t| (t.external_physical || t.internal_panel) && !t.active) + .map(|t| { + let name = if t.friendly.is_empty() && t.internal_panel { + "laptop panel" + } else { + &t.friendly + }; + format!("{} ({})", name, t.tech) + }) .collect() } diff --git a/crates/pf-win-display/src/win_display.rs b/crates/pf-win-display/src/win_display.rs index 74677ccc..b9eb49be 100644 --- a/crates/pf-win-display/src/win_display.rs +++ b/crates/pf-win-display/src/win_display.rs @@ -844,15 +844,21 @@ pub unsafe fn count_other_active(keep_target_ids: &[u32]) -> Option { } /// One CONNECTED display target from a full (`QDC_ALL_PATHS`) CCD sweep — the disturbance- -/// attribution inventory. `external_physical` is the load-bearing bit: a standby TV/monitor on a -/// real connector is the prime suspect for the periodic link-probe stutter class, while internal -/// panels and indirect/virtual targets (our own IDD included) are not. +/// attribution inventory. `external_physical` and `internal_panel` are the load-bearing bits: a +/// standby TV/monitor on a real connector is the classic suspect for the periodic link-probe +/// stutter class, and a laptop panel the exclusive isolate DEACTIVATED is the hybrid-laptop +/// variant of the same disturbance (field A/B 2026-07-27: dark-but-connected eDP head on the +/// iGPU → ~2 s stall metronome; `topology: primary` → zero) — only indirect/virtual targets +/// (our own IDD included) can never be suspects. pub struct TargetInventory { pub target_id: u32, /// Whether any active path drives this target (part of the desktop right now). pub active: bool, /// External physical connector (HDMI/DP/DVI/…): candidate for standby link-probe churn. pub external_physical: bool, + /// Internal panel (eDP/LVDS/embedded): candidate for dark-head servicing churn when + /// connected-but-inactive (the exclusive isolate on a laptop creates exactly that state). + pub internal_panel: bool, /// Short connector label for logs (`"HDMI"`, `"DisplayPort"`, `"internal-panel"`, …). pub tech: &'static str, /// The monitor's friendly name (`"LG TV SSCR2"`); empty when the EDID carries none. @@ -953,6 +959,7 @@ pub unsafe fn target_inventory() -> Vec { target_id: t.id, active: active.contains(&key), external_physical, + internal_panel: tech == "internal-panel", tech, friendly: utf16z_str(&req.monitorFriendlyDeviceName), monitor_device_path: utf16z_str(&req.monitorDevicePath),