Skip to main content

windows_capture/
monitor.rs

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