windows_capture/
window.rs1use std::ptr;
18
19use windows::Graphics::Capture::GraphicsCaptureItem;
20use windows::Win32::Foundation::{GetLastError, HWND, LPARAM, RECT, TRUE};
21use windows::Win32::Graphics::Dwm::{DWMWA_EXTENDED_FRAME_BOUNDS, DwmGetWindowAttribute};
22use windows::Win32::Graphics::Gdi::{MONITOR_DEFAULTTONULL, MonitorFromWindow};
23use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;
24use windows::Win32::System::Threading::{GetCurrentProcessId, OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
25use windows::Win32::System::WinRT::Graphics::Capture::IGraphicsCaptureItemInterop;
26use windows::Win32::UI::HiDpi::GetDpiForWindow;
27use windows::Win32::UI::WindowsAndMessaging::{
28 EnumChildWindows, FindWindowW, GWL_EXSTYLE, GWL_STYLE, GetClientRect, GetDesktopWindow, GetForegroundWindow,
29 GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW, GetWindowThreadProcessId, IsWindowVisible,
30 WS_CHILD, WS_EX_TOOLWINDOW,
31};
32use windows::core::{BOOL, HSTRING, Owned};
33
34use crate::monitor::Monitor;
35use crate::settings::GraphicsCaptureItemType;
36
37#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
38pub enum Error {
40 #[error("No active window found.")]
44 NoActiveWindow,
45 #[error("Failed to find a window with the name: {0}")]
49 NotFound(String),
50 #[error("Failed to convert a Windows string from UTF-16")]
52 FailedToConvertWindowsString,
53 #[error("A Windows API call failed: {0}")]
57 WindowsError(#[from] windows::core::Error),
58}
59
60#[derive(Eq, PartialEq, Clone, Copy, Debug)]
74pub struct Window {
75 window: HWND,
76}
77
78unsafe impl Send for Window {}
79
80impl Window {
81 #[inline]
87 pub fn foreground() -> Result<Self, Error> {
88 let window = unsafe { GetForegroundWindow() };
89
90 if window.is_invalid() {
91 return Err(Error::NoActiveWindow);
92 }
93
94 Ok(Self { window })
95 }
96
97 #[inline]
104 pub fn from_name(title: &str) -> Result<Self, Error> {
105 let hstring_title = HSTRING::from(title);
106 let window = unsafe { FindWindowW(None, &hstring_title)? };
107
108 if window.is_invalid() {
109 return Err(Error::NotFound(String::from(title)));
110 }
111
112 Ok(Self { window })
113 }
114
115 #[inline]
123 pub fn from_contains_name(title: &str) -> Result<Self, Error> {
124 let windows = Self::enumerate()?;
125
126 let mut target_window = None;
127 for window in windows {
128 if window.title()?.contains(title) {
129 target_window = Some(window);
130 break;
131 }
132 }
133
134 target_window.map_or_else(|| Err(Error::NotFound(String::from(title))), Ok)
135 }
136
137 #[inline]
143 pub fn title(&self) -> Result<String, Error> {
144 let len = unsafe { GetWindowTextLengthW(self.window) };
145
146 if len == 0 {
147 return Ok(String::new());
148 }
149
150 let mut buf = vec![0u16; usize::try_from(len).unwrap() + 1];
151 let copied = unsafe { GetWindowTextW(self.window, &mut buf) };
152 if copied == 0 {
153 return Ok(String::new());
154 }
155
156 let name = String::from_utf16(&buf[..copied as usize]).map_err(|_| Error::FailedToConvertWindowsString)?;
157
158 Ok(name)
159 }
160
161 #[inline]
167 pub fn process_id(&self) -> Result<u32, Error> {
168 let mut id = 0;
169 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
170
171 if id == 0 {
172 return Err(Error::WindowsError(unsafe { GetLastError().into() }));
173 }
174
175 Ok(id)
176 }
177
178 #[inline]
187 pub fn process_name(&self) -> Result<String, Error> {
188 let id = self.process_id()?;
189
190 let process = unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, id) }?;
191 let process = unsafe { Owned::new(process) };
192
193 let mut name = vec![0u16; 260];
194 let size = unsafe { GetModuleBaseNameW(*process, None, &mut name) };
195
196 if size == 0 {
197 return Err(Error::WindowsError(unsafe { GetLastError().into() }));
198 }
199
200 let name =
201 String::from_utf16(&name.as_slice().iter().take_while(|ch| **ch != 0x0000).copied().collect::<Vec<u16>>())
202 .map_err(|_| Error::FailedToConvertWindowsString)?;
203
204 Ok(name)
205 }
206
207 #[inline]
211 #[must_use]
212 pub fn monitor(&self) -> Option<Monitor> {
213 let window = self.window;
214
215 let monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
216
217 if monitor.is_invalid() { None } else { Some(Monitor::from_raw_hmonitor(monitor.0)) }
218 }
219
220 #[inline]
226 pub fn rect(&self) -> Result<RECT, Error> {
227 let mut rect = RECT::default();
228 let result = unsafe { GetWindowRect(self.window, &mut rect) };
229 if result.is_ok() { Ok(rect) } else { Err(Error::WindowsError(unsafe { GetLastError().into() })) }
230 }
231
232 #[inline]
238 pub fn title_bar_height(&self) -> Result<u32, Error> {
239 let mut window_rect = RECT::default();
240 let mut client_rect = RECT::default();
241
242 unsafe {
243 DwmGetWindowAttribute(
244 self.window,
245 DWMWA_EXTENDED_FRAME_BOUNDS,
246 &mut window_rect as *mut RECT as *mut std::ffi::c_void,
247 std::mem::size_of::<RECT>() as u32,
248 )
249 }?;
250
251 unsafe { GetClientRect(self.window, &mut client_rect) }?;
252
253 let window_height = window_rect.bottom - window_rect.top;
254 let dpi = unsafe { GetDpiForWindow(self.window) };
255 let client_height = (client_rect.bottom - client_rect.top) * dpi as i32 / 96;
256 let actual_title_height = (window_height - client_height) as i32;
257
258 Ok(actual_title_height as u32)
259 }
260
261 #[inline]
268 #[must_use]
269 pub fn is_valid(&self) -> bool {
270 if !unsafe { IsWindowVisible(self.window).as_bool() } {
271 return false;
272 }
273
274 let mut id = 0;
275 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
276 if id == unsafe { GetCurrentProcessId() } {
277 return false;
278 }
279
280 let mut rect = RECT::default();
281 let result = unsafe { GetClientRect(self.window, &mut rect) };
282 if result.is_ok() {
283 #[cfg(target_pointer_width = "64")]
284 let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) };
285 #[cfg(target_pointer_width = "64")]
286 let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) };
287
288 #[cfg(target_pointer_width = "32")]
289 let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) as isize };
290 #[cfg(target_pointer_width = "32")]
291 let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) as isize };
292
293 if (ex_styles & isize::try_from(WS_EX_TOOLWINDOW.0).unwrap()) != 0 {
294 return false;
295 }
296 if (styles & isize::try_from(WS_CHILD.0).unwrap()) != 0 {
297 return false;
298 }
299 } else {
300 return false;
301 }
302
303 true
304 }
305
306 #[inline]
312 pub fn enumerate() -> Result<Vec<Self>, Error> {
313 let mut windows: Vec<Self> = Vec::new();
314
315 unsafe {
316 EnumChildWindows(
317 Some(GetDesktopWindow()),
318 Some(Self::enum_windows_callback),
319 LPARAM(ptr::addr_of_mut!(windows) as isize),
320 )
321 .ok()?;
322 };
323
324 Ok(windows)
325 }
326
327 pub fn width(&self) -> Result<i32, Error> {
333 let rect = self.rect()?;
334 Ok(rect.right - rect.left)
335 }
336
337 pub fn height(&self) -> Result<i32, Error> {
343 let rect = self.rect()?;
344 Ok(rect.bottom - rect.top)
345 }
346
347 #[inline]
349 #[must_use]
350 pub const fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
351 Self { window: HWND(hwnd) }
352 }
353
354 #[inline]
356 #[must_use]
357 pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
358 self.window.0
359 }
360
361 #[inline]
363 unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL {
364 let windows = unsafe { &mut *(vec.0 as *mut Vec<Self>) };
365
366 if Self::from_raw_hwnd(window.0).is_valid() {
367 windows.push(Self { window });
368 }
369
370 TRUE
371 }
372}
373
374impl TryInto<GraphicsCaptureItemType> for Window {
375 type Error = windows::core::Error;
376
377 #[inline]
378 fn try_into(self) -> Result<GraphicsCaptureItemType, Self::Error> {
379 let window = HWND(self.as_raw_hwnd());
380
381 let interop = windows::core::factory::<GraphicsCaptureItem, IGraphicsCaptureItemInterop>()?;
382 let item = unsafe { interop.CreateForWindow(window)? };
383
384 Ok(GraphicsCaptureItemType::Window((item, self)))
385 }
386}