Skip to main content

running_process/
console_detect.rs

1//! Windows console popup detection using the Win32 `EnumWindows` API.
2//!
3//! On non-Windows platforms every function is a no-op that returns an empty
4//! `Vec`.
5
6/// Metadata about a single visible window that appeared during monitoring.
7#[derive(Debug, Clone)]
8pub struct ConsoleWindowInfo {
9    /// Process id that owns the visible window.
10    pub pid: u32,
11    /// Window title captured at enumeration time.
12    pub title: String,
13    /// Native window handle represented as an integer for stable transport.
14    pub hwnd: u64,
15}
16
17// ---------------------------------------------------------------------------
18// Windows implementation
19// ---------------------------------------------------------------------------
20#[cfg(windows)]
21mod imp {
22    use super::ConsoleWindowInfo;
23    use std::collections::HashSet;
24    use std::time::{Duration, Instant};
25
26    use winapi::shared::minwindef::{BOOL, LPARAM, TRUE};
27    use winapi::shared::windef::HWND;
28    use winapi::um::winuser::{
29        EnumWindows, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible,
30    };
31
32    /// Enumerate all currently visible windows and return their metadata.
33    fn enumerate_visible_windows() -> Vec<ConsoleWindowInfo> {
34        let mut results: Vec<ConsoleWindowInfo> = Vec::new();
35
36        unsafe extern "system" fn enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
37            // Only consider visible windows.
38            if IsWindowVisible(hwnd) == 0 {
39                return TRUE; // continue enumeration
40            }
41
42            // Obtain the owning PID.
43            let mut pid: u32 = 0;
44            GetWindowThreadProcessId(hwnd, &mut pid);
45
46            // Read the window title into a wide‑char buffer.
47            let mut title_buf: [u16; 512] = [0u16; 512];
48            let len = GetWindowTextW(hwnd, title_buf.as_mut_ptr(), title_buf.len() as i32);
49            let title = if len > 0 {
50                String::from_utf16_lossy(&title_buf[..len as usize])
51            } else {
52                String::new()
53            };
54
55            let results = &mut *(lparam as *mut Vec<ConsoleWindowInfo>);
56            results.push(ConsoleWindowInfo {
57                pid,
58                title,
59                hwnd: hwnd as u64,
60            });
61
62            TRUE // continue enumeration
63        }
64
65        unsafe {
66            EnumWindows(
67                Some(enum_callback),
68                &mut results as *mut Vec<ConsoleWindowInfo> as LPARAM,
69            );
70        }
71
72        results
73    }
74
75    /// Monitor for **new** console windows that appear within `duration`.
76    ///
77    /// 1. Takes a snapshot of all currently visible window HWNDs.
78    /// 2. Polls every 50 ms for the given duration.
79    /// 3. Any HWND not present in the initial snapshot is collected.
80    pub fn monitor_console_windows(duration: Duration) -> Vec<ConsoleWindowInfo> {
81        // Initial snapshot — these windows already existed before monitoring.
82        let baseline: HashSet<u64> = enumerate_visible_windows().iter().map(|w| w.hwnd).collect();
83
84        let mut seen_new: HashSet<u64> = HashSet::new();
85        let mut new_windows: Vec<ConsoleWindowInfo> = Vec::new();
86
87        let start = Instant::now();
88        let poll_interval = Duration::from_millis(50);
89
90        while start.elapsed() < duration {
91            // #199: intentional — Win32 exposes no "new console window"
92            // event; enumerating top-level windows is the only way to
93            // detect new ones. 50ms poll trades responsiveness for CPU.
94            std::thread::sleep(poll_interval);
95
96            for info in enumerate_visible_windows() {
97                if !baseline.contains(&info.hwnd) && seen_new.insert(info.hwnd) {
98                    new_windows.push(info);
99                }
100            }
101        }
102
103        new_windows
104    }
105}
106
107#[cfg(windows)]
108pub use imp::monitor_console_windows;
109
110// ---------------------------------------------------------------------------
111// Non-Windows stub
112// ---------------------------------------------------------------------------
113#[cfg(not(windows))]
114/// Monitor for new console windows during `duration`.
115///
116/// Non-Windows platforms do not expose Win32 console popups, so this returns an
117/// empty list.
118pub fn monitor_console_windows(_duration: std::time::Duration) -> Vec<ConsoleWindowInfo> {
119    Vec::new()
120}