windows_capture/
monitor.rs

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