From 7b1bc32c3fc68f27e565d7fb5fadf60fc1969bdd Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Mon, 25 May 2026 21:03:36 -0400 Subject: [PATCH] Harden window evidence adapters --- src/window/linux.rs | 43 ++++++++++++++++++++++++++++++++++++------- src/window/windows.rs | 30 +++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/window/linux.rs b/src/window/linux.rs index 3d6e6a6..4a9794e 100644 --- a/src/window/linux.rs +++ b/src/window/linux.rs @@ -27,8 +27,12 @@ pub fn get_snapshot() -> WindowSnapshot { pub fn minimize_other(title: &str) { let window_info = get_window_info(); - if &window_info.title != title { - run(&format!("xdotool windowminimize {}", window_info.wid)); + if window_info.wid.is_empty() { + return; + } + + if window_info.title != title { + run_xdotool(&["windowminimize", &window_info.wid]); } } @@ -38,8 +42,12 @@ struct WindowInfo { wid: String, } -fn run(cmd: &str) -> Option { - let output = Command::new("sh").arg("-c").arg(cmd).output(); +fn is_valid_window_id(wid: &str) -> bool { + !wid.is_empty() && wid.bytes().all(|byte| byte.is_ascii_digit()) +} + +fn run_xdotool(args: &[&str]) -> Option { + let output = Command::new("xdotool").args(args).output(); let Ok(Output { status, @@ -68,17 +76,38 @@ fn get_window_info() -> WindowInfo { wid: "".to_string(), }; - let Some(wid) = run("xdotool getactivewindow") else { + let Some(wid) = run_xdotool(&["getactivewindow"]) else { return none; }; - let Some(class) = run(&format!("xdotool getwindowclassname {wid}")) else { + if !is_valid_window_id(&wid) { return none; }; - let Some(title) = run(&format!("xdotool getwindowname {wid}")) else { + let Some(class) = run_xdotool(&["getwindowclassname", &wid]) else { + return none; + }; + + let Some(title) = run_xdotool(&["getwindowname", &wid]) else { return none; }; WindowInfo { title, class, wid } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn valid_window_id_accepts_ascii_digits_only() { + assert!(is_valid_window_id("12345")); + assert!(is_valid_window_id("0")); + + assert!(!is_valid_window_id("")); + assert!(!is_valid_window_id("12abc")); + assert!(!is_valid_window_id(" 123")); + assert!(!is_valid_window_id("123 ")); + assert!(!is_valid_window_id("123")); + } +} diff --git a/src/window/windows.rs b/src/window/windows.rs index 651ec07..ac7025d 100644 --- a/src/window/windows.rs +++ b/src/window/windows.rs @@ -10,19 +10,32 @@ pub fn get_title_clean() -> String { } pub fn get_snapshot() -> WindowSnapshot { - let title = get_window_info().title; + let window_info = get_window_info(); + if window_info.hwnd.is_null() { + return WindowSnapshot::unavailable("foreground window unavailable"); + } + let re = Regex::new(r"-?\d+([:.]\d+)+%?").unwrap(); + let title = re.replace_all(&window_info.title, "").to_string(); + let health = if window_info.title.is_empty() { + EvidenceHealth::Degraded("foreground window title unavailable".to_string()) + } else { + EvidenceHealth::Degraded("window class unavailable on current Windows adapter".to_string()) + }; + WindowSnapshot { - title: re.replace_all(&title, "").to_string(), + title, class: None, - health: EvidenceHealth::Degraded( - "window class unavailable on current Windows adapter".to_string(), - ), + health, } } pub fn minimize_other(title: &str) { let window_info = get_window_info(); + if window_info.hwnd.is_null() { + return; + } + if window_info.title != title { unsafe { ShowWindow(window_info.hwnd, SW_MINIMIZE); @@ -38,6 +51,13 @@ struct WindowInfo { fn get_window_info() -> WindowInfo { unsafe { let hwnd = GetForegroundWindow(); + if hwnd.is_null() { + return WindowInfo { + title: String::new(), + hwnd, + }; + } + let mut text: [u16; 512] = [0; 512]; let len = GetWindowTextW(hwnd, text.as_mut_ptr(), text.len() as i32) as usize; let title = OsString::from_wide(&text[..len])