libobs_window_helper/
window.rs

1use std::{
2    ffi::OsString,
3    os::windows::ffi::OsStrExt,
4    path::{Path, PathBuf},
5    str::FromStr,
6};
7
8use crate::{get_thread_proc_id, string_conv::ToUtf8String, ProcessInfo};
9use anyhow::{anyhow, Result as AnyResult};
10use windows::{
11    core::HSTRING,
12    Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
13    Win32::{
14        Foundation::{CloseHandle, HANDLE, HWND, MAX_PATH, UNICODE_STRING},
15        Globalization::GetSystemDefaultLangID,
16        Graphics::Gdi::{
17            MonitorFromWindow, HMONITOR, MONITOR_DEFAULTTONEAREST, MONITOR_DEFAULTTONULL,
18        },
19        Storage::FileSystem::{
20            GetFileVersionInfoExW, GetFileVersionInfoSizeExW, VerQueryValueW, FILE_VER_GET_NEUTRAL,
21        },
22        System::{
23            Diagnostics::Debug::ReadProcessMemory,
24            ProcessStatus::GetModuleFileNameExW,
25            Threading::{
26                OpenProcess, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION,
27                PROCESS_TERMINATE, PROCESS_VM_READ,
28            },
29        },
30        UI::WindowsAndMessaging::{
31            GetClassNameW, GetWindowTextLengthW, GetWindowTextW,
32        },
33    },
34};
35use windows_result::Error;
36
37const SZ_STRING_FILE_INFO: &'static str = "StringFileInfo";
38const SZ_PRODUCT_NAME: &'static str = "ProductName";
39const SZ_HEX_CODE_PAGE_ID_UNICODE: &'static str = "04B0";
40
41/// Retrieves the executable path and process ID associated with the given window handle.
42///
43/// # Arguments
44///
45/// * `handle` - The handle to the window.
46///
47/// # Returns
48///
49/// Returns a tuple containing the process ID and the path to the executable.
50///
51/// # Errors
52///
53/// Returns an error if there was a problem retrieving the executable path or process ID.
54pub fn get_exe(handle: HWND) -> AnyResult<(u32, PathBuf)> {
55    let ProcessInfo { process_id: proc_id, .. } = get_thread_proc_id(handle)?;
56    let h_proc = unsafe {
57        OpenProcess(
58            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE,
59            false,
60            proc_id,
61        )?
62    };
63
64    let exe = unsafe {
65        let mut path = [0 as u16; MAX_PATH as usize];
66        // HMODULE should be null, not default
67        let res = GetModuleFileNameExW(Some(h_proc), None, &mut path);
68        if res > 0 {
69            Ok::<String, anyhow::Error>(path.as_ref().to_utf8())
70        } else {
71            Err(Error::from_win32().into())
72        }
73    }?;
74
75    unsafe {
76        CloseHandle(h_proc)?;
77    }
78
79    Ok((proc_id, PathBuf::from_str(&exe)?))
80}
81
82pub fn get_title(handle: HWND) -> AnyResult<String> {
83    let len = unsafe { GetWindowTextLengthW(handle) };
84    if len == 0 {
85        return Err(Error::from_win32().into());
86    }
87
88    let len = TryInto::<usize>::try_into(len)?;
89
90    let mut title = vec![0 as u16; len + 1];
91    let get_title_res = unsafe { GetWindowTextW(handle, &mut title) };
92    if get_title_res == 0 {
93        return Err(Error::from_win32().into());
94    }
95
96    Ok(title.to_utf8())
97}
98
99pub fn get_window_class(handle: HWND) -> AnyResult<String> {
100    let mut class = [0 as u16; MAX_PATH as usize +1];
101
102    let len = unsafe { GetClassNameW(handle, &mut class) };
103    if len == 0 {
104        return Err(Error::from_win32().into());
105    }
106
107    Ok(class.as_ref().to_utf8())
108}
109
110pub fn get_product_name(full_exe: &Path) -> AnyResult<String> {
111    let exe_wide = HSTRING::from(full_exe.as_os_str());
112
113    let mut dummy = 0;
114    let required_buffer_size =
115        unsafe { GetFileVersionInfoSizeExW(FILE_VER_GET_NEUTRAL, &exe_wide, &mut dummy) };
116    if required_buffer_size == 0 {
117        return Err(Error::from_win32().into());
118    }
119
120    let mut buffer: Vec<u16> = vec![0; required_buffer_size as usize];
121    unsafe {
122        GetFileVersionInfoExW(
123            FILE_VER_GET_NEUTRAL,
124            &exe_wide,
125            None,
126            required_buffer_size,
127            buffer.as_mut_ptr() as *mut _,
128        )?;
129    }
130
131    let lang_id = unsafe { GetSystemDefaultLangID() };
132    let query_key: Vec<u16> = OsString::from(format!(
133        "\\{}\\{}{}\\{}",
134        SZ_STRING_FILE_INFO, lang_id, SZ_HEX_CODE_PAGE_ID_UNICODE, SZ_PRODUCT_NAME
135    ))
136    .encode_wide()
137    .collect();
138    let query_key = HSTRING::from_wide(&query_key);
139
140    let mut pages_ptr: *mut u16 = std::ptr::null_mut();
141    let mut pages_length = 0;
142
143    unsafe {
144        VerQueryValueW(
145            buffer.as_mut_ptr() as _,
146            &query_key,
147            &mut pages_ptr as *mut _ as _,
148            &mut pages_length,
149        )
150        .ok()?
151    };
152
153    let chars_in_buf = required_buffer_size / (std::mem::size_of::<u16>() as u32);
154    if pages_ptr.is_null() || chars_in_buf < pages_length {
155        return Err(anyhow!("Invalid state"));
156    }
157
158    let product_name = unsafe { std::slice::from_raw_parts(pages_ptr, pages_length as usize - 1) };
159    let product_name = String::from_utf16_lossy(product_name);
160
161    Ok(product_name)
162}
163
164pub fn hwnd_to_monitor(handle: HWND) -> AnyResult<HMONITOR> {
165    unsafe {
166        let res = MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST);
167        if res.is_invalid() {
168            return Err(Error::from_win32().into());
169        }
170
171        Ok(res)
172    }
173}
174
175pub fn intersects_with_multiple_monitors(handle: HWND) -> AnyResult<bool> {
176    unsafe {
177        let res = MonitorFromWindow(handle, MONITOR_DEFAULTTONULL);
178
179        return Ok(!res.is_invalid());
180    }
181}
182
183pub fn get_command_line_args(wnd: HWND) -> AnyResult<String> {
184    let ProcessInfo { process_id: proc_id, ..} = get_thread_proc_id(wnd)?;
185
186    let handle = unsafe {
187        OpenProcess(
188            PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, //
189            false,                                       //
190            proc_id,                                     //
191        )?
192    };
193
194    if handle.is_invalid() {
195        return Err(Error::from_win32().into());
196    }
197
198    let res = unsafe { get_command_line_args_priv(handle) };
199    unsafe {
200        CloseHandle(handle)?;
201    }
202
203    res
204}
205
206unsafe fn get_command_line_args_priv(handle: HANDLE) -> AnyResult<String> {
207    let mut pbi = PROCESS_BASIC_INFORMATION::default();
208    // get process information
209    NtQueryInformationProcess(
210        handle,
211        ProcessBasicInformation,
212        &mut pbi as *mut _ as _,
213        size_of_val(&pbi) as u32,
214        std::ptr::null_mut(),
215    )
216    .ok()?;
217
218    // use WinDbg "dt ntdll!_PEB" command and search for ProcessParameters offset to find the truth out
219    let process_parameter_offset = 0x20;
220    let command_line_offset = 0x70;
221
222    // read basic info to get ProcessParameters address, we only need the beginning of PEB
223    let peb_size = process_parameter_offset + 8;
224    let mut peb = vec![0u8; peb_size];
225
226    // read basic info to get CommandLine address, we only need the beginning of ProcessParameters
227    let pp_size = command_line_offset + 16;
228    let mut pp = vec![0u8; pp_size];
229
230    // read PEB
231    ReadProcessMemory(
232        handle,
233        pbi.PebBaseAddress as _,
234        peb.as_mut_ptr() as _,
235        peb_size,
236        None,
237    )?;
238
239    // read ProcessParameters
240    let parameters = *(peb.as_ptr().add(process_parameter_offset) as *const *const u8); // address in remote process adress space
241
242    ReadProcessMemory(
243        handle,               //
244        parameters as _,      //
245        pp.as_mut_ptr() as _, //
246        pp_size,              //
247        None,                 //
248    )?;
249
250    let ptr_cmd_line = pp
251        .as_ptr() //
252        .add(command_line_offset) as *const UNICODE_STRING;
253
254    let maximum_len = (*ptr_cmd_line).MaximumLength as usize;
255    let mut cmd_line = vec![0u16; maximum_len];
256
257    ReadProcessMemory(
258        handle,
259        (*ptr_cmd_line).Buffer.as_ptr() as _,
260        cmd_line.as_mut_ptr() as _,
261        maximum_len,
262        None,
263    )?;
264
265    Ok(cmd_line.to_utf8())
266}