windows_capture/
window.rs

1use 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/// Represents a window in the Windows operating system.
39///
40/// # Example
41/// ```no_run
42/// use windows_capture::window::Window;
43///
44/// fn main() -> Result<(), Box<dyn std::error::Error>> {
45///     let window = Window::foreground()?;
46///     println!("Foreground window title: {}", window.title()?);
47///
48///     Ok(())
49/// }
50/// ```
51#[derive(Eq, PartialEq, Clone, Copy, Debug)]
52pub struct Window {
53    window: HWND,
54}
55
56unsafe impl Send for Window {}
57
58impl Window {
59    /// Returns the foreground window.
60    ///
61    /// # Errors
62    ///
63    /// Returns an `Error::NoActiveWindow` if there is no active window.
64    #[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    /// Creates a `Window` instance from a window name.
76    ///
77    /// # Arguments
78    ///
79    /// * `title` - The name of the window.
80    ///
81    /// # Errors
82    ///
83    /// Returns an `Error::NotFound` if the window is not found.
84    #[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    /// Creates a `Window` instance from a window name substring.
97    ///
98    /// # Arguments
99    ///
100    /// * `title` - The substring to search for in window names.
101    ///
102    /// # Errors
103    ///
104    /// Returns an `Error::NotFound` if no window with a matching name substring is found.
105    #[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    /// Returns the title of the window.
121    ///
122    /// # Errors
123    ///
124    /// Returns an `Error` if there is an error retrieving the window title.
125    #[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    /// Returns the process name of the window.
150    ///
151    /// # Errors
152    ///
153    /// Returns an `Error` if there is an error retrieving the process name.
154    #[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    /// Returns the process name of the window.
167    ///
168    /// Requires the `PROCESS_QUERY_INFORMATION` and `PROCESS_VM_READ` permissions.
169    ///
170    /// # Errors
171    ///
172    /// Returns an `Error` if there is an error retrieving the process name.
173    #[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    /// Returns the monitor that has the largest area of intersection with the window.
201    ///
202    /// Returns `None` if the window doesn't intersect with any monitor.
203    #[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    /// Returns the rectangle of the window in screen coordinates.
218    ///
219    /// # Errors
220    ///
221    /// Returns an `Error::WindowsError` if there is an error retrieving the window rectangle.
222    #[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    /// Checks if the window is a valid window.
234    ///
235    /// # Returns
236    ///
237    /// Returns `true` if the window is valid, `false` otherwise.
238    #[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    /// Returns a list of all windows.
271    ///
272    /// # Errors
273    ///
274    /// Returns an `Error` if there is an error enumerating the windows.
275    #[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    /// Creates a `Window` instance from a raw HWND.
292    ///
293    /// # Arguments
294    ///
295    /// * `hwnd` - The raw HWND.
296    #[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    /// Returns the raw HWND of the window.
303    #[must_use]
304    #[inline]
305    pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
306        self.window.0
307    }
308
309    // Callback used for enumerating all windows.
310    #[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
322// Implements TryFrom For Window To Convert It To GraphicsCaptureItem
323impl 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}