Skip to main content

running_process_core/
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    pub pid: u32,
10    pub title: String,
11    pub hwnd: u64,
12}
13
14// ---------------------------------------------------------------------------
15// Windows implementation
16// ---------------------------------------------------------------------------
17#[cfg(windows)]
18mod imp {
19    use super::ConsoleWindowInfo;
20    use std::collections::HashSet;
21    use std::time::{Duration, Instant};
22
23    use winapi::shared::minwindef::{BOOL, LPARAM, TRUE};
24    use winapi::shared::windef::HWND;
25    use winapi::um::winuser::{
26        EnumWindows, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible,
27    };
28
29    /// Enumerate all currently visible windows and return their metadata.
30    fn enumerate_visible_windows() -> Vec<ConsoleWindowInfo> {
31        let mut results: Vec<ConsoleWindowInfo> = Vec::new();
32
33        unsafe extern "system" fn enum_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
34            // Only consider visible windows.
35            if IsWindowVisible(hwnd) == 0 {
36                return TRUE; // continue enumeration
37            }
38
39            // Obtain the owning PID.
40            let mut pid: u32 = 0;
41            GetWindowThreadProcessId(hwnd, &mut pid);
42
43            // Read the window title into a wide‑char buffer.
44            let mut title_buf: [u16; 512] = [0u16; 512];
45            let len = GetWindowTextW(hwnd, title_buf.as_mut_ptr(), title_buf.len() as i32);
46            let title = if len > 0 {
47                String::from_utf16_lossy(&title_buf[..len as usize])
48            } else {
49                String::new()
50            };
51
52            let results = &mut *(lparam as *mut Vec<ConsoleWindowInfo>);
53            results.push(ConsoleWindowInfo {
54                pid,
55                title,
56                hwnd: hwnd as u64,
57            });
58
59            TRUE // continue enumeration
60        }
61
62        unsafe {
63            EnumWindows(
64                Some(enum_callback),
65                &mut results as *mut Vec<ConsoleWindowInfo> as LPARAM,
66            );
67        }
68
69        results
70    }
71
72    /// Monitor for **new** console windows that appear within `duration`.
73    ///
74    /// 1. Takes a snapshot of all currently visible window HWNDs.
75    /// 2. Polls every 50 ms for the given duration.
76    /// 3. Any HWND not present in the initial snapshot is collected.
77    pub fn monitor_console_windows(duration: Duration) -> Vec<ConsoleWindowInfo> {
78        // Initial snapshot — these windows already existed before monitoring.
79        let baseline: HashSet<u64> = enumerate_visible_windows()
80            .iter()
81            .map(|w| w.hwnd)
82            .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            std::thread::sleep(poll_interval);
92
93            for info in enumerate_visible_windows() {
94                if !baseline.contains(&info.hwnd) && seen_new.insert(info.hwnd) {
95                    new_windows.push(info);
96                }
97            }
98        }
99
100        new_windows
101    }
102}
103
104#[cfg(windows)]
105pub use imp::monitor_console_windows;
106
107// ---------------------------------------------------------------------------
108// Non-Windows stub
109// ---------------------------------------------------------------------------
110#[cfg(not(windows))]
111pub fn monitor_console_windows(_duration: std::time::Duration) -> Vec<ConsoleWindowInfo> {
112    Vec::new()
113}