Skip to main content

windows_capture/
graphics_capture_picker.rs

1use windows::Graphics::Capture::GraphicsCaptureItem;
2use windows::Win32::Foundation::{ERROR_CLASS_ALREADY_EXISTS, GetLastError, HWND, LPARAM, LRESULT, WPARAM};
3use windows::Win32::System::LibraryLoader::GetModuleHandleW;
4use windows::Win32::UI::Shell::IInitializeWithWindow;
5use windows::Win32::UI::WindowsAndMessaging::{
6    CS_HREDRAW, CS_VREDRAW, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE,
7    PeekMessageW, RegisterClassExW, TranslateMessage, WM_DESTROY, WNDCLASSEXW, WS_EX_TOOLWINDOW, WS_POPUP, WS_VISIBLE,
8};
9use windows::core::{Interface, w};
10use windows_future::AsyncStatus;
11
12use crate::settings::GraphicsCaptureItemType;
13use crate::winrt::WinRT;
14
15#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
16/// Errors that can occur while showing or interacting with the Graphics Capture Picker.
17pub enum Error {
18    /// An error returned by an underlying Windows API call.
19    #[error("Windows API error: {0}")]
20    WindowsError(#[from] windows::core::Error),
21    /// The user canceled the picker (no item selected).
22    #[error("User canceled the picker")]
23    Canceled,
24}
25
26/// Window procedure for the hidden owner window used by the picker.
27///
28/// Safety: Called by the system with a valid `HWND` and message parameters.
29/// Forwards unhandled messages to `DefWindowProcW`.
30unsafe extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
31    match msg {
32        WM_DESTROY => LRESULT(0),
33        _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
34    }
35}
36
37/// RAII guard that keeps the picker resources alive until the selected item is
38/// consumed.
39pub struct HwndGuard {
40    hwnd: HWND,
41    _winrt: WinRT,
42}
43
44impl Drop for HwndGuard {
45    fn drop(&mut self) {
46        unsafe {
47            let _ = DestroyWindow(self.hwnd);
48            let mut msg = MSG::default();
49            while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
50                // We just remove them; no need to dispatch at this point.
51            }
52        }
53    }
54}
55
56/// The successfully picked graphics capture item and its associated window guard.
57pub struct PickedGraphicsCaptureItem {
58    /// The selected `GraphicsCaptureItem` (window or monitor).
59    pub item: GraphicsCaptureItem,
60    /// Keeps the hidden owner `HWND` alive until the picked item is consumed.
61    _guard: HwndGuard,
62}
63
64impl PickedGraphicsCaptureItem {
65    /// Returns the size of the picked item as `(width, height)`.
66    pub fn size(&self) -> windows::core::Result<(i32, i32)> {
67        let size = self.item.Size()?;
68        Ok((size.Width, size.Height))
69    }
70}
71
72/// Helper for prompting the user to pick a window or monitor using the system
73/// Graphics Capture Picker.
74pub struct GraphicsCapturePicker;
75
76impl GraphicsCapturePicker {
77    /// Shows the system Graphics Capture Picker dialog and returns the chosen item.
78    ///
79    /// A tiny, off-screen tool window is created as the picker owner and initialized
80    /// via `IInitializeWithWindow`. While the picker is visible, a minimal message
81    /// pump is run to keep the UI responsive.
82    ///
83    /// # Returns
84    ///
85    /// - `Ok(Some(PickedGraphicsCaptureItem))` if the user selects a target
86    /// - `Ok(None)` if the picker completes without a result
87    ///
88    /// # Errors
89    /// - [`Error::Canceled`] when the user cancels the picker
90    /// - [`Error::WindowsError`] for underlying Windows API failures
91    pub fn pick_item() -> Result<Option<PickedGraphicsCaptureItem>, Error> {
92        // The picker and the item it returns must be created in an initialized
93        // WinRT apartment. Keep this guard with the item so Windows 10 does not
94        // disconnect it before capture starts.
95        let winrt = WinRT::new()?;
96
97        let hinst = unsafe { GetModuleHandleW(None) }?;
98        let wc = WNDCLASSEXW {
99            cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
100            style: CS_HREDRAW | CS_VREDRAW,
101            lpfnWndProc: Some(wnd_proc),
102            hInstance: hinst.into(),
103            lpszClassName: w!("windows-capture-picker-window"),
104            ..Default::default()
105        };
106
107        if unsafe { RegisterClassExW(&wc) } == 0 {
108            let err = unsafe { GetLastError() };
109            if err != ERROR_CLASS_ALREADY_EXISTS {
110                return Err(Error::WindowsError(err.into()));
111            }
112        }
113
114        let hwnd = unsafe {
115            CreateWindowExW(
116                WS_EX_TOOLWINDOW,
117                w!("windows-capture-picker-window"),
118                w!("Windows Capture Picker"),
119                WS_POPUP | WS_VISIBLE,
120                -69000,
121                -69000,
122                0,
123                0,
124                None,
125                None,
126                Some(hinst.into()),
127                None,
128            )
129        }?;
130
131        // Construct the guard immediately so the hidden window is also cleaned
132        // up when picker initialization, selection, or result retrieval fails.
133        let guard = HwndGuard { hwnd, _winrt: winrt };
134
135        let picker = windows::Graphics::Capture::GraphicsCapturePicker::new()?;
136        let initialize_with_window: IInitializeWithWindow = picker.cast()?;
137        unsafe { initialize_with_window.Initialize(guard.hwnd) }?;
138
139        let op = picker.PickSingleItemAsync()?;
140
141        loop {
142            match op.Status()? {
143                AsyncStatus::Started => unsafe {
144                    let mut msg = MSG::default();
145                    while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
146                        // Normal UI pump while the picker is up
147                        let _ = TranslateMessage(&msg);
148                        DispatchMessageW(&msg);
149                    }
150                },
151                AsyncStatus::Completed => break,
152                AsyncStatus::Canceled => return Err(Error::Canceled),
153                AsyncStatus::Error => return Err(Error::WindowsError(op.ErrorCode()?.into())),
154                _ => {}
155            }
156        }
157
158        op.GetResults()
159            .ok()
160            .map_or_else(|| Ok(None), |item| Ok(Some(PickedGraphicsCaptureItem { item, _guard: guard })))
161    }
162}
163
164impl TryInto<GraphicsCaptureItemType> for PickedGraphicsCaptureItem {
165    type Error = windows::core::Error;
166
167    #[inline]
168    fn try_into(self) -> Result<GraphicsCaptureItemType, Self::Error> {
169        Ok(GraphicsCaptureItemType::Unknown((self.item, self._guard)))
170    }
171}