1#![warn(clippy::cargo)]
2
3use lazy_static::lazy_static;
4use regex::Regex;
5use registry::{Hive, RegKey, Security};
6use std::{
7 fmt::{Debug, Display},
8 mem, ptr,
9};
10use thiserror::Error;
11use tracing::info;
12use winapi::{
13 shared::{
14 minwindef::{BOOL, LPARAM},
15 windef::{HDC, HMONITOR, HWND, RECT},
16 },
17 um::{
18 errhandlingapi::GetLastError,
19 wingdi::DISPLAY_DEVICEW,
20 winuser::{
21 EnumDisplayDevicesW, EnumDisplayMonitors, GetMonitorInfoW, GetWindowDC,
22 EDD_GET_DEVICE_INTERFACE_NAME, MONITORINFO, MONITORINFOEXW,
23 },
24 },
25};
26
27#[derive(Debug, Clone, Eq, PartialEq)]
28pub struct Monitor {
29 pub driver_id: String,
30 pub id: String,
31}
32
33impl Monitor {
34 const LIST_PATH: &'static str = r"SYSTEM\CurrentControlSet\Enum\DISPLAY";
35
36 pub fn all() -> Result<Vec<Self>, MonitorError> {
39 let drivers = Hive::LocalMachine
40 .open(Self::LIST_PATH, Security::Read)
41 .map_err(|e| MonitorError::ListDisplayDrivers(e.into()))?;
42
43 let mut all_monitors = vec![];
44 for driver_key in drivers.keys() {
45 let driver_id = driver_key
46 .map_err(|e| MonitorError::ListDisplayDrivers(e.into()))?
47 .to_string();
48 let mut monitors = Self::for_driver(driver_id)?;
49 all_monitors.append(&mut monitors);
50 }
51
52 Ok(all_monitors)
53 }
54
55 fn for_driver(driver_id: String) -> Result<Vec<Self>, MonitorError> {
56 let driver_key =
57 Self::driver_key(&driver_id).map_err(|e| MonitorError::ListMonitorsForDriver {
58 driver_id: driver_id.clone(),
59 source: e.into(),
60 })?;
61
62 let mut list = vec![];
63 for monitor_key in driver_key.keys() {
64 match monitor_key {
65 Ok(monitor_key) => {
66 let id = monitor_key.to_string();
67
68 list.push(Self {
69 driver_id: driver_id.clone(),
70 id,
71 });
72 }
73 Err(error) => {
74 info!(
75 ?error,
76 "Can't access a sub-key of driver {}, assuming not a monitor", driver_id
77 );
78 }
79 }
80 }
81
82 Ok(list)
83 }
84
85 pub fn intersecting(window: HWND) -> Result<Vec<Self>, MonitorError> {
91 assert!(!window.is_null());
92
93 let hdc = unsafe { GetWindowDC(window) };
94 if hdc.is_null() {
95 return Err(MonitorError::ListIntersecting {
96 window: window as usize,
97 source: WinError::last(),
98 });
99 }
100 extern "system" fn cb(h: HMONITOR, _ctx: HDC, _rect: *mut RECT, list_ptr: LPARAM) -> BOOL {
101 let mut info = MONITORINFOEXW {
102 cbSize: mem::size_of::<MONITORINFOEXW>() as u32,
103 ..Default::default()
104 };
105 unsafe {
106 GetMonitorInfoW(h, &mut info as *mut MONITORINFOEXW as *mut MONITORINFO);
107 }
108
109 let mut interface_name = DISPLAY_DEVICEW {
110 cb: mem::size_of::<DISPLAY_DEVICEW>() as u32,
111 ..Default::default()
112 };
113 let status = unsafe {
114 EnumDisplayDevicesW(
115 &info.szDevice[0],
116 0,
117 &mut interface_name,
118 EDD_GET_DEVICE_INTERFACE_NAME,
119 )
120 };
121 if status == 0 {
122 panic!();
123 }
124 let interface_name = wchars_to_string(&interface_name.DeviceID);
125
126 let list_ptr = list_ptr as *mut Vec<Result<Monitor, MonitorError>>;
127 let list = unsafe { &mut *list_ptr };
128
129 let monitor = Monitor::from_interface_name(&interface_name);
130 list.push(monitor);
131
132 BOOL::from(true) }
134
135 let mut monitors = Vec::<Result<Monitor, MonitorError>>::new();
136 let monitors_ptr = &mut monitors as *mut Vec<_> as LPARAM;
137 unsafe {
138 EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(cb), monitors_ptr);
139 }
140
141 monitors.into_iter().collect()
142 }
143
144 fn from_interface_name(name: &str) -> Result<Self, MonitorError> {
145 lazy_static! {
146 static ref RE: Regex = Regex::new(
147 r"(?x)^
148 \\\\\?\\DISPLAY
149 \#(?P<d>[A-Z0-9]+)
150 \#(?P<m>[A-Za-z0-9&]+)
151 \#\{.*?\}
152 $"
153 )
154 .unwrap();
155 }
156
157 let caps = RE
158 .captures(name)
159 .ok_or_else(|| MonitorError::InvalidInterface(name.to_string()))?;
160
161 let driver_id = caps.name("d").unwrap().as_str().to_string();
162 let monitor_id = caps.name("m").unwrap().as_str().to_string();
163
164 Ok(Self {
165 driver_id,
166 id: monitor_id,
167 })
168 }
169
170 pub fn edid(&self) -> Result<Vec<u8>, MonitorError> {
177 let data = self
178 .params_key()?
179 .value(r"EDID")
180 .map_err(|err| MonitorError::GetEdid {
181 monitor: self.clone(),
182 source: err.into(),
183 })?;
184
185 let bytes = match data {
186 registry::value::Data::Binary(bytes) => bytes,
187 _ => unreachable!("EDID will always be in bytes"),
188 };
189
190 Ok(bytes)
191 }
192
193 fn params_key(&self) -> Result<RegKey, MonitorError> {
194 fn helper(monitor: &Monitor) -> Result<RegKey, RegistryError> {
195 let driver_key = Monitor::driver_key(&monitor.driver_id)?;
196 let monitor_key = driver_key.open(&monitor.id, Security::Read)?;
197 let data = monitor_key.open(r"Device Parameters", Security::Read)?;
198 Ok(data)
199 }
200
201 helper(self).map_err(|source| MonitorError::GetParams {
202 monitor: self.clone(),
203 source,
204 })
205 }
206
207 fn driver_key(driver_id: &str) -> Result<registry::RegKey, registry::key::Error> {
208 let path = format!(r"{}\{}", Self::LIST_PATH, driver_id);
209 Hive::LocalMachine.open(path, Security::Read)
210 }
211}
212
213#[derive(Debug, Error)]
214pub enum MonitorError {
215 #[error("Error listing display drivers to get monitors")]
216 ListDisplayDrivers(#[source] RegistryError),
217 #[error("Error listing monitors for display driver {driver_id}")]
218 ListMonitorsForDriver {
219 driver_id: String,
220 #[source]
221 source: RegistryError,
222 },
223 #[error("Error getting EDID for monitor {monitor:?}")]
224 GetEdid {
225 monitor: Monitor,
226 #[source]
227 source: RegistryError,
228 },
229 #[error("Could not get monitors intersecting window with hwnd {window}. Windows error or invalid hwnd.")]
230 ListIntersecting {
231 window: usize,
232 #[source]
233 source: WinError,
234 },
235 #[error("Error parsing monitor interface name. Expected something like \\\\?DISPLAY#MEI96A2#4&289d...#{{...}}, got: {0}")]
236 InvalidInterface(String),
237 #[error("Failed to get monitor parameters from the registry")]
238 GetParams {
239 monitor: Monitor,
240 #[source]
241 source: RegistryError,
242 },
243}
244
245#[derive(Debug, Error)]
246pub enum RegistryError {
247 #[error(transparent)]
248 Key(#[from] registry::key::Error),
249 #[error(transparent)]
250 Value(#[from] registry::value::Error),
251 #[error(transparent)]
252 KeyIter(#[from] registry::iter::keys::Error),
253}
254
255pub(crate) fn wchars_to_string(wchars: &[u16]) -> String {
256 let end = wchars.iter().position(|&i| i == 0).unwrap_or(wchars.len());
258 let (wchars, _) = wchars.split_at(end);
259
260 String::from_utf16_lossy(wchars)
261}
262
263#[derive(PartialEq, Eq, Clone, Copy, Error)]
264pub struct WinError(u32);
265
266impl WinError {
267 pub fn code(&self) -> u32 {
268 self.0
269 }
270
271 pub fn last() -> Self {
272 let code = unsafe { GetLastError() };
273 Self(code)
274 }
275}
276
277impl Display for WinError {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 let code = self.0;
280 f.debug_tuple("WinError")
281 .field(&format!("0x{:X}", code))
282 .finish()
283 }
284}
285
286impl Debug for WinError {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 Display::fmt(self, f)
289 }
290}
291
292impl From<u32> for WinError {
293 fn from(code: u32) -> Self {
294 Self(code)
295 }
296}
297
298impl From<i32> for WinError {
299 fn from(code: i32) -> Self {
300 assert!(code > 0);
301 Self(code as u32)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn can_list_monitors() {
311 let list = Monitor::all().unwrap();
312 println!("{:#?}", list)
313 }
314
315 #[test]
316 fn can_get_edids() {
317 let monitors = Monitor::all().unwrap();
318 let edids = monitors.iter().flat_map(Monitor::edid).collect::<Vec<_>>();
319 assert!(edids.len() > 0);
320 println!("{:#?}", edids);
321 }
322
323 #[test]
324 fn can_list_monitors_for_hwnd() {
325 use winit::{
326 event_loop::{ControlFlow, EventLoop},
327 platform::windows::{EventLoopExtWindows, WindowExtWindows},
328 window::WindowBuilder,
329 };
330
331 let event_loop = EventLoop::<()>::new_any_thread();
332 let window = WindowBuilder::new().build(&event_loop).unwrap();
333
334 let mut already_ran = false;
335 event_loop.run(move |_event, _, control_flow| {
336 if !already_ran {
337 let hwnd = window.hwnd() as HWND;
338 let monitors = Monitor::intersecting(hwnd).unwrap();
339
340 assert!(monitors.len() == 1);
341 let monitor = &monitors[0];
342 eprintln!("{:#?}", monitor);
343
344 let edid = monitor.edid().unwrap();
345 eprintln!("edid: {:?}...", &edid[..20]);
346
347 already_ran = true;
348 }
349
350 *control_flow = ControlFlow::Exit;
351 });
352 }
353}