windows_capture/
window.rs1use std::{ptr, string::FromUtf16Error};
2
3use windows::{
4 Graphics::Capture::GraphicsCaptureItem,
5 Win32::{
6 Foundation::{HWND, LPARAM, RECT, TRUE},
7 Graphics::Gdi::{MONITOR_DEFAULTTONULL, MonitorFromWindow},
8 System::{
9 ProcessStatus::GetModuleBaseNameW,
10 Threading::{
11 GetCurrentProcessId, OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
12 },
13 WinRT::Graphics::Capture::IGraphicsCaptureItemInterop,
14 },
15 UI::WindowsAndMessaging::{
16 EnumChildWindows, FindWindowW, GWL_EXSTYLE, GWL_STYLE, GetClientRect, GetDesktopWindow,
17 GetForegroundWindow, GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW,
18 GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible, WS_CHILD, WS_EX_TOOLWINDOW,
19 },
20 },
21 core::{BOOL, HSTRING, Owned},
22};
23
24use crate::monitor::Monitor;
25
26#[derive(thiserror::Error, Debug)]
27pub enum Error {
28 #[error("No active window found")]
29 NoActiveWindow,
30 #[error("Failed to find window with name: {0}")]
31 NotFound(String),
32 #[error("Failed to convert windows string from UTF-16: {0}")]
33 FailedToConvertWindowsString(#[from] FromUtf16Error),
34 #[error("Windows API error: {0}")]
35 WindowsError(#[from] windows::core::Error),
36}
37
38#[derive(Eq, PartialEq, Clone, Copy, Debug)]
52pub struct Window {
53 window: HWND,
54}
55
56unsafe impl Send for Window {}
57
58impl Window {
59 #[inline]
65 pub fn foreground() -> Result<Self, Error> {
66 let window = unsafe { GetForegroundWindow() };
67
68 if window.is_invalid() {
69 return Err(Error::NoActiveWindow);
70 }
71
72 Ok(Self { window })
73 }
74
75 #[inline]
85 pub fn from_name(title: &str) -> Result<Self, Error> {
86 let hstring_title = HSTRING::from(title);
87 let window = unsafe { FindWindowW(None, &hstring_title)? };
88
89 if window.is_invalid() {
90 return Err(Error::NotFound(String::from(title)));
91 }
92
93 Ok(Self { window })
94 }
95
96 #[inline]
106 pub fn from_contains_name(title: &str) -> Result<Self, Error> {
107 let windows = Self::enumerate()?;
108
109 let mut target_window = None;
110 for window in windows {
111 if window.title()?.contains(title) {
112 target_window = Some(window);
113 break;
114 }
115 }
116
117 target_window.map_or_else(|| Err(Error::NotFound(String::from(title))), Ok)
118 }
119
120 #[inline]
126 pub fn title(&self) -> Result<String, Error> {
127 let len = unsafe { GetWindowTextLengthW(self.window) };
128
129 let mut name = vec![0u16; usize::try_from(len).unwrap() + 1];
130 if len >= 1 {
131 let copied = unsafe { GetWindowTextW(self.window, &mut name) };
132 if copied == 0 {
133 return Ok(String::new());
134 }
135 }
136
137 let name = String::from_utf16(
138 &name
139 .as_slice()
140 .iter()
141 .take_while(|ch| **ch != 0x0000)
142 .copied()
143 .collect::<Vec<u16>>(),
144 )?;
145
146 Ok(name)
147 }
148
149 #[inline]
155 pub fn process_id(&self) -> Result<u32, Error> {
156 let mut id = 0;
157 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
158
159 if id == 0 {
160 return Err(windows::core::Error::from_win32().into());
161 }
162
163 Ok(id)
164 }
165
166 #[inline]
174 pub fn process_name(&self) -> Result<String, Error> {
175 let id = self.process_id()?;
176
177 let process =
178 unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, id) }?;
179 let process = unsafe { Owned::new(process) };
180
181 let mut name = vec![0u16; 260];
182 let size = unsafe { GetModuleBaseNameW(*process, None, &mut name) };
183
184 if size == 0 {
185 return Err(windows::core::Error::from_win32().into());
186 }
187
188 let name = String::from_utf16(
189 &name
190 .as_slice()
191 .iter()
192 .take_while(|ch| **ch != 0x0000)
193 .copied()
194 .collect::<Vec<u16>>(),
195 )?;
196
197 Ok(name)
198 }
199
200 #[must_use]
204 #[inline]
205 pub fn monitor(&self) -> Option<Monitor> {
206 let window = self.window;
207
208 let monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
209
210 if monitor.is_invalid() {
211 None
212 } else {
213 Some(Monitor::from_raw_hmonitor(monitor.0))
214 }
215 }
216
217 #[inline]
223 pub fn rect(&self) -> Result<RECT, Error> {
224 let mut rect = RECT::default();
225 let result = unsafe { GetWindowRect(self.window, &mut rect) };
226 if result.is_ok() {
227 Ok(rect)
228 } else {
229 Err(Error::WindowsError(windows::core::Error::from_win32()))
230 }
231 }
232
233 #[must_use]
239 #[inline]
240 pub fn is_valid(&self) -> bool {
241 if !unsafe { IsWindowVisible(self.window).as_bool() } {
242 return false;
243 }
244
245 let mut id = 0;
246 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
247 if id == unsafe { GetCurrentProcessId() } {
248 return false;
249 }
250
251 let mut rect = RECT::default();
252 let result = unsafe { GetClientRect(self.window, &mut rect) };
253 if result.is_ok() {
254 let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) };
255 let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) };
256
257 if (ex_styles & isize::try_from(WS_EX_TOOLWINDOW.0).unwrap()) != 0 {
258 return false;
259 }
260 if (styles & isize::try_from(WS_CHILD.0).unwrap()) != 0 {
261 return false;
262 }
263 } else {
264 return false;
265 }
266
267 true
268 }
269
270 #[inline]
276 pub fn enumerate() -> Result<Vec<Self>, Error> {
277 let mut windows: Vec<Self> = Vec::new();
278
279 unsafe {
280 EnumChildWindows(
281 Some(GetDesktopWindow()),
282 Some(Self::enum_windows_callback),
283 LPARAM(ptr::addr_of_mut!(windows) as isize),
284 )
285 .ok()?;
286 };
287
288 Ok(windows)
289 }
290
291 #[must_use]
297 #[inline]
298 pub const fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
299 Self { window: HWND(hwnd) }
300 }
301
302 #[must_use]
304 #[inline]
305 pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
306 self.window.0
307 }
308
309 #[inline]
311 unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL {
312 let windows = &mut *(vec.0 as *mut Vec<Self>);
313
314 if Self::from_raw_hwnd(window.0).is_valid() {
315 windows.push(Self { window });
316 }
317
318 TRUE
319 }
320}
321
322impl TryFrom<Window> for GraphicsCaptureItem {
324 type Error = Error;
325
326 #[inline]
327 fn try_from(value: Window) -> Result<Self, Self::Error> {
328 let window = HWND(value.as_raw_hwnd());
329
330 let interop = windows::core::factory::<Self, IGraphicsCaptureItemInterop>()?;
331 Ok(unsafe { interop.CreateForWindow(window)? })
332 }
333}