dxcapture/
window_finder.rs

1/// author: Robert Mikhayelyan <rob.mikh@outlook.com>
2
3use winapi::{
4    shared::{
5        minwindef::{BOOL, DWORD, LPARAM},
6        windef::HWND,
7    },
8    um::{
9        dwmapi::{DwmGetWindowAttribute, DWMWA_CLOAKED, DWM_CLOAKED_SHELL},
10        wincon::{GetConsoleTitleW, SetConsoleTitleW},
11        winuser::{
12            EnumWindows, GetAncestor, GetClassNameW, GetShellWindow, GetWindowLongW,
13            GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, GA_ROOT, GWL_EXSTYLE, GWL_STYLE,
14            WS_DISABLED, WS_EX_TOOLWINDOW,
15        },
16    },
17};
18
19/// Window informations.
20#[derive(Debug, Clone)]
21pub struct WindowInfo {
22    pub handle: HWND,
23    pub title: String,
24    pub class_name: String,
25}
26
27fn get_shell_window() -> HWND {
28    unsafe { GetShellWindow() }
29}
30
31fn is_window_visible(window: HWND) -> bool {
32    unsafe { IsWindowVisible(window) == 1 }
33}
34
35fn is_root_window(window: HWND) -> bool {
36    unsafe { GetAncestor(window, GA_ROOT) == window }
37}
38
39fn match_title_and_class_name(window: &WindowInfo, title: &str, class_name: &str) -> bool {
40    window.title == title && window.class_name == class_name
41}
42
43fn is_known_blocked_window(window: &WindowInfo) -> bool {
44    match_title_and_class_name(window, "Task View", "Windows.UI.Core.CoreWindow")
45        || match_title_and_class_name(
46            window,
47            "DesktopWindowXamlSource",
48            "Windows.UI.Core.CoreWindow",
49        )
50        || match_title_and_class_name(window, "PopupHost", "Xaml_WindowedPopupClass")
51}
52
53fn is_capturable_window(window: &WindowInfo) -> bool {
54    if window.title.is_empty()
55        || window.handle == get_shell_window()
56        || !is_window_visible(window.handle)
57        || !is_root_window(window.handle)
58    {
59        return false;
60    }
61
62    let style = unsafe { GetWindowLongW(window.handle, GWL_STYLE) as u32 };
63    if style & WS_DISABLED > 0 {
64        return false;
65    }
66
67    let ex_style = unsafe { GetWindowLongW(window.handle, GWL_EXSTYLE) as u32 };
68    if ex_style & WS_EX_TOOLWINDOW > 0 {
69        return false;
70    }
71
72    if window.class_name == "Windows.UI.Core.CoreWindow"
73        || window.class_name == "ApplicationFrameWindow"
74    {
75        let mut cloaked = 0;
76        let result = unsafe {
77            winrt::ErrorCode(DwmGetWindowAttribute(
78                window.handle,
79                DWMWA_CLOAKED,
80                &mut cloaked as *mut _ as *mut _,
81                std::mem::size_of::<DWORD>() as u32,
82            ) as u32)
83            .ok()
84        };
85        if let Ok(_) = result {
86            if cloaked == DWM_CLOAKED_SHELL {
87                return false;
88            }
89        }
90    }
91
92    if is_known_blocked_window(window) {
93        return false;
94    }
95
96    return true;
97}
98
99extern "system" fn enum_window(handle: HWND, lparam: LPARAM) -> BOOL {
100    let window_text_length = unsafe { GetWindowTextLengthW(handle) };
101    if window_text_length > 0 {
102        let window_text = unsafe {
103            let window_text_length = window_text_length + 1;
104            let mut text_array = vec![0u16; window_text_length as usize];
105            GetWindowTextW(
106                handle,
107                text_array.as_mut_ptr() as *mut _,
108                window_text_length,
109            );
110            std::string::String::from_utf16_lossy(&text_array)
111                .trim_matches(char::from(0))
112                .to_string()
113        };
114        let class_name = unsafe {
115            let class_text_length: i32 = 256;
116            let mut text_array = vec![0u16; class_text_length as usize];
117            GetClassNameW(handle, text_array.as_mut_ptr() as *mut _, class_text_length);
118            std::string::String::from_utf16_lossy(&text_array)
119                .trim_matches(char::from(0))
120                .to_string()
121        };
122        let info = WindowInfo {
123            handle: handle,
124            title: window_text,
125            class_name: class_name,
126        };
127
128        if !is_capturable_window(&info) {
129            return 1;
130        }
131
132        unsafe {
133            let list = std::mem::transmute::<LPARAM, *mut Vec<WindowInfo>>(lparam);
134            (*list).push(info);
135        };
136    }
137
138    return 1;
139}
140
141/// Finds all visible windows and returns them as a Vec<[`WindowInfo`]>.
142pub fn get_capturable_windows() -> Vec<WindowInfo> {
143    // https://support.microsoft.com/en-us/help/124103/how-to-obtain-a-console-window-handle-hwnd
144    let current_console_title = unsafe {
145        let console_title_length: u32 = 256;
146        let mut text_array = vec![0u16; console_title_length as usize];
147        GetConsoleTitleW(text_array.as_mut_ptr() as *mut _, console_title_length);
148        std::string::String::from_utf16_lossy(&text_array)
149            .trim_matches(char::from(0))
150            .to_string()
151    };
152
153    unsafe {
154        let temp_guid = uuid::Uuid::new_v4();
155        let mut new_console_title: Vec<u16> = temp_guid.to_string().encode_utf16().collect();
156        new_console_title.push(0);
157        SetConsoleTitleW(new_console_title.as_mut_ptr() as *mut _);
158    };
159    let duration = std::time::Duration::from_millis(40);
160    std::thread::sleep(duration);
161
162    let mut window_list = Vec::<WindowInfo>::new();
163    let result = unsafe { EnumWindows(Some(enum_window), &mut window_list as *mut _ as _) };
164    if result == 0 {
165        panic!("EnumWindows failed!");
166        // TODO: GetLastError
167        // TODO: ErrorCode conversion
168    }
169
170    unsafe {
171        let mut new_console_title: Vec<u16> = current_console_title.encode_utf16().collect();
172        new_console_title.push(0);
173        SetConsoleTitleW(new_console_title.as_mut_ptr() as *mut _);
174    };
175
176    window_list
177}
178
179pub fn find_window(window_name: &str) -> Vec<WindowInfo> {
180    let window_list = get_capturable_windows();
181    let mut windows: Vec<WindowInfo> = Vec::new();
182    for window_info in &window_list {
183        let title = window_info.title.to_lowercase();
184        if title.contains(&window_name.to_string().to_lowercase()) {
185            windows.push(window_info.clone());
186        }
187    }
188    windows
189}