diff --git a/crates/punktfunk-host/src/capture.rs b/crates/punktfunk-host/src/capture.rs index 27ad110..3e91a67 100644 --- a/crates/punktfunk-host/src/capture.rs +++ b/crates/punktfunk-host/src/capture.rs @@ -320,6 +320,8 @@ pub fn capture_virtual_output(_vout: crate::vdisplay::VirtualOutput) -> Result, + stop: Arc, +} + +impl DesktopWatcher { + pub fn start() -> Self { + let state = Arc::new(AtomicU8::new(DESKTOP_NORMAL)); + let stop = Arc::new(AtomicBool::new(false)); + let s = state.clone(); + let st = stop.clone(); + let _ = std::thread::Builder::new() + .name("desktop-watch".into()) + .spawn(move || { + let mut last = u8::MAX; + while !st.load(Ordering::Relaxed) { + let v = if unsafe { is_secure_desktop() } { + DESKTOP_SECURE + } else { + DESKTOP_NORMAL + }; + s.store(v, Ordering::Release); + if v != last { + tracing::info!( + desktop = if v == DESKTOP_SECURE { + "Winlogon(secure)" + } else { + "Default" + }, + "input desktop changed" + ); + last = v; + } + std::thread::sleep(Duration::from_millis(20)); + } + }); + DesktopWatcher { state, stop } + } + + /// The shared atomic ([`DESKTOP_NORMAL`]/[`DESKTOP_SECURE`]) for the capture mux to read. + pub fn state(&self) -> Arc { + self.state.clone() + } + + /// True when the secure (Winlogon) desktop is the input desktop right now. + pub fn is_secure(&self) -> bool { + self.state.load(Ordering::Acquire) == DESKTOP_SECURE + } +} + +impl Drop for DesktopWatcher { + fn drop(&mut self) { + self.stop.store(true, Ordering::Relaxed); + } +} + +/// True if the current input desktop is "Winlogon" (the secure desktop). Best-effort: if the desktop +/// can't be opened or named, report not-secure (the safe default — keep WGC/normal capture). +unsafe fn is_secure_desktop() -> bool { + let desk = match OpenInputDesktop( + DESKTOP_CONTROL_FLAGS(0), + false, + DESKTOP_ACCESS_FLAGS(DESKTOP_READOBJECTS), + ) { + Ok(d) => d, + Err(_) => return false, + }; + let mut buf = [0u16; 64]; + let mut needed = 0u32; + let ok = GetUserObjectInformationW( + HANDLE(desk.0), + UOI_NAME, + Some(buf.as_mut_ptr() as *mut _), + (buf.len() * 2) as u32, + Some(&mut needed), + ) + .is_ok(); + let _ = CloseDesktop(desk); + if !ok { + return false; + } + let name = String::from_utf16_lossy(&buf); + name.trim_end_matches('\u{0}') + .eq_ignore_ascii_case("Winlogon") +} + +/// `DESKTOP_READOBJECTS` access right (the windows crate exposes it as a typed flag; we need the raw +/// bit for `OpenInputDesktop`'s access mask). +const DESKTOP_READOBJECTS: u32 = 0x0001;