NetDesk 1.5.0 — source release
This commit is contained in:
commit
ec4955682f
227 files changed
+80669
No files matched your search
@@ -0,0 +1,131 @@
|
||||
//! 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<AtomicBool>,
|
||||
thread: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
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<AtomicBool>) {
|
||||
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");
|
||||
}
|
||||
Reference in new issue
Block a user