feat(windows): pf-vdisplay — all-Rust IddCx virtual display (replaces SudoVDA)
P1 done: a pure-Rust UMDF2 IddCx driver, drop-in compatible with the host's
existing vdisplay/sudovda.rs control plane (the {e5bcc234} interface + the
SudoVDA IOCTL ABI), so the host drives it unchanged. Validated streaming on
glass at 5120x1440@240 — steady 240 fps, ~2.4 ms encode, clean teardown, full
parity with SudoVDA.
- Vendored wdf-umdf-sys / wdf-umdf bindgen crates (MIT, from virtual-display-rs)
+ the SDK-version build.rs fix that resolves the IddCxStub lib path by the WDK
version actually containing um\x64\iddcx, not the max base SDK.
- pf-vdisplay crate: entry/callbacks/context/control/monitor/edid/
swap_chain_processor. Our OWN 128-byte EDID (manufacturer PNK, product
punktfunk — no SudoVDA bytes), a real swap-chain drain (faithful vdd port,
required so DWM keeps compositing), the SudoVDA-compatible IOCTL control plane
(ADD/REMOVE/PING/GET_WATCHDOG/GET_VERSION/SET_RENDER_ADAPTER) + a watchdog that
tears down orphaned monitors when the host stops pinging.
- deploy-dev.ps1: stage + sign + stampinf (date.time DriverVer) + Inf2Cat +
install, codifying the "bump DriverVer or pnputil keeps the old binary" gotcha.
- docs/windows-virtual-display-rust-port.md: investigation, the on-glass
validation, and the two traps that cost time (Session-0 measurement +
accumulated device-state needing a reboot).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals, unused)]
|
||||
|
||||
mod bindings;
|
||||
mod ntstatus;
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
pub use bindings::*;
|
||||
pub use ntstatus::*;
|
||||
pub use paste::paste;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! WdfIsFunctionAvailable {
|
||||
($name:ident) => {{
|
||||
// SAFETY: We only ever do read access
|
||||
let higher = unsafe { $crate::WdfClientVersionHigherThanFramework } != 0;
|
||||
// SAFETY: We only ever do read access
|
||||
let fn_count = unsafe { $crate::WdfFunctionCount };
|
||||
|
||||
// https://github.com/microsoft/Windows-Driver-Frameworks/blob/main/src/publicinc/wdf/umdf/2.33/wdffuncenum.h#L126
|
||||
$crate::paste! {
|
||||
// index is always positive, see
|
||||
// https://github.com/microsoft/Windows-Driver-Frameworks/blob/main/src/publicinc/wdf/umdf/2.33/wdffuncenum.h
|
||||
const FN_INDEX: u32 = $crate::WDFFUNCENUM::[<$name TableIndex>].0 as u32;
|
||||
|
||||
FN_INDEX < $crate::WDF_ALWAYS_AVAILABLE_FUNCTION_COUNT
|
||||
|| !higher || FN_INDEX < fn_count
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! WdfIsStructureAvailable {
|
||||
($name:ident) => {{
|
||||
// SAFETY: We only ever do read access
|
||||
let higher = unsafe { $crate::WdfClientVersionHigherThanFramework } != 0;
|
||||
// SAFETY: We only ever do read access
|
||||
let struct_count = unsafe { $crate::WdfStructureCount };
|
||||
|
||||
// https://github.com/microsoft/Windows-Driver-Frameworks/blob/main/src/publicinc/wdf/umdf/2.33/wdffuncenum.h#L141
|
||||
$crate::paste! {
|
||||
// index is always positive, see
|
||||
// https://github.com/microsoft/Windows-Driver-Frameworks/blob/main/src/publicinc/wdf/umdf/2.33/wdffuncenum.h
|
||||
const STRUCT_INDEX: u32 = $crate::WDFSTRUCTENUM::[<INDEX_ $name>].0 as u32;
|
||||
|
||||
!higher || STRUCT_INDEX < struct_count
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! IddCxIsFunctionAvailable {
|
||||
($name:ident) => {{
|
||||
// SAFETY: We only ever do read access
|
||||
let higher = unsafe { $crate::IddClientVersionHigherThanFramework } != 0;
|
||||
// SAFETY: We only ever do read access
|
||||
let fn_count = unsafe { $crate::IddFunctionCount };
|
||||
|
||||
$crate::paste! {
|
||||
const FN_INDEX: u32 = $crate::IDDFUNCENUM::[<$name TableIndex>].0 as u32;
|
||||
|
||||
FN_INDEX < $crate::IDD_ALWAYS_AVAILABLE_FUNCTION_COUNT
|
||||
|| !higher || FN_INDEX < fn_count
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! IddCxIsStructureAvailable {
|
||||
($name:ident) => {{
|
||||
// SAFETY: We only ever do read access
|
||||
let higher = unsafe { $crate::IddClientVersionHigherThanFramework } != 0;
|
||||
// SAFETY: We only ever do read access
|
||||
let struct_count = unsafe { $crate::IddStructureCount };
|
||||
|
||||
$crate::paste! {
|
||||
const STRUCT_INDEX: u32 = $crate::IDDSTRUCTENUM::[<INDEX_ $name>].0 as u32;
|
||||
|
||||
!higher || STRUCT_INDEX < struct_count
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! WDF_STRUCTURE_SIZE {
|
||||
($name:ty) => {
|
||||
u32::try_from(::core::mem::size_of::<$name>()).expect("size is correct")
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! WDF_NO_HANDLE {
|
||||
() => {
|
||||
::core::ptr::null_mut()
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! WDF_NO_OBJECT_ATTRIBUTES {
|
||||
() => {
|
||||
::core::ptr::null_mut()
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE {
|
||||
($attr:ident, $context_type:ident) => {
|
||||
$attr.ContextTypeInfo = $context_type;
|
||||
};
|
||||
}
|
||||
|
||||
impl WDF_OBJECT_ATTRIBUTES {
|
||||
/// Initializes the [`WDF_OBJECT_ATTRIBUTES`] structure
|
||||
/// <https://github.com/microsoft/Windows-Driver-Frameworks/blob/a94b8c30dad524352fab90872aefc83920b98e56/src/publicinc/wdf/umdf/2.33/wdfobject.h#L136/>
|
||||
///
|
||||
/// Sets
|
||||
/// - `ExecutionLevel` to [`WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent`]
|
||||
/// - `SynchronizationScope` to [`WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent`]
|
||||
#[must_use]
|
||||
pub fn init() -> Self {
|
||||
// SAFETY: All fields are zero-able
|
||||
let mut attributes: Self = unsafe { ::core::mem::zeroed() };
|
||||
|
||||
attributes.Size = WDF_STRUCTURE_SIZE!(Self);
|
||||
attributes.SynchronizationScope =
|
||||
WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent;
|
||||
attributes.ExecutionLevel = WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent;
|
||||
|
||||
attributes
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn init_context_type(context_type: &_WDF_OBJECT_CONTEXT_TYPE_INFO) -> Self {
|
||||
let mut attr = Self::init();
|
||||
|
||||
WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE!(attr, context_type);
|
||||
|
||||
attr
|
||||
}
|
||||
}
|
||||
|
||||
impl WDF_DRIVER_CONFIG {
|
||||
/// Initializes the [`WDF_DRIVER_CONFIG`] structure
|
||||
/// <https://github.com/microsoft/Windows-Driver-Frameworks/blob/a94b8c30dad524352fab90872aefc83920b98e56/src/publicinc/wdf/umdf/2.33/wdfdriver.h#L134/>
|
||||
#[must_use]
|
||||
pub fn init(EvtDriverDeviceAdd: PFN_WDF_DRIVER_DEVICE_ADD) -> Self {
|
||||
// SAFETY: All fields are zero-able
|
||||
let mut config: Self = unsafe { core::mem::zeroed() };
|
||||
|
||||
config.Size = WDF_STRUCTURE_SIZE!(Self);
|
||||
|
||||
config.EvtDriverDeviceAdd = EvtDriverDeviceAdd;
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
impl WDF_PNPPOWER_EVENT_CALLBACKS {
|
||||
/// Initializes the [`WDF_PNPPOWER_EVENT_CALLBACKS`] structure
|
||||
/// <https://github.com/microsoft/Windows-Driver-Frameworks/blob/a94b8c30dad524352fab90872aefc83920b98e56/src/publicinc/wdf/umdf/2.33/wdfdevice.h#L1278/>
|
||||
#[must_use]
|
||||
pub fn init() -> Self {
|
||||
// SAFETY: All fields are zero-able
|
||||
let mut callbacks: Self = unsafe { core::mem::zeroed() };
|
||||
callbacks.Size = WDF_STRUCTURE_SIZE!(Self);
|
||||
|
||||
callbacks
|
||||
}
|
||||
}
|
||||
|
||||
/// If this returns None, the struct is NOT available to be used
|
||||
macro_rules! IDD_STRUCTURE_SIZE {
|
||||
($name:ty) => {{
|
||||
// SAFETY: We only ever do read access, copy is fine
|
||||
let higher = unsafe { IddClientVersionHigherThanFramework } != 0;
|
||||
// SAFETY: We only ever do read access, copy is fine
|
||||
let struct_count = unsafe { IddStructureCount };
|
||||
|
||||
if higher {
|
||||
// as u32 is fine, since there's no way there's > 4 billion structs
|
||||
const STRUCT_INDEX: u32 =
|
||||
$crate::paste! { IDDSTRUCTENUM::[<INDEX_ $name:upper>].0 as u32 };
|
||||
|
||||
// SAFETY: A pointer to a [size_t], copying the pointer is ok
|
||||
let ptr = unsafe { IddStructures };
|
||||
|
||||
if STRUCT_INDEX < struct_count {
|
||||
// SAFETY: we validated struct index is able to be accessed
|
||||
let ptr = unsafe { ptr.add(STRUCT_INDEX as usize) };
|
||||
// SAFETY: So it's ok to read
|
||||
u32::try_from(unsafe { ptr.read() }).ok()
|
||||
} else {
|
||||
// struct CANNOT be used
|
||||
None
|
||||
}
|
||||
} else {
|
||||
u32::try_from(::std::mem::size_of::<$name>()).ok()
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl IDD_CX_CLIENT_CONFIG {
|
||||
#[must_use]
|
||||
pub fn init() -> Option<Self> {
|
||||
// SAFETY: All fields are zero-able
|
||||
let mut config: Self = unsafe { core::mem::zeroed() };
|
||||
|
||||
config.Size = IDD_STRUCTURE_SIZE!(IDD_CX_CLIENT_CONFIG)?;
|
||||
|
||||
Some(config)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user