libobs_window_helper\util/
helper.rs1use 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)]
20pub struct WindowInfo {
22 pub full_exe: String,
24 pub obs_id: String,
26 #[cfg(all(not(feature = "serde"), not(feature = "specta")))]
27 pub handle: HWND,
29 pub pid: u32,
31 pub title: Option<String>,
33 pub class: Option<String>,
35 pub product_name: Option<String>,
37 pub monitor: Option<String>,
39 pub intersects: Option<bool>,
41 pub cmd_line: Option<String>,
43 pub is_game: bool,
45}
46
47fn encode_string(s: &str) -> String {
48 s.replace("#", "#22").replace(":", "#3A")
49}
50
51pub 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 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}