windows_capture/
window.rs

1use std::{ptr, string::FromUtf16Error};
2
3use windows::{
4    core::HSTRING,
5    Graphics::Capture::GraphicsCaptureItem,
6    Win32::{
7        Foundation::{BOOL, HWND, LPARAM, RECT, TRUE},
8        Graphics::Gdi::{MonitorFromWindow, MONITOR_DEFAULTTONULL},
9        System::{
10            Threading::GetCurrentProcessId, WinRT::Graphics::Capture::IGraphicsCaptureItemInterop,
11        },
12        UI::WindowsAndMessaging::{
13            EnumChildWindows, FindWindowW, GetClientRect, GetDesktopWindow, GetForegroundWindow,
14            GetWindowLongPtrW, GetWindowRect, GetWindowTextLengthW, GetWindowTextW,
15            GetWindowThreadProcessId, IsWindowVisible, GWL_EXSTYLE, GWL_STYLE, WS_CHILD,
16            WS_EX_TOOLWINDOW,
17        },
18    },
19};
20
21use crate::monitor::Monitor;
22
23#[derive(thiserror::Error, Debug)]
24pub enum Error {
25    #[error("No active window found")]
26    NoActiveWindow,
27    #[error("Failed to find window with name: {0}")]
28    NotFound(String),
29    #[error("Failed to convert windows string from UTF-16: {0}")]
30    FailedToConvertWindowsString(#[from] FromUtf16Error),
31    #[error("Windows API error: {0}")]
32    WindowsError(#[from] windows::core::Error),
33}
34
35/// Represents a window in the Windows operating system.
36///
37/// # Example
38/// ```no_run
39/// use windows_capture::window::Window;
40///
41/// fn main() -> Result<(), Box<dyn std::error::Error>> {
42///     let window = Window::foreground()?;
43///     println!("Foreground window title: {}", window.title()?);
44///
45///     Ok(())
46/// }
47/// ```
48#[derive(Eq, PartialEq, Clone, Copy, Debug)]
49pub struct Window {
50    window: HWND,
51}
52
53unsafe impl Send for Window {}
54
55impl Window {
56    /// Returns the foreground window.
57    ///
58    /// # Errors
59    ///
60    /// Returns an `Error::NoActiveWindow` if there is no active window.
61    #[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    /// Creates a `Window` instance from a window name.
73    ///
74    /// # Arguments
75    ///
76    /// * `title` - The name of the window.
77    ///
78    /// # Errors
79    ///
80    /// Returns an `Error::NotFound` if the window is not found.
81    #[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    /// Creates a `Window` instance from a window name substring.
94    ///
95    /// # Arguments
96    ///
97    /// * `title` - The substring to search for in window names.
98    ///
99    /// # Errors
100    ///
101    /// Returns an `Error::NotFound` if no window with a matching name substring is found.
102    #[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    /// Returns the title of the window.
118    ///
119    /// # Errors
120    ///
121    /// Returns an `Error` if there is an error retrieving the window title.
122    #[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
136                .as_slice()
137                .iter()
138                .take_while(|ch| **ch != 0x0000)
139                .copied()
140                .collect::<Vec<_>>(),
141        )?;
142
143        Ok(name)
144    }
145
146    /// Returns the monitor that has the largest area of intersection with the window.
147    ///
148    /// Returns `None` if the window doesn't intersect with any monitor.
149    #[must_use]
150    #[inline]
151    pub fn monitor(&self) -> Option<Monitor> {
152        let window = self.window;
153
154        let monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
155
156        if monitor.is_invalid() {
157            None
158        } else {
159            Some(Monitor::from_raw_hmonitor(monitor.0))
160        }
161    }
162
163    /// Returns the rectangle of the window in screen coordinates.
164    ///
165    /// # Errors
166    ///
167    /// Returns an `Error::WindowsError` if there is an error retrieving the window rectangle.
168    #[inline]
169    pub fn rect(&self) -> Result<RECT, Error> {
170        let mut rect = RECT::default();
171        let result = unsafe { GetWindowRect(self.window, &mut rect) };
172        if result.is_ok() {
173            Ok(rect)
174        } else {
175            Err(Error::WindowsError(windows::core::Error::from_win32()))
176        }
177    }
178
179    /// Checks if the window is a valid window.
180    ///
181    /// # Returns
182    ///
183    /// Returns `true` if the window is valid, `false` otherwise.
184    #[must_use]
185    #[inline]
186    pub fn is_valid(&self) -> bool {
187        if !unsafe { IsWindowVisible(self.window).as_bool() } {
188            return false;
189        }
190
191        let mut id = 0;
192        unsafe { GetWindowThreadProcessId(self.window, Some(&mut id)) };
193        if id == unsafe { GetCurrentProcessId() } {
194            return false;
195        }
196
197        let mut rect = RECT::default();
198        let result = unsafe { GetClientRect(self.window, &mut rect) };
199        if result.is_ok() {
200            let styles = unsafe { GetWindowLongPtrW(self.window, GWL_STYLE) };
201            let ex_styles = unsafe { GetWindowLongPtrW(self.window, GWL_EXSTYLE) };
202
203            if (ex_styles & isize::try_from(WS_EX_TOOLWINDOW.0).unwrap()) != 0 {
204                return false;
205            }
206            if (styles & isize::try_from(WS_CHILD.0).unwrap()) != 0 {
207                return false;
208            }
209        } else {
210            return false;
211        }
212
213        true
214    }
215
216    /// Returns a list of all windows.
217    ///
218    /// # Errors
219    ///
220    /// Returns an `Error` if there is an error enumerating the windows.
221    #[inline]
222    pub fn enumerate() -> Result<Vec<Self>, Error> {
223        let mut windows: Vec<Self> = Vec::new();
224
225        unsafe {
226            EnumChildWindows(
227                GetDesktopWindow(),
228                Some(Self::enum_windows_callback),
229                LPARAM(ptr::addr_of_mut!(windows) as isize),
230            )
231            .ok()?;
232        };
233
234        Ok(windows)
235    }
236
237    /// Creates a `Window` instance from a raw HWND.
238    ///
239    /// # Arguments
240    ///
241    /// * `hwnd` - The raw HWND.
242    #[must_use]
243    #[inline]
244    pub const fn from_raw_hwnd(hwnd: *mut std::ffi::c_void) -> Self {
245        Self { window: HWND(hwnd) }
246    }
247
248    /// Returns the raw HWND of the window.
249    #[must_use]
250    #[inline]
251    pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
252        self.window.0
253    }
254
255    // Callback used for enumerating all windows.
256    #[inline]
257    unsafe extern "system" fn enum_windows_callback(window: HWND, vec: LPARAM) -> BOOL {
258        let windows = &mut *(vec.0 as *mut Vec<Self>);
259
260        if Self::from_raw_hwnd(window.0).is_valid() {
261            windows.push(Self { window });
262        }
263
264        TRUE
265    }
266}
267
268// Implements TryFrom For Window To Convert It To GraphicsCaptureItem
269impl TryFrom<Window> for GraphicsCaptureItem {
270    type Error = Error;
271
272    #[inline]
273    fn try_from(value: Window) -> Result<Self, Self::Error> {
274        let window = HWND(value.as_raw_hwnd());
275
276        let interop = windows::core::factory::<Self, IGraphicsCaptureItemInterop>()?;
277        Ok(unsafe { interop.CreateForWindow(window)? })
278    }
279}