147 lines
5.4 KiB
Rust
147 lines
5.4 KiB
Rust
//! Privacy-mode feasibility probe. Determines whether a window marked
|
|
//! `SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)` is hidden from THIS host's
|
|
//! own screen capture — both DXGI Desktop Duplication (`grab`) and the GDI
|
|
//! BitBlt fallback (`grab_seed`). If it is, a fullscreen black excluded window
|
|
//! is a real, driver-free privacy mode: the local monitor shows black while the
|
|
//! remote operator still captures the desktop behind it.
|
|
//!
|
|
//! Method: show a small solid-magenta popup at a known spot, capture with the
|
|
//! affinity OFF (control — magenta must appear) then ON (magenta must vanish),
|
|
//! and count magenta pixels in each frame. Safe: the window is small and shown
|
|
//! for ~2s, then destroyed. Developed by Akronlogic.
|
|
|
|
#![cfg(windows)]
|
|
|
|
use std::time::{Duration, Instant};
|
|
use windows::core::w;
|
|
use windows::Win32::Foundation::{COLORREF, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM};
|
|
use windows::Win32::Graphics::Gdi::CreateSolidBrush;
|
|
use windows::Win32::UI::WindowsAndMessaging::{
|
|
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, PeekMessageW, RegisterClassW,
|
|
SetWindowDisplayAffinity, ShowWindow, TranslateMessage, MSG, PM_REMOVE, SW_SHOW,
|
|
WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WNDCLASSW, WS_EX_TOPMOST, WS_POPUP, WS_VISIBLE,
|
|
};
|
|
|
|
use netdesk_host::capture::DxgiCapturer;
|
|
|
|
unsafe extern "system" fn wndproc(h: HWND, m: u32, w: WPARAM, l: LPARAM) -> LRESULT {
|
|
DefWindowProcW(h, m, w, l)
|
|
}
|
|
|
|
fn pump(ms: u64) {
|
|
let end = Instant::now() + Duration::from_millis(ms);
|
|
while Instant::now() < end {
|
|
unsafe {
|
|
let mut msg = MSG::default();
|
|
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
|
|
let _ = TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
std::thread::sleep(Duration::from_millis(15));
|
|
}
|
|
}
|
|
|
|
/// Count magenta (B=255,G=0,R=255 with tolerance) pixels in a BGRA buffer.
|
|
fn count_magenta(buf: &[u8]) -> usize {
|
|
buf.chunks_exact(4)
|
|
.filter(|p| p[0] >= 250 && p[1] <= 6 && p[2] >= 250)
|
|
.count()
|
|
}
|
|
|
|
/// Freshest DXGI frame over `ms` (None if the duplication reported no change).
|
|
fn freshest_dxgi(cap: &mut DxgiCapturer, ms: u64) -> Option<Vec<u8>> {
|
|
let end = Instant::now() + Duration::from_millis(ms);
|
|
let mut last = None;
|
|
while Instant::now() < end {
|
|
if let Ok(Some(f)) = cap.grab(80) {
|
|
last = Some(f);
|
|
}
|
|
}
|
|
last
|
|
}
|
|
|
|
fn main() {
|
|
unsafe {
|
|
let hinst = HINSTANCE::default();
|
|
let brush = CreateSolidBrush(COLORREF(0x00FF_00FF)); // B=FF,G=00,R=FF = magenta
|
|
let class = w!("NdPrivacyProbe");
|
|
let wc = WNDCLASSW {
|
|
lpfnWndProc: Some(wndproc),
|
|
hInstance: hinst,
|
|
lpszClassName: class,
|
|
hbrBackground: brush,
|
|
..Default::default()
|
|
};
|
|
let atom = RegisterClassW(&wc);
|
|
if atom == 0 {
|
|
eprintln!("RegisterClassW failed");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let hwnd = match CreateWindowExW(
|
|
WS_EX_TOPMOST,
|
|
class,
|
|
w!("nd-privacy-probe"),
|
|
WS_POPUP | WS_VISIBLE,
|
|
120,
|
|
120,
|
|
520,
|
|
380,
|
|
None,
|
|
None,
|
|
hinst,
|
|
None,
|
|
) {
|
|
Ok(h) => h,
|
|
Err(e) => {
|
|
eprintln!("CreateWindowExW failed: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let _ = ShowWindow(hwnd, SW_SHOW);
|
|
pump(400);
|
|
|
|
let mut cap = match DxgiCapturer::new(0) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("DxgiCapturer::new failed: {e}");
|
|
let _ = DestroyWindow(hwnd);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
// CONTROL: affinity OFF — the window must be visible to both captures.
|
|
let _ = SetWindowDisplayAffinity(hwnd, WDA_NONE);
|
|
pump(300);
|
|
let ctl_gdi = cap.grab_seed().map(|b| count_magenta(&b)).unwrap_or(usize::MAX);
|
|
let ctl_dxgi = freshest_dxgi(&mut cap, 700).map(|b| count_magenta(&b));
|
|
|
|
// EXCLUDE: affinity ON — the window must vanish from capture.
|
|
let _ = SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE);
|
|
pump(400);
|
|
let exc_gdi = cap.grab_seed().map(|b| count_magenta(&b)).unwrap_or(usize::MAX);
|
|
let exc_dxgi = freshest_dxgi(&mut cap, 900).map(|b| count_magenta(&b));
|
|
|
|
let _ = DestroyWindow(hwnd);
|
|
|
|
println!("PRIVACY_PROBE control_gdi_magenta={ctl_gdi} exclude_gdi_magenta={exc_gdi}");
|
|
println!(
|
|
"PRIVACY_PROBE control_dxgi_magenta={:?} exclude_dxgi_magenta={:?}",
|
|
ctl_dxgi, exc_dxgi
|
|
);
|
|
|
|
let gdi_ok = ctl_gdi > 1000 && exc_gdi == 0;
|
|
// DXGI: control should show magenta; exclude should show none. If the
|
|
// duplication reported no change in a phase (None), note it.
|
|
let dxgi_ok = matches!((ctl_dxgi, exc_dxgi), (Some(c), Some(e)) if c > 1000 && e == 0);
|
|
if gdi_ok && dxgi_ok {
|
|
println!("VERDICT: PRIVACY_WDA_WORKS — WDA_EXCLUDEFROMCAPTURE hides the window from BOTH DXGI and GDI capture");
|
|
} else if gdi_ok {
|
|
println!("VERDICT: PRIVACY_WDA_GDI_ONLY — GDI excludes it; DXGI result {:?}/{:?} (inconclusive or not excluded)", ctl_dxgi, exc_dxgi);
|
|
} else {
|
|
println!("VERDICT: PRIVACY_WDA_FAILS — the window was still captured; privacy needs an IDD virtual display");
|
|
}
|
|
}
|
|
}
|