libobs_window_helper\util/
helper.rs

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