windows_capture/
window.rs1use std::ptr;
2
3use windows::Graphics::Capture::GraphicsCaptureItem;
4use windows::Win32::Foundation::{HWND, LPARAM, RECT, TRUE};
5use windows::Win32::Graphics::Dwm::{DWMWA_EXTENDED_FRAME_BOUNDS, DwmGetWindowAttribute};
6use windows::Win32::Graphics::Gdi::{MONITOR_DEFAULTTONULL, MonitorFromWindow};
7use windows::Win32::System::ProcessStatus::GetModuleBaseNameW;
8use windows::Win32::System::Threading::{
9 GetCurrentProcessId, OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
10};
11use windows::Win32::System::WinRT::Graphics::Capture::IGraphicsCaptureItemInterop;
12use windows::Win32::UI::HiDpi::GetDpiForWindow;
13use windows::Win32::UI::WindowsAndMessaging::{
14 EnumChildWindows, FindWindowW, GWL_EXSTYLE, GWL_STYLE, GetClientRect, GetDesktopWindow,
15 GetForegroundWindow, GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW,
16 GetWindowThreadProcessId, IsWindowVisible, WS_CHILD, WS_EX_TOOLWINDOW,
17};
18use windows::core::{BOOL, HSTRING, Owned};
19
20use crate::monitor::Monitor;
21use crate::settings::{CaptureItemTypes, TryIntoCaptureItemWithType};
22
23#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
24pub enum Error {
25 #[error("No active window found.")]
26 NoActiveWindow,
27 #[error("Failed to find a window with the name: {0}")]
28 NotFound(String),
29 #[error("Failed to convert a Windows string from UTF-16")]
30 FailedToConvertWindowsString,
31 #[error("A Windows API call failed: {0}")]
32 WindowsError(#[from] windows::core::Error),
33}
34
35#[derive(Eq, PartialEq, Clone, Copy, Debug)]
49pub struct Window {
50 window: HWND,
51}
52
53unsafe impl Send for Window {}
54
55impl Window {
56 #[inline]
62 pub fn foreground() -> Result<Self, Error> {
63 let window = unsafe { GetForegroundWindow() };
64
65 if window.is_invalid() {
66 return Err(Error::NoActiveWindow);
67 }
68
69 Ok(Self { window })
70 }
71
72 #[inline]
82 pub fn from_name(title: &str) -> Result<Self, Error> {
83 let hstring_title = HSTRING::from(title);
84 let window = unsafe { FindWindowW(None, &hstring_title)? };
85
86 if window.is_invalid() {
87 return Err(Error::NotFound(String::from(title)));
88 }
89
90 Ok(Self { window })
91 }
92
93 #[inline]
103 pub fn from_contains_name(title: &str) -> Result<Self, Error> {
104 let windows = Self::enumerate()?;
105
106 let mut target_window = None;
107 for window in windows {
108 if window.title()?.contains(title) {
109 target_window = Some(window);
110 break;
111 }
112 }
113
114 target_window.map_or_else(|| Err(Error::NotFound(String::from(title))), Ok)
115 }
116
117 #[inline]
123 pub fn title(&self) -> Result<String, Error> {
124 let len = unsafe { GetWindowTextLengthW(self.window) };
125
126 let mut name = vec![0u16; usize::try_from(len).unwrap() + 1];
127 if len >= 1 {
128 let copied = unsafe { GetWindowTextW(self.window, &mut name) };
129 if copied == 0 {
130 return Ok(String::new());
131 }
132 }
133
134 let name = String::from_utf16(
135 &name.as_slice().iter().take_while(|ch| **ch != 0x0000).copied().collect::<Vec<u16>>(),
136 )
137 .map_err(|_| Error::FailedToConvertWindowsString)?;
138
139 Ok(name)
140 }
141
142 #[inline]
148 pub fn process_id(&self) -> Result<u32, Error> {
149 let mut id = 0;
150 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
151
152 if id == 0 {
153 return Err(windows::core::Error::from_win32().into());
154 }
155
156 Ok(id)
157 }
158
159 #[inline]
167 pub fn process_name(&self) -> Result<String, Error> {
168 let id = self.process_id()?;
169
170 let process =
171 unsafe { OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, id) }?;
172 let process = unsafe { Owned::new(process) };
173
174 let mut name = vec![0u16; 260];
175 let size = unsafe { GetModuleBaseNameW(*process, None, &mut name) };
176
177 if size == 0 {
178 return Err(windows::core::Error::from_win32().into());
179 }
180
181 let name = String::from_utf16(
182 &name.as_slice().iter().take_while(|ch| **ch != 0x0000).copied().collect::<Vec<u16>>(),
183 )
184 .map_err(|_| Error::FailedToConvertWindowsString)?;
185
186 Ok(name)
187 }
188
189 #[must_use]
193 #[inline]
194 pub fn monitor(&self) -> Option<Monitor> {
195 let window = self.window;
196
197 let monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
198
199 if monitor.is_invalid() { None } else { Some(Monitor::from_raw_hmonitor(monitor.0)) }
200 }
201
202 #[inline]
208 pub fn rect(&self) -> Result<RECT, Error> {
209 let mut rect = RECT::default();
210 let result = unsafe { GetWindowRect(self.window, &mut rect) };
211 if result.is_ok() {
212 Ok(rect)
213 } else {
214 Err(Error::WindowsError(windows::core::Error::from_win32()))
215 }
216 }
217
218 #[inline]
224 pub fn title_bar_height(&self) -> Result<u32, Error> {
225 let mut window_rect = RECT::default();
226 let mut client_rect = RECT::default();
227
228 unsafe {
229 DwmGetWindowAttribute(
230 self.window,
231 DWMWA_EXTENDED_FRAME_BOUNDS,
232 &mut window_rect as *mut RECT as *mut std::ffi::c_void,
233 std::mem::size_of::<RECT>() as u32,
234 )
235 }?;
236
237 unsafe { GetClientRect(self.window, &mut client_rect) }?;
238
239 let window_height = window_rect.bottom - window_rect.top;
240 let dpi = unsafe { GetDpiForWindow(self.window) };
241 let client_height = (client_rect.bottom - client_rect.top) * dpi as i32 / 96;
242 let actual_title_height = (window_height - client_height) as i32;
243
244 Ok(actual_title_height as u32)
245 }
246
247 #[must_use]
254 #[inline]
255 pub fn is_valid(&self) -> bool {
256 if !unsafe { IsWindowVisible(self.window).as_bool() } {
257 return false;
258 }
259
260 let mut id = 0;
261 unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
262 if id == unsafe { GetCurrentProcessId() } {
263 return false;
264 }
265
266 let mut rect = RECT::default();
267 let result = unsafe { GetClientRect(self.window, &mut rect) };
268 if result.is_ok() {
269 let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) };
270 let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) };
271
272 if (ex_styles & isize::try_from(WS_EX_TOOLWINDOW.0).unwrap()) != 0 {
273 return false;
274 }
275 if (styles & isize::try_from(WS_CHILD.0).unwrap()) != 0 {
276 return false;
277 }
278 } else {
279 return false;
280 }
281
282 true
283 }
284
285 #[inline]
291 pub fn enumerate() -> Result<Vec<Self>, Error> {
292 let mut windows: Vec<Self> = Vec::new();
293
294 unsafe {
295 EnumChildWindows(
296 Some(GetDesktopWindow()),
297 Some(Self::enum_windows_callback),
298 LPARAM(ptr::addr_of_mut!(windows) as isize),
299 )
300 .ok()?;
301 };
302
303 Ok(windows)
304 }
305
306 #[must_use]
312 #[inline]
313 pub const fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
314 Self { window: HWND(hwnd) }
315 }
316
317 #[must_use]
319 #[inline]
320 pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
321 self.window.0
322 }
323
324 #[inline]
326 unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL {
327 let windows = unsafe { &mut *(vec.0 as *mut Vec<Self>) };
328
329 if Self::from_raw_hwnd(window.0).is_valid() {
330 windows.push(Self { window });
331 }
332
333 TRUE
334 }
335}
336
337impl TryIntoCaptureItemWithType for Window {
339 #[inline]
340 fn try_into_capture_item(
341 self,
342 ) -> Result<(GraphicsCaptureItem, CaptureItemTypes), windows::core::Error> {
343 let window = HWND(self.as_raw_hwnd());
344
345 let interop = windows::core::factory::<GraphicsCaptureItem, IGraphicsCaptureItemInterop>()?;
346 let item = unsafe { interop.CreateForWindow(window)? };
347
348 Ok((item, CaptureItemTypes::Window(self)))
349 }
350}