refactor(host/W6.2): extract the Windows display-topology cluster into the pf-win-display leaf crate

windows/{win_display,monitor_devnode,display_events}.rs move into
crates/pf-win-display: the CCD/GDI path-activation + mode-set + HDR
advanced-colour + source-rect helpers, the PnP monitor-devnode enable/disable
lever, and the WM_DISPLAYCHANGE / device-arrival watch. The coming pf-capture
crate's IDD-push capturer consumes all three; the host's pf-vdisplay backend
consumes win_display + monitor_devnode. A leaf lets both depend on them as a
PEER instead of the capturer reaching back into the orchestrator (plan §W6).

win_display's one external tie (crate::vdisplay::Mode) becomes the underlying
punktfunk_core::Mode; the cluster is otherwise self-contained (pf-paths for the
state file, serde_json for it, windows). pub(crate) items bump to pub at the
boundary; win_display carries a module-level allow(missing_safety_doc) to keep
the pre-carve behavior (the FFI helpers were pub(crate) unsafe fn with prose
safety docs — the lint only fires once they're pub, and this is an internal
publish=false leaf). The host imports the three modules at its crate root, so
every crate::{win_display,monitor_devnode,display_events}::* path is unchanged.

Verified: Linux clippy -D warnings (leaf empty + host
nvenc,vulkan-encode,pyrowave --all-targets); Windows clippy -D warnings
(pf-win-display --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:
2026-07-17 10:14:17 +02:00
parent b168790e0a
commit ccc4b08d45
9 changed files with 105 additions and 37 deletions
+32
View File
@@ -0,0 +1,32 @@
# The Windows display-topology cluster (plan §W6): CCD/GDI path activation, mode-setting, HDR
# advanced-colour toggles, source-rect geometry ([`win_display`]); PnP monitor devnode enable/disable
# ([`monitor_devnode`]); and the WM_DISPLAYCHANGE / device-arrival watch ([`display_events`]). A leaf
# so the IDD-push capturer (pf-capture) and the pf-vdisplay backend (host) depend on it as a PEER
# instead of the capturer reaching back into the host for display utilities. Windows-only content;
# compiles to an empty lib elsewhere.
[package]
name = "pf-win-display"
version = "0.12.0"
edition = "2021"
rust-version.workspace = true
license = "MIT OR Apache-2.0"
description = "punktfunk host Windows display-topology helpers: CCD/GDI mode-set + path activation, HDR advanced colour, PnP monitor devnodes, and the display-change event watch."
publish = false
[target.'cfg(target_os = "windows")'.dependencies]
# `Mode` (the negotiated display mode) is the core wire type; `pf-paths` for the pnp-disabled-monitors
# state file.
punktfunk-core = { path = "../punktfunk-core", features = ["quic"] }
pf-paths = { path = "../pf-paths" }
anyhow = "1"
tracing = "0.1"
# The pnp-disabled-monitors state file (a `Vec<String>` of instance ids) is serialized as JSON.
serde_json = "1"
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Devices_DeviceAndDriverInstallation",
"Win32_Devices_Display",
"Win32_Graphics_Gdi",
"Win32_UI_WindowsAndMessaging",
"Win32_System_LibraryLoader",
] }
@@ -1,6 +1,6 @@
//! OS display-event listener — the attribution sensor for the periodic-stutter disturbance class.
//!
//! The capture-stall watch ([`crate::capture::windows::idd_push`]) can SAY "DWM stopped composing
//! The capture-stall watch (the IDD-push capturer in `pf-capture`) can SAY "DWM stopped composing
//! on a stable period", but not WHY. Field evidence (Apollo's Stuttering Clinic, Apollo #384,
//! Tom's HW "stutter from disabled-but-connected monitors") points at a connected-but-idle sink
//! (standby TV/monitor, active HDMI cable, KVM/AVR) re-probing the link every few seconds; the GPU
@@ -49,7 +49,7 @@ use windows::Win32::UI::WindowsAndMessaging::{
/// One OS-visible display event, timestamped at receipt.
#[derive(Clone)]
pub(crate) struct DisplayEvent {
pub struct DisplayEvent {
pub at: Instant,
pub kind: DisplayEventKind,
/// Monitor device instance id for arrival/removal (e.g. `DISPLAY\GSM83CD\...`), else `None`.
@@ -57,7 +57,7 @@ pub(crate) struct DisplayEvent {
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum DisplayEventKind {
pub enum DisplayEventKind {
/// A monitor device interface ARRIVED — a sink (re)connected as Windows sees it.
MonitorArrival,
/// A monitor device interface was REMOVED — a sink dropped as Windows sees it.
@@ -103,7 +103,7 @@ fn state() -> &'static Mutex<State> {
/// Start the listener thread (idempotent). Degraded-not-fatal: if window/registration creation
/// fails the ring just stays empty — the stall log then reports "listener unavailable" naturally
/// via empty summaries, and streaming is unaffected.
pub(crate) fn spawn_once() {
pub fn spawn_once() {
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let spawned = std::thread::Builder::new()
@@ -119,7 +119,7 @@ pub(crate) fn spawn_once() {
}
/// Events with `from <= at <= to`, oldest-first.
pub(crate) fn events_between(from: Instant, to: Instant) -> Vec<DisplayEvent> {
pub fn events_between(from: Instant, to: Instant) -> Vec<DisplayEvent> {
let st = state().lock().unwrap();
st.events
.iter()
@@ -130,7 +130,7 @@ pub(crate) fn events_between(from: Instant, to: Instant) -> Vec<DisplayEvent> {
/// Compact one-line summary for log fields: `"monitor-removal x2 (DISPLAY\GSM83CD\...),
/// devnodes-changed x1"`; `"none"` when empty.
pub(crate) fn summarize(events: &[DisplayEvent]) -> String {
pub fn summarize(events: &[DisplayEvent]) -> String {
if events.is_empty() {
return "none".into();
}
@@ -159,7 +159,7 @@ pub(crate) fn summarize(events: &[DisplayEvent]) -> String {
/// 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 `"<friendly> (<connector>)"`. Never blocks on the CCD lock.
pub(crate) fn connected_inactive_externals() -> Vec<String> {
pub fn connected_inactive_externals() -> Vec<String> {
let st = state().lock().unwrap();
st.inventory
.iter()
+17
View File
@@ -0,0 +1,17 @@
//! Windows display-topology helpers (plan §W6), extracted from the host's `windows/{win_display,
//! monitor_devnode,display_events}.rs` so the IDD-push capturer (`pf-capture`) and the pf-vdisplay
//! backend (the host) depend on them as a leaf PEER instead of the capturer reaching back into the
//! orchestrator. Windows-only; compiles to an empty lib elsewhere.
//!
//! - [`win_display`]: CCD/GDI path activation, mode-setting, HDR advanced-colour toggles, and the
//! source-desktop geometry the capturer duplicates.
//! - [`monitor_devnode`]: PnP monitor devnode enable/disable (the parallel-display isolation lever).
//! - [`display_events`]: the `WM_DISPLAYCHANGE` / device-arrival watch that lets a capture stall say
//! whether an OS display event coincided with it.
#[cfg(target_os = "windows")]
pub mod display_events;
#[cfg(target_os = "windows")]
pub mod monitor_devnode;
#[cfg(target_os = "windows")]
pub mod win_display;
@@ -66,7 +66,7 @@ fn write_journal(ids: &[String]) {
/// The standard device-interface-path → instance-id transform: strip the `\\?\` prefix and the
/// trailing `#{interface-class-guid}`, then `#` separators become `\`.
// pub(crate): `display_events` applies the same transform to DBT_DEVICEARRIVAL interface paths.
pub(crate) fn instance_id_from_interface_path(path: &str) -> Option<String> {
pub fn instance_id_from_interface_path(path: &str) -> Option<String> {
let rest = path.strip_prefix(r"\\?\")?;
let cut = rest.rfind("#{")?;
Some(rest[..cut].replace('#', "\\"))
@@ -10,6 +10,12 @@
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program).
#![deny(clippy::undocumented_unsafe_blocks)]
// These CCD/GDI FFI helpers were `pub(crate) unsafe fn` before the pf-win-display carve (plan §W6),
// where `missing_safety_doc` stays silent; crossing the crate boundary makes them `pub` and would
// demand a `# Safety` heading on each. Their callers' obligations (call on the right desktop thread
// with a live OS target id) are stated in each fn's prose doc, and this is an internal
// (publish=false) leaf — keep the pre-carve behavior rather than adding 12 formal headings.
#![allow(clippy::missing_safety_doc)]
use std::mem::size_of;
@@ -41,7 +47,7 @@ use windows::Win32::Graphics::Gdi::{
ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_MODE,
};
use crate::vdisplay::Mode;
use punktfunk_core::Mode;
/// Force the desktop into EXTEND topology - the programmatic equivalent of the Win+P / DisplaySwitch
/// "Extend" shortcut. Windows defaults a FRESHLY-ADDED monitor into CLONE/duplicate mode when a
@@ -52,7 +58,7 @@ use crate::vdisplay::Mode;
/// OWN active path, so the rest of bring-up (`resolve_gdi_name` -> `set_active_mode` ->
/// `isolate_displays_ccd`) proceeds. Best-effort + idempotent: a no-op on a single-display (already
/// sole/extended) box, so it is safe to call unconditionally. `rc == 0` is success.
pub(crate) unsafe fn force_extend_topology() {
pub unsafe fn force_extend_topology() {
// A topology flag with no supplied path/mode arrays tells the OS to recompute + apply that preset
// for the currently-connected displays (the same code path DisplaySwitch.exe drives).
let rc = SetDisplayConfig(None, None, SDC_APPLY | SDC_TOPOLOGY_EXTEND);
@@ -78,7 +84,7 @@ pub(crate) unsafe fn force_extend_topology() {
/// source not already driving another display — mode indices invalidated so `SDC_ALLOW_CHANGES` lets
/// the OS pick modes for the new path. Returns `true` when the apply reports success; the caller
/// still re-polls [`resolve_gdi_name`] to confirm the path actually committed.
pub(crate) unsafe fn activate_target_path(target_id: u32) -> bool {
pub unsafe fn activate_target_path(target_id: u32) -> bool {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &mut np, &mut nm).is_err() {
@@ -169,7 +175,7 @@ pub(crate) unsafe fn activate_target_path(target_id: u32) -> bool {
/// Resolve the `\\.\DisplayN` GDI name for a virtual-display target id via the CCD API. Returns `None`
/// until the OS activates the target into the desktop topology (needs a real WDDM GPU; on a
/// GPU-less box this stays `None` even though ADD succeeded).
pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
pub unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() {
@@ -212,7 +218,7 @@ pub(crate) unsafe fn resolve_gdi_name(target_id: u32) -> Option<String> {
///
/// # Safety
/// Calls the GDI/CCD APIs; safe to call from any thread.
pub(crate) unsafe fn active_resolution(target_id: u32) -> Option<(u32, u32)> {
pub unsafe fn active_resolution(target_id: u32) -> Option<(u32, u32)> {
let gdi = resolve_gdi_name(target_id)?;
let wname: Vec<u16> = gdi.encode_utf16().chain(std::iter::once(0)).collect();
let mut dm = DEVMODEW {
@@ -230,7 +236,7 @@ pub(crate) unsafe fn active_resolution(target_id: u32) -> Option<(u32, u32)> {
/// secure (Winlogon) desktop makes it render SDR/composed so DXGI Desktop Duplication can capture it
/// (the HDR fullscreen independent-flip otherwise storms `ACCESS_LOST` → black); re-enable on return so
/// WGC keeps HDR on the normal desktop. Returns true on a successful `DisplayConfigSetDeviceInfo`.
pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool {
pub unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() {
@@ -283,7 +289,7 @@ pub(crate) unsafe fn set_advanced_color(target_id: u32, enable: bool) -> bool {
/// list (both happen transiently during a display-topology re-probe): the caller decides the fallback —
/// the capture loop's poller keeps the last known value, since reading a blip as "HDR off" used to cost
/// an HDR session TWO spurious ring recreates (false, then true again a poll later).
pub(crate) unsafe fn advanced_color_enabled(target_id: u32) -> Option<bool> {
pub unsafe fn advanced_color_enabled(target_id: u32) -> Option<bool> {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() {
@@ -324,9 +330,9 @@ pub(crate) unsafe fn advanced_color_enabled(target_id: u32) -> Option<bool> {
/// ADVERTISES the mode; Windows otherwise activates an IDD target at a 1280x720 default, so the
/// ACTIVE mode (what DXGI Desktop Duplication captures) must be set explicitly. CDS_TEST first so a
/// mode the driver didn't advertise just leaves the default instead of erroring the session.
// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD/GDI mode-set helper
// pub so vdisplay::pf_vdisplay can reuse this backend-neutral CCD/GDI mode-set helper
// (a pf-vdisplay monitor's GDI name is a real OS device name, so it works unchanged).
pub(crate) fn set_active_mode(gdi_name: &str, mode: Mode) {
pub fn set_active_mode(gdi_name: &str, mode: Mode) {
let wname: Vec<u16> = gdi_name.encode_utf16().chain(std::iter::once(0)).collect();
// Enumerate the modes the driver actually advertises for this output and pick the best match for
@@ -461,8 +467,8 @@ pub(crate) fn set_active_mode(gdi_name: &str, mode: Mode) {
}
/// Saved active display topology, for restoring on teardown.
// pub(crate) so vdisplay::pf_vdisplay's Monitor can hold the same saved-topology type.
pub(crate) type SavedConfig = (Vec<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_MODE_INFO>);
// pub so vdisplay::pf_vdisplay's Monitor can hold the same saved-topology type.
pub type SavedConfig = (Vec<DISPLAYCONFIG_PATH_INFO>, Vec<DISPLAYCONFIG_MODE_INFO>);
/// `DISPLAYCONFIG_PATH_ACTIVE` (wingdi.h) — the `flags` bit marking a path active. The `windows` crate
/// doesn't export it, so define it here.
@@ -505,7 +511,7 @@ unsafe fn query_active_config() -> Option<SavedConfig> {
/// that would still be lit besides the managed virtual set. `None` on query failure. Used to VERIFY
/// isolation actually took, and (in the `primary` topology) to detect a physical that is ALREADY
/// active so we can skip a force-EXTEND that would reset its refresh.
pub(crate) unsafe fn count_other_active(keep_target_ids: &[u32]) -> Option<u32> {
pub unsafe fn count_other_active(keep_target_ids: &[u32]) -> Option<u32> {
let (paths, _) = query_active_config()?;
Some(
paths
@@ -522,7 +528,7 @@ pub(crate) unsafe fn count_other_active(keep_target_ids: &[u32]) -> Option<u32>
/// 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.
pub(crate) struct TargetInventory {
pub struct TargetInventory {
pub target_id: u32,
/// Whether any active path drives this target (part of the desktop right now).
pub active: bool,
@@ -568,7 +574,7 @@ fn utf16z_str(buf: &[u16]) -> String {
/// matrix to unique targets) with its name, connector class and active state. Read-only CCD; can
/// briefly serialize on the display-config lock during topology churn — callers must keep it OFF
/// the capture thread (`display_events` runs it on its own listener thread and caches).
pub(crate) unsafe fn target_inventory() -> Vec<TargetInventory> {
pub unsafe fn target_inventory() -> Vec<TargetInventory> {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &mut np, &mut nm).is_err() {
@@ -647,9 +653,9 @@ pub(crate) unsafe fn target_inventory() -> Vec<TargetInventory> {
/// Re-issued with the grown/shrunk set on each slot add/remove while the group lives; the FIRST call's
/// returned config is what teardown restores (the caller keeps it on the group record and discards
/// later returns). Returns the original active config to restore on teardown.
// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD isolation helper
// pub so vdisplay::pf_vdisplay can reuse this backend-neutral CCD isolation helper
// (it operates on real OS target ids — a pf-vdisplay monitor's target_id qualifies).
pub(crate) unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option<SavedConfig> {
pub unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option<SavedConfig> {
// Snapshot the ORIGINAL active config ONCE for restore-on-teardown, before any changes.
let saved = query_active_config()?;
@@ -702,7 +708,7 @@ pub(crate) unsafe fn isolate_displays_ccd(keep_target_ids: &[u32]) -> Option<Sav
/// Used by the IDD-push compose kick to dirty THE TARGET display: with parallel displays the
/// cursor sits on ONE of them, and a cursor wiggle only dirties that one — a sibling display's
/// kick must first know where to send the cursor (Stage W3 on-glass finding).
pub(crate) unsafe fn source_desktop_rect(target_id: u32) -> Option<(i32, i32, i32, i32)> {
pub unsafe fn source_desktop_rect(target_id: u32) -> Option<(i32, i32, i32, i32)> {
let (paths, modes) = query_active_config()?;
for p in &paths {
if p.targetInfo.id != target_id || p.flags & DISPLAYCONFIG_PATH_ACTIVE == 0 {
@@ -730,7 +736,7 @@ pub(crate) unsafe fn source_desktop_rect(target_id: u32) -> Option<(i32, i32, i3
/// treats the source at `(0,0)` as primary, so auto-row's first member lands primary — the group's
/// designated member. Paths not named stay where they are. Best-effort: a failure leaves the OS
/// placement (mouse crossing may not match the layout table until the next apply).
pub(crate) unsafe fn apply_source_positions(positions: &[(u32, i32, i32)]) {
pub unsafe fn apply_source_positions(positions: &[(u32, i32, i32)]) {
if positions.len() < 2 {
return; // a single (or no) member sits at the origin — nothing to arrange
}
@@ -789,7 +795,7 @@ pub(crate) unsafe fn apply_source_positions(positions: &[(u32, i32, i32)]) {
/// atomic CCD `SetDisplayConfig` (NOT GDI `CDS_SET_PRIMARY`, which storms
/// `DXGI_ERROR_MODE_CHANGE_IN_PROGRESS` when another display is live — see [`set_active_mode`]).
/// Returns the original config to restore on teardown.
pub(crate) unsafe fn set_virtual_primary_ccd(keep_target_id: u32) -> Option<SavedConfig> {
pub unsafe fn set_virtual_primary_ccd(keep_target_id: u32) -> Option<SavedConfig> {
let mut np = 0u32;
let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() {
@@ -871,8 +877,8 @@ pub(crate) unsafe fn set_virtual_primary_ccd(keep_target_id: u32) -> Option<Save
/// Restore the topology saved by [`isolate_displays_ccd`] (teardown, before the virtual output is
/// removed), re-activating the displays we deactivated.
// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper.
pub(crate) unsafe fn restore_displays_ccd(saved: &SavedConfig) {
// pub so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper.
pub unsafe fn restore_displays_ccd(saved: &SavedConfig) {
let (paths, modes) = saved;
if paths.is_empty() {
return;
+3
View File
@@ -22,6 +22,9 @@ pf-zerocopy = { path = "../pf-zerocopy" }
# Shared frame/format vocabulary (CapturedFrame/PixelFormat/…), HDR metadata, thread QoS, and the
# Windows DXGI capture identity — the leaf both capture and encode speak (plan §W6).
pf-frame = { path = "../pf-frame" }
# Windows display-topology helpers (CCD/GDI mode-set, PnP monitor devnodes, display-change watch),
# extracted to a leaf crate (plan §W6). Empty on non-Windows, so it lives in the main deps.
pf-win-display = { path = "../pf-win-display" }
# M3 native control plane (the `punktfunk/1` QUIC handshake; data plane stays native-thread UDP).
quinn = "0.11"
anyhow = "1"
+4 -8
View File
@@ -32,9 +32,6 @@ mod crash;
#[cfg(target_os = "windows")]
#[path = "windows/ddc.rs"]
mod ddc;
#[cfg(target_os = "windows")]
#[path = "windows/display_events.rs"]
mod display_events;
#[cfg(target_os = "linux")]
#[path = "linux/drm_sync.rs"]
mod drm_sync;
@@ -56,9 +53,6 @@ mod library;
mod log_capture;
mod mgmt;
mod mgmt_token;
#[cfg(target_os = "windows")]
#[path = "windows/monitor_devnode.rs"]
mod monitor_devnode;
mod native;
mod native_pairing;
mod pipeline;
@@ -76,9 +70,11 @@ mod vdisplay;
#[cfg(target_os = "windows")]
#[path = "windows/win_adapter.rs"]
mod win_adapter;
// The Windows display-topology cluster (CCD/GDI mode-set, PnP monitor devnodes, the display-change
// watch) lives in the `pf-win-display` leaf crate (plan §W6); import the modules at the crate root
// so every existing `crate::{win_display,monitor_devnode,display_events}::*` path stays valid.
#[cfg(target_os = "windows")]
#[path = "windows/win_display.rs"]
mod win_display;
use pf_win_display::{display_events, monitor_devnode, win_display};
// The zero-copy GPU plumbing lives in the `pf-zerocopy` leaf crate (plan §W6); this shim keeps
// every existing `crate::zerocopy::*` path valid. `drm_fourcc` consumes the frame vocabulary, so
// it sits with `capture` and is re-exported here for its old callers.