windows_capture/
monitor.rs

1use std::num::ParseIntError;
2use std::string::FromUtf16Error;
3use std::{mem, ptr};
4
5use windows::Graphics::Capture::GraphicsCaptureItem;
6use windows::Win32::Devices::Display::{
7    DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
8    DISPLAYCONFIG_DEVICE_INFO_HEADER, DISPLAYCONFIG_MODE_INFO, DISPLAYCONFIG_PATH_INFO,
9    DISPLAYCONFIG_SOURCE_DEVICE_NAME, DISPLAYCONFIG_TARGET_DEVICE_NAME,
10    DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS, DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY,
11    DisplayConfigGetDeviceInfo, GetDisplayConfigBufferSizes, QDC_ONLY_ACTIVE_PATHS,
12    QueryDisplayConfig,
13};
14use windows::Win32::Foundation::{LPARAM, POINT, RECT, TRUE};
15use windows::Win32::Graphics::Gdi::{
16    DEVMODEW, DISPLAY_DEVICE_STATE_FLAGS, DISPLAY_DEVICEW, ENUM_CURRENT_SETTINGS,
17    EnumDisplayDevicesW, EnumDisplayMonitors, EnumDisplaySettingsW, GetMonitorInfoW, HDC, HMONITOR,
18    MONITOR_DEFAULTTONULL, MONITORINFO, MONITORINFOEXW, MonitorFromPoint,
19};
20use windows::Win32::System::WinRT::Graphics::Capture::IGraphicsCaptureItemInterop;
21use windows::core::{BOOL, HSTRING, PCWSTR};
22
23use crate::settings::{CaptureItemTypes, TryIntoCaptureItemWithType};
24
25#[derive(thiserror::Error, Debug)]
26pub enum Error {
27    #[error("Failed to find the specified monitor.")]
28    NotFound,
29    #[error("Failed to get the monitor's name.")]
30    NameNotFound,
31    #[error("The monitor index must be greater than zero.")]
32    IndexIsLowerThanOne,
33    #[error("Failed to get monitor information.")]
34    FailedToGetMonitorInfo,
35    #[error("Failed to get the monitor's display settings.")]
36    FailedToGetMonitorSettings,
37    #[error("Failed to get the monitor's device name.")]
38    FailedToGetMonitorName,
39    #[error("Failed to parse the monitor index: {0}")]
40    FailedToParseMonitorIndex(#[from] ParseIntError),
41    #[error("Failed to convert a Windows string: {0}")]
42    FailedToConvertWindowsString(#[from] FromUtf16Error),
43    #[error("A Windows API call failed: {0}")]
44    WindowsError(#[from] windows::core::Error),
45}
46
47/// Represents a display monitor.
48///
49/// # Example
50/// ```no_run
51/// use windows_capture::monitor::Monitor;
52///
53/// fn main() -> Result<(), Box<dyn std::error::Error>> {
54///     let monitor = Monitor::primary()?;
55///     println!("Primary Monitor: {}", monitor.name()?);
56///
57///     Ok(())
58/// }
59/// ```
60#[derive(Eq, PartialEq, Clone, Copy, Debug)]
61pub struct Monitor {
62    monitor: HMONITOR,
63}
64
65unsafe impl Send for Monitor {}
66
67impl Monitor {
68    /// Returns the primary monitor.
69    ///
70    /// # Errors
71    ///
72    /// Returns `Error::NotFound` if the primary monitor cannot be found.
73    #[inline]
74    pub fn primary() -> Result<Self, Error> {
75        let point = POINT { x: 0, y: 0 };
76        let monitor = unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONULL) };
77
78        if monitor.is_invalid() {
79            return Err(Error::NotFound);
80        }
81
82        Ok(Self { monitor })
83    }
84
85    /// Returns the monitor at the specified index.
86    ///
87    /// # Arguments
88    ///
89    /// * `index` - The one-based index of the monitor to retrieve.
90    ///
91    /// # Errors
92    ///
93    /// * `Error::IndexIsLowerThanOne` - If the `index` is less than 1.
94    /// * `Error::NotFound` - If no monitor is found at the specified `index`.
95    #[inline]
96    pub fn from_index(index: usize) -> Result<Self, Error> {
97        if index < 1 {
98            return Err(Error::IndexIsLowerThanOne);
99        }
100
101        let monitor = Self::enumerate()?;
102        let monitor = match monitor.get(index - 1) {
103            Some(monitor) => *monitor,
104            None => return Err(Error::NotFound),
105        };
106
107        Ok(monitor)
108    }
109
110    /// Returns the one-based index of the monitor.
111    ///
112    /// # Errors
113    ///
114    /// Returns an `Error` if the monitor's device name cannot be parsed to determine the index.
115    #[inline]
116    pub fn index(&self) -> Result<usize, Error> {
117        let device_name = self.device_name()?;
118        Ok(device_name.replace("\\\\.\\DISPLAY", "").parse()?)
119    }
120
121    /// Returns the friendly name of the monitor.
122    ///
123    /// # Errors
124    ///
125    /// Returns an `Error` if the monitor's name cannot be retrieved.
126    #[inline]
127    pub fn name(&self) -> Result<String, Error> {
128        let mut monitor_info = MONITORINFOEXW {
129            monitorInfo: MONITORINFO {
130                cbSize: u32::try_from(mem::size_of::<MONITORINFOEXW>()).unwrap(),
131                rcMonitor: RECT::default(),
132                rcWork: RECT::default(),
133                dwFlags: 0,
134            },
135            szDevice: [0; 32],
136        };
137        if unsafe {
138            !GetMonitorInfoW(
139                HMONITOR(self.as_raw_hmonitor()),
140                std::ptr::addr_of_mut!(monitor_info).cast(),
141            )
142            .as_bool()
143        } {
144            return Err(Error::FailedToGetMonitorInfo);
145        }
146
147        let mut number_of_paths = 0;
148        let mut number_of_modes = 0;
149        unsafe {
150            GetDisplayConfigBufferSizes(
151                QDC_ONLY_ACTIVE_PATHS,
152                &mut number_of_paths,
153                &mut number_of_modes,
154            )
155            .ok()?;
156        };
157
158        let mut paths = vec![DISPLAYCONFIG_PATH_INFO::default(); number_of_paths as usize];
159        let mut modes = vec![DISPLAYCONFIG_MODE_INFO::default(); number_of_modes as usize];
160        unsafe {
161            QueryDisplayConfig(
162                QDC_ONLY_ACTIVE_PATHS,
163                &mut number_of_paths,
164                paths.as_mut_ptr(),
165                &mut number_of_modes,
166                modes.as_mut_ptr(),
167                None,
168            )
169        }
170        .ok()?;
171
172        for path in paths {
173            let mut source = DISPLAYCONFIG_SOURCE_DEVICE_NAME {
174                header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
175                    r#type: DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
176                    size: u32::try_from(mem::size_of::<DISPLAYCONFIG_SOURCE_DEVICE_NAME>())
177                        .unwrap(),
178                    adapterId: path.sourceInfo.adapterId,
179                    id: path.sourceInfo.id,
180                },
181                viewGdiDeviceName: [0; 32],
182            };
183
184            let device_name = self.device_name()?;
185            let view_gdi_device_name = String::from_utf16(
186                &monitor_info
187                    .szDevice
188                    .as_slice()
189                    .iter()
190                    .take_while(|ch| **ch != 0x0000)
191                    .copied()
192                    .collect::<Vec<u16>>(),
193            )?;
194
195            if unsafe { DisplayConfigGetDeviceInfo(&mut source.header) } == 0
196                && device_name == view_gdi_device_name
197            {
198                let mut target = DISPLAYCONFIG_TARGET_DEVICE_NAME {
199                    header: DISPLAYCONFIG_DEVICE_INFO_HEADER {
200                        r#type: DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME,
201                        size: u32::try_from(mem::size_of::<DISPLAYCONFIG_TARGET_DEVICE_NAME>())
202                            .unwrap(),
203                        adapterId: path.sourceInfo.adapterId,
204                        id: path.targetInfo.id,
205                    },
206                    flags: DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS::default(),
207                    outputTechnology: DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY::default(),
208                    edidManufactureId: 0,
209                    edidProductCodeId: 0,
210                    connectorInstance: 0,
211                    monitorFriendlyDeviceName: [0; 64],
212                    monitorDevicePath: [0; 128],
213                };
214
215                if unsafe { DisplayConfigGetDeviceInfo(&mut target.header) } == 0 {
216                    let name = String::from_utf16(
217                        &target
218                            .monitorFriendlyDeviceName
219                            .as_slice()
220                            .iter()
221                            .take_while(|ch| **ch != 0x0000)
222                            .copied()
223                            .collect::<Vec<u16>>(),
224                    )?;
225                    return Ok(name);
226                }
227
228                return Err(Error::FailedToGetMonitorInfo);
229            }
230        }
231
232        Err(Error::NameNotFound)
233    }
234
235    /// Returns the device name of the monitor (e.g., `\\.\DISPLAY1`).
236    ///
237    /// # Errors
238    ///
239    /// Returns an `Error` if the monitor's device name cannot be retrieved.
240    #[inline]
241    pub fn device_name(&self) -> Result<String, Error> {
242        let mut monitor_info = MONITORINFOEXW {
243            monitorInfo: MONITORINFO {
244                cbSize: u32::try_from(mem::size_of::<MONITORINFOEXW>()).unwrap(),
245                rcMonitor: RECT::default(),
246                rcWork: RECT::default(),
247                dwFlags: 0,
248            },
249            szDevice: [0; 32],
250        };
251        if unsafe {
252            !GetMonitorInfoW(
253                HMONITOR(self.as_raw_hmonitor()),
254                std::ptr::addr_of_mut!(monitor_info).cast(),
255            )
256            .as_bool()
257        } {
258            return Err(Error::FailedToGetMonitorInfo);
259        }
260
261        let device_name = String::from_utf16(
262            &monitor_info
263                .szDevice
264                .as_slice()
265                .iter()
266                .take_while(|ch| **ch != 0x0000)
267                .copied()
268                .collect::<Vec<u16>>(),
269        )?;
270
271        Ok(device_name)
272    }
273
274    /// Returns the device string of the monitor (e.g., `NVIDIA GeForce RTX 4090`).
275    ///
276    /// # Errors
277    ///
278    /// Returns an `Error` if the monitor's device string cannot be retrieved.
279    #[inline]
280    pub fn device_string(&self) -> Result<String, Error> {
281        let mut monitor_info = MONITORINFOEXW {
282            monitorInfo: MONITORINFO {
283                cbSize: u32::try_from(mem::size_of::<MONITORINFOEXW>()).unwrap(),
284                rcMonitor: RECT::default(),
285                rcWork: RECT::default(),
286                dwFlags: 0,
287            },
288            szDevice: [0; 32],
289        };
290        if unsafe {
291            !GetMonitorInfoW(
292                HMONITOR(self.as_raw_hmonitor()),
293                std::ptr::addr_of_mut!(monitor_info).cast(),
294            )
295            .as_bool()
296        } {
297            return Err(Error::FailedToGetMonitorInfo);
298        }
299
300        let mut display_device = DISPLAY_DEVICEW {
301            cb: u32::try_from(mem::size_of::<DISPLAY_DEVICEW>()).unwrap(),
302            DeviceName: [0; 32],
303            DeviceString: [0; 128],
304            StateFlags: DISPLAY_DEVICE_STATE_FLAGS::default(),
305            DeviceID: [0; 128],
306            DeviceKey: [0; 128],
307        };
308
309        if unsafe {
310            !EnumDisplayDevicesW(
311                PCWSTR::from_raw(monitor_info.szDevice.as_mut_ptr()),
312                0,
313                &mut display_device,
314                0,
315            )
316            .as_bool()
317        } {
318            return Err(Error::FailedToGetMonitorName);
319        }
320
321        let device_string = String::from_utf16(
322            &display_device
323                .DeviceString
324                .as_slice()
325                .iter()
326                .take_while(|ch| **ch != 0x0000)
327                .copied()
328                .collect::<Vec<u16>>(),
329        )?;
330
331        Ok(device_string)
332    }
333
334    /// Returns the refresh rate of the monitor in hertz (Hz).
335    ///
336    /// # Errors
337    ///
338    /// Returns an `Error` if the monitor's refresh rate cannot be retrieved.
339    #[inline]
340    pub fn refresh_rate(&self) -> Result<u32, Error> {
341        let mut device_mode = DEVMODEW {
342            dmSize: u16::try_from(mem::size_of::<DEVMODEW>()).unwrap(),
343            ..DEVMODEW::default()
344        };
345        let name = HSTRING::from(self.device_name()?);
346        if unsafe {
347            !EnumDisplaySettingsW(PCWSTR(name.as_ptr()), ENUM_CURRENT_SETTINGS, &mut device_mode)
348                .as_bool()
349        } {
350            return Err(Error::FailedToGetMonitorSettings);
351        }
352
353        Ok(device_mode.dmDisplayFrequency)
354    }
355
356    /// Returns the width of the monitor in pixels.
357    ///
358    /// # Errors
359    ///
360    /// Returns an `Error` if the monitor's width cannot be retrieved.
361    #[inline]
362    pub fn width(&self) -> Result<u32, Error> {
363        let mut device_mode = DEVMODEW {
364            dmSize: u16::try_from(mem::size_of::<DEVMODEW>()).unwrap(),
365            ..DEVMODEW::default()
366        };
367        let name = HSTRING::from(self.device_name()?);
368        if unsafe {
369            !EnumDisplaySettingsW(PCWSTR(name.as_ptr()), ENUM_CURRENT_SETTINGS, &mut device_mode)
370                .as_bool()
371        } {
372            return Err(Error::FailedToGetMonitorSettings);
373        }
374
375        Ok(device_mode.dmPelsWidth)
376    }
377
378    /// Returns the height of the monitor in pixels.
379    ///
380    /// # Errors
381    ///
382    /// Returns an `Error` if the monitor's height cannot be retrieved.
383    #[inline]
384    pub fn height(&self) -> Result<u32, Error> {
385        let mut device_mode = DEVMODEW {
386            dmSize: u16::try_from(mem::size_of::<DEVMODEW>()).unwrap(),
387            ..DEVMODEW::default()
388        };
389        let name = HSTRING::from(self.device_name()?);
390        if unsafe {
391            !EnumDisplaySettingsW(PCWSTR(name.as_ptr()), ENUM_CURRENT_SETTINGS, &mut device_mode)
392                .as_bool()
393        } {
394            return Err(Error::FailedToGetMonitorSettings);
395        }
396
397        Ok(device_mode.dmPelsHeight)
398    }
399
400    /// Returns a list of all available monitors.
401    ///
402    /// # Errors
403    ///
404    /// Returns an `Error` if the monitor enumeration fails.
405    #[inline]
406    pub fn enumerate() -> Result<Vec<Self>, Error> {
407        let mut monitors: Vec<Self> = Vec::new();
408
409        unsafe {
410            EnumDisplayMonitors(
411                None,
412                None,
413                Some(Self::enum_monitors_callback),
414                LPARAM(ptr::addr_of_mut!(monitors) as isize),
415            )
416            .ok()?;
417        };
418
419        Ok(monitors)
420    }
421
422    /// Creates a `Monitor` instance from a raw `HMONITOR` handle.
423    ///
424    /// # Arguments
425    ///
426    /// * `hmonitor` - The raw `HMONITOR` handle.
427    #[must_use]
428    #[inline]
429    pub const fn from_raw_hmonitor(monitor: *mut std::ffi::c_void) -> Self {
430        Self { monitor: HMONITOR(monitor) }
431    }
432
433    /// Returns the raw `HMONITOR` handle of the monitor.
434    #[must_use]
435    #[inline]
436    pub const fn as_raw_hmonitor(&self) -> *mut std::ffi::c_void {
437        self.monitor.0
438    }
439
440    // Callback used for enumerating all monitors.
441    #[inline]
442    unsafe extern "system" fn enum_monitors_callback(
443        monitor: HMONITOR,
444        _: HDC,
445        _: *mut RECT,
446        vec: LPARAM,
447    ) -> BOOL {
448        let monitors = unsafe { &mut *(vec.0 as *mut Vec<Self>) };
449
450        monitors.push(Self { monitor });
451
452        TRUE
453    }
454}
455
456// Implements `TryIntoCaptureItemWithType` for `Monitor` to convert it to a `GraphicsCaptureItem`.
457impl TryIntoCaptureItemWithType for Monitor {
458    #[inline]
459    fn try_into_capture_item(
460        self,
461    ) -> Result<(GraphicsCaptureItem, CaptureItemTypes), windows::core::Error> {
462        let monitor = HMONITOR(self.as_raw_hmonitor());
463
464        let interop = windows::core::factory::<GraphicsCaptureItem, IGraphicsCaptureItemInterop>()?;
465
466        let item = unsafe { interop.CreateForMonitor(monitor)? };
467
468        Ok((item, CaptureItemTypes::Monitor(self)))
469    }
470}