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