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#[derive(Eq, PartialEq, Clone, Copy, Debug)]
60pub struct Monitor {
61 monitor: HMONITOR,
62}
63
64unsafe impl Send for Monitor {}
65
66impl Monitor {
67 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
448 #[inline]
449 pub const fn as_raw_hmonitor(&self) -> *mut std::ffi::c_void {
450 self.monitor.0
451 }
452
453 #[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
469impl 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}