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
Generated
+13
View File
@@ -2860,6 +2860,18 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "pf-win-display"
version = "0.12.0"
dependencies = [
"anyhow",
"pf-paths",
"punktfunk-core",
"serde_json",
"tracing",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "pf-zerocopy" name = "pf-zerocopy"
version = "0.12.0" version = "0.12.0"
@@ -3177,6 +3189,7 @@ dependencies = [
"pf-gpu", "pf-gpu",
"pf-host-config", "pf-host-config",
"pf-paths", "pf-paths",
"pf-win-display",
"pf-zerocopy", "pf-zerocopy",
"pipewire", "pipewire",
"punktfunk-core", "punktfunk-core",
+1
View File
@@ -15,6 +15,7 @@ members = [
"crates/pf-gpu", "crates/pf-gpu",
"crates/pf-zerocopy", "crates/pf-zerocopy",
"crates/pf-frame", "crates/pf-frame",
"crates/pf-win-display",
"crates/pyrowave-sys", "crates/pyrowave-sys",
"clients/probe", "clients/probe",
"clients/linux", "clients/linux",
+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. //! 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, //! 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 //! 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 //! (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. /// One OS-visible display event, timestamped at receipt.
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct DisplayEvent { pub struct DisplayEvent {
pub at: Instant, pub at: Instant,
pub kind: DisplayEventKind, pub kind: DisplayEventKind,
/// Monitor device instance id for arrival/removal (e.g. `DISPLAY\GSM83CD\...`), else `None`. /// 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)] #[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. /// A monitor device interface ARRIVED — a sink (re)connected as Windows sees it.
MonitorArrival, MonitorArrival,
/// A monitor device interface was REMOVED — a sink dropped as Windows sees it. /// 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 /// 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 /// fails the ring just stays empty — the stall log then reports "listener unavailable" naturally
/// via empty summaries, and streaming is unaffected. /// via empty summaries, and streaming is unaffected.
pub(crate) fn spawn_once() { pub fn spawn_once() {
static ONCE: Once = Once::new(); static ONCE: Once = Once::new();
ONCE.call_once(|| { ONCE.call_once(|| {
let spawned = std::thread::Builder::new() let spawned = std::thread::Builder::new()
@@ -119,7 +119,7 @@ pub(crate) fn spawn_once() {
} }
/// Events with `from <= at <= to`, oldest-first. /// 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(); let st = state().lock().unwrap();
st.events st.events
.iter() .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\...), /// Compact one-line summary for log fields: `"monitor-removal x2 (DISPLAY\GSM83CD\...),
/// devnodes-changed x1"`; `"none"` when empty. /// devnodes-changed x1"`; `"none"` when empty.
pub(crate) fn summarize(events: &[DisplayEvent]) -> String { pub fn summarize(events: &[DisplayEvent]) -> String {
if events.is_empty() { if events.is_empty() {
return "none".into(); 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 /// 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). /// 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. /// 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(); let st = state().lock().unwrap();
st.inventory st.inventory
.iter() .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 /// The standard device-interface-path → instance-id transform: strip the `\\?\` prefix and the
/// trailing `#{interface-class-guid}`, then `#` separators become `\`. /// trailing `#{interface-class-guid}`, then `#` separators become `\`.
// pub(crate): `display_events` applies the same transform to DBT_DEVICEARRIVAL interface paths. // 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 rest = path.strip_prefix(r"\\?\")?;
let cut = rest.rfind("#{")?; let cut = rest.rfind("#{")?;
Some(rest[..cut].replace('#', "\\")) Some(rest[..cut].replace('#', "\\"))
@@ -10,6 +10,12 @@
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program). // Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program).
#![deny(clippy::undocumented_unsafe_blocks)] #![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; use std::mem::size_of;
@@ -41,7 +47,7 @@ use windows::Win32::Graphics::Gdi::{
ENUM_CURRENT_SETTINGS, ENUM_DISPLAY_SETTINGS_MODE, 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 /// 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 /// "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` -> /// 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 /// `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. /// 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 // 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). // for the currently-connected displays (the same code path DisplaySwitch.exe drives).
let rc = SetDisplayConfig(None, None, SDC_APPLY | SDC_TOPOLOGY_EXTEND); 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 /// 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 /// 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. /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &mut np, &mut nm).is_err() { 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` /// 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 /// 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). /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { 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 /// # Safety
/// Calls the GDI/CCD APIs; safe to call from any thread. /// 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 gdi = resolve_gdi_name(target_id)?;
let wname: Vec<u16> = gdi.encode_utf16().chain(std::iter::once(0)).collect(); let wname: Vec<u16> = gdi.encode_utf16().chain(std::iter::once(0)).collect();
let mut dm = DEVMODEW { 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 /// 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 /// (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`. /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { 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 — /// 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 /// 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). /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { 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 /// 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 /// 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. /// 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). // (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(); 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 // 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. /// Saved active display topology, for restoring on teardown.
// pub(crate) so vdisplay::pf_vdisplay's Monitor can hold the same saved-topology type. // pub 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 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 /// `DISPLAYCONFIG_PATH_ACTIVE` (wingdi.h) — the `flags` bit marking a path active. The `windows` crate
/// doesn't export it, so define it here. /// 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 /// 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 /// 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. /// 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()?; let (paths, _) = query_active_config()?;
Some( Some(
paths 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 /// 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 /// 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. /// panels and indirect/virtual targets (our own IDD included) are not.
pub(crate) struct TargetInventory { pub struct TargetInventory {
pub target_id: u32, pub target_id: u32,
/// Whether any active path drives this target (part of the desktop right now). /// Whether any active path drives this target (part of the desktop right now).
pub active: bool, 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 /// 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 /// 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). /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &mut np, &mut nm).is_err() { 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 /// 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 /// 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. /// 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). // (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. // Snapshot the ORIGINAL active config ONCE for restore-on-teardown, before any changes.
let saved = query_active_config()?; 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 /// 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 /// 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). /// 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()?; let (paths, modes) = query_active_config()?;
for p in &paths { for p in &paths {
if p.targetInfo.id != target_id || p.flags & DISPLAYCONFIG_PATH_ACTIVE == 0 { 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 /// 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 /// 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). /// 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 { if positions.len() < 2 {
return; // a single (or no) member sits at the origin — nothing to arrange 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 /// 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`]). /// `DXGI_ERROR_MODE_CHANGE_IN_PROGRESS` when another display is live — see [`set_active_mode`]).
/// Returns the original config to restore on teardown. /// 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 np = 0u32;
let mut nm = 0u32; let mut nm = 0u32;
if GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &mut np, &mut nm).is_err() { 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 /// Restore the topology saved by [`isolate_displays_ccd`] (teardown, before the virtual output is
/// removed), re-activating the displays we deactivated. /// removed), re-activating the displays we deactivated.
// pub(crate) so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper. // pub so vdisplay::pf_vdisplay can reuse this backend-neutral CCD restore helper.
pub(crate) unsafe fn restore_displays_ccd(saved: &SavedConfig) { pub unsafe fn restore_displays_ccd(saved: &SavedConfig) {
let (paths, modes) = saved; let (paths, modes) = saved;
if paths.is_empty() { if paths.is_empty() {
return; 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 # 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). # Windows DXGI capture identity — the leaf both capture and encode speak (plan §W6).
pf-frame = { path = "../pf-frame" } 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). # M3 native control plane (the `punktfunk/1` QUIC handshake; data plane stays native-thread UDP).
quinn = "0.11" quinn = "0.11"
anyhow = "1" anyhow = "1"
+4 -8
View File
@@ -32,9 +32,6 @@ mod crash;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[path = "windows/ddc.rs"] #[path = "windows/ddc.rs"]
mod ddc; mod ddc;
#[cfg(target_os = "windows")]
#[path = "windows/display_events.rs"]
mod display_events;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[path = "linux/drm_sync.rs"] #[path = "linux/drm_sync.rs"]
mod drm_sync; mod drm_sync;
@@ -56,9 +53,6 @@ mod library;
mod log_capture; mod log_capture;
mod mgmt; mod mgmt;
mod mgmt_token; mod mgmt_token;
#[cfg(target_os = "windows")]
#[path = "windows/monitor_devnode.rs"]
mod monitor_devnode;
mod native; mod native;
mod native_pairing; mod native_pairing;
mod pipeline; mod pipeline;
@@ -76,9 +70,11 @@ mod vdisplay;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[path = "windows/win_adapter.rs"] #[path = "windows/win_adapter.rs"]
mod win_adapter; 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")] #[cfg(target_os = "windows")]
#[path = "windows/win_display.rs"] use pf_win_display::{display_events, monitor_devnode, win_display};
mod win_display;
// The zero-copy GPU plumbing lives in the `pf-zerocopy` leaf crate (plan §W6); this shim keeps // 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 // 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. // it sits with `capture` and is re-exported here for its old callers.