//! Driver-free privacy mode. A fullscreen, topmost, opaque-black overlay marked //! `WDA_EXCLUDEFROMCAPTURE`, so: //! - the LOCAL monitor(s) show solid black — a bystander at the host sees //! nothing of what the remote operator is doing; //! - the host's OWN screen capture (DXGI Desktop Duplication + the GDI BitBlt //! fallback) EXCLUDES the overlay, so the remote operator still sees and //! controls the real desktop behind it. //! The overlay is also click-through + non-activating (WS_EX_TRANSPARENT | //! WS_EX_NOACTIVATE), so the operator's injected SendInput passes straight //! through to the real windows; blocking the LOCAL keyboard/mouse is the //! separate `block_input` toggle. No virtual-display driver is needed — verified //! by the `netdesk-privacy-probe` bin (WDA_EXCLUDEFROMCAPTURE hid a window from //! both DXGI and GDI capture, 197600 -> 0 magenta pixels). //! //! The overlay lives on its own thread with a message pump (a window must be //! serviced on its creating thread); dropping [`Privacy`] tears it down and //! restores the screen. #![cfg(windows)] use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread::JoinHandle; use std::time::Duration; 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, GetSystemMetrics, PeekMessageW, RegisterClassW, SetLayeredWindowAttributes, SetWindowDisplayAffinity, ShowWindow, TranslateMessage, LWA_ALPHA, MSG, PM_REMOVE, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_SHOWNA, WDA_EXCLUDEFROMCAPTURE, WNDCLASSW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_POPUP, WS_VISIBLE, }; unsafe extern "system" fn wndproc(h: HWND, m: u32, w: WPARAM, l: LPARAM) -> LRESULT { DefWindowProcW(h, m, w, l) } /// A live privacy-mode blackout. Drop it to restore the screen. pub struct Privacy { stop: Arc, thread: Option>, } impl Privacy { /// Raise the blackout overlay (returns once the window thread has started). pub fn start() -> Privacy { let stop = Arc::new(AtomicBool::new(false)); let s2 = stop.clone(); let thread = std::thread::Builder::new() .name("netdesk-privacy".into()) .spawn(move || unsafe { run(s2) }) .ok(); Privacy { stop, thread } } } impl Drop for Privacy { fn drop(&mut self) { self.stop.store(true, Ordering::Relaxed); if let Some(t) = self.thread.take() { let _ = t.join(); } } } unsafe fn run(stop: Arc) { let hinst = HINSTANCE::default(); let brush = CreateSolidBrush(COLORREF(0x0000_0000)); // solid black let class = w!("NdPrivacyBlackout"); let wc = WNDCLASSW { lpfnWndProc: Some(wndproc), hInstance: hinst, lpszClassName: class, hbrBackground: brush, ..Default::default() }; // 0 if the class is already registered (a prior toggle this process) — fine. let _ = RegisterClassW(&wc); // Cover the whole virtual desktop (all monitors). let x = GetSystemMetrics(SM_XVIRTUALSCREEN); let y = GetSystemMetrics(SM_YVIRTUALSCREEN); let cx = GetSystemMetrics(SM_CXVIRTUALSCREEN).max(1); let cy = GetSystemMetrics(SM_CYVIRTUALSCREEN).max(1); let hwnd = match CreateWindowExW( WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE, class, w!("nd-privacy"), WS_POPUP | WS_VISIBLE, x, y, cx, cy, None, None, hinst, None, ) { Ok(h) => h, Err(e) => { log::warn!("privacy: window create failed: {e}"); return; } }; // Opaque (alpha 255) so the overlay is solid black on the physical screen. let _ = SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA); // Exclude from THIS host's own capture so the remote still sees the desktop. if let Err(e) = SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE) { log::warn!("privacy: SetWindowDisplayAffinity failed: {e}"); } // Show without activating (keyboard focus stays on the real foreground app, // so injected keystrokes still land there). let _ = ShowWindow(hwnd, SW_SHOWNA); log::info!("privacy: blackout up ({cx}x{cy} @ {x},{y}); excluded from capture, click-through"); let mut msg = MSG::default(); while !stop.load(Ordering::Relaxed) { while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() { let _ = TranslateMessage(&msg); DispatchMessageW(&msg); } std::thread::sleep(Duration::from_millis(30)); } let _ = DestroyWindow(hwnd); log::info!("privacy: blackout down; screen restored"); }