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#[derive(Eq, PartialEq, Clone, Copy, Debug)]
61pub struct Monitor {
62 monitor: HMONITOR,
63}
64
65unsafe impl Send for Monitor {}
66
67impl Monitor {
68 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
435 #[inline]
436 pub const fn as_raw_hmonitor(&self) -> *mut std::ffi::c_void {
437 self.monitor.0
438 }
439
440 #[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
456impl 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}