libobs_window_helper\util/
helper.rs

1use anyhow::{anyhow, Result as AnyResult};
2use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GetWindowThreadProcessId};
3use windows_result::{Error, Result as WinResult};
4
5use crate::{
6    is_blacklisted_window,
7    monitor::get_monitor_id,
8    validators::is_microsoft_internal_exe,
9    window::{
10        get_command_line_args, get_exe, get_product_name, get_title, get_window_class,
11        hwnd_to_monitor, intersects_with_multiple_monitors,
12    },
13};
14
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(feature = "specta", derive(specta::Type))]
17#[derive(Debug, Clone)]
18/// Represents information about a window.
19pub struct WindowInfo {
20    /// The full path to the executable associated with the window.
21    pub full_exe: String,
22    /// The unique identifier of the window in OBS.
23    pub obs_id: String,
24    #[cfg(not(feature = "serde"))]
25    /// The handle to the window (only enabled when feature `serde` is disabled).
26    pub handle: HWND,
27    /// The process ID of the window.
28    pub pid: u32,
29    /// The title of the window.
30    pub title: Option<String>,
31    /// The class name of the window.
32    pub class: Option<String>,
33    /// The product name of the window.
34    pub product_name: Option<String>,
35    /// The monitor on which the window is located.
36    pub monitor: Option<String>,
37    /// Indicates whether the window is between multiple monitors.
38    pub intersects: Option<bool>,
39    /// The command line used to launch the process.
40    pub cmd_line: Option<String>,
41    /// If this window can be recorded using a game capture source.
42    pub is_game: bool,
43}
44
45fn encode_string(s: &str) -> String {
46    s.replace("#", "#22").replace(":", "#3A")
47}
48
49/// Retrieves the OBS window information associated with the given window handle.
50///
51/// # Arguments
52///
53/// * `handle` - The handle to the window.
54/// * `is_game` - If this flag is true, only game windows (that can be captured by the game source) are considered. Otherwise `window_capture` source info is returned.
55///
56/// # Returns
57///
58/// Returns the OBS window information as struct
59///
60/// # Errors
61///
62/// Returns an error if there was a problem retrieving the OBS ID.
63pub fn get_window_info(wnd: HWND) -> AnyResult<WindowInfo> {
64    let (proc_id, full_exe) = get_exe(wnd)?;
65    let exe = full_exe
66        .file_name()
67        .ok_or(anyhow!("Failed to get file name"))?;
68    let exe = exe.to_str().ok_or(anyhow!("Failed to convert to str"))?;
69    let exe = exe.to_string();
70
71    if is_microsoft_internal_exe(&exe) {
72        return Err(anyhow!("Handle is a Microsoft internal exe"));
73    }
74
75    if exe == "obs64.exe" {
76        return Err(anyhow!("Handle is obs64.exe"));
77    }
78
79    let is_game = !is_blacklisted_window(&exe);
80
81    let title = get_title(wnd).ok();
82    let class = get_window_class(wnd).ok();
83
84    let product_name = get_product_name(&full_exe).ok();
85    let monitor = Some(hwnd_to_monitor(wnd)?);
86    let intersects = intersects_with_multiple_monitors(wnd).ok();
87    let cmd_line = get_command_line_args(wnd).ok();
88    let monitor_id = monitor.map(|e| get_monitor_id(e).ok()).flatten();
89
90    let title_o = title.as_ref().map_or("", |v| v);
91    let class_o = class.as_ref().map_or("", |v| v);
92
93    let obs_id: Vec<String> = vec![title_o, class_o, &exe]
94        .into_iter()
95        .map(|e| encode_string(e))
96        .collect();
97
98    let obs_id = obs_id.join(":");
99    Ok(WindowInfo {
100        full_exe: full_exe.to_string_lossy().to_string(),
101        obs_id,
102        #[cfg(not(feature = "serde"))]
103        handle: wnd,
104        pid: proc_id,
105        title,
106        class,
107        product_name,
108        monitor: monitor_id,
109        intersects,
110        cmd_line,
111        is_game,
112    })
113}
114
115pub struct ProcessInfo {
116    pub process_id: u32,
117    pub thread_id: u32,
118}
119
120pub fn get_thread_proc_id(wnd: HWND) -> WinResult<ProcessInfo> {
121    let mut proc_id = 0u32;
122
123    let thread_id = unsafe { GetWindowThreadProcessId(wnd, Some(&mut proc_id)) };
124    if thread_id == 0 {
125        return Err(Error::from_win32());
126    }
127
128    Ok(ProcessInfo {
129        process_id: proc_id,
130        thread_id,
131    })
132}