windows_capture/
window.rs1use 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#[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
136 .as_slice()
137 .iter()
138 .take_while(|ch| **ch != 0x0000)
139 .copied()
140 .collect::<Vec<_>>(),
141 )?;
142
143 Ok(name)
144 }
145
146 #[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 #[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 #[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 #[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 #[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 #[must_use]
250 #[inline]
251 pub const fn as_raw_hwnd(&self) -> *mut std::ffi::c_void {
252 self.window.0
253 }
254
255 #[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
268impl 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}