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