monitor_control_win/
lib.rs

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    /// List all monitor-looking things we can find. Expect this to return
37    /// spurious and duplicate results.
38    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    /// List all monitors a window is on.
86    ///
87    /// If your window is on exactly one monitor, this should return exactly
88    /// one result. Unlike [`Self::all`], your should not get spurious or
89    /// duplicate results.
90    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) // continue enumerating
133        }
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    /// Get the Extended Device Identification Data of a monitor.
171    ///
172    /// You can feed this to an [EDID parser][edid-parser-crate] to get
173    /// information about the display such as the model name or colorspace.
174    ///
175    /// [edid-parser-crate]: https://crates.io/crates/edid
176    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    // Take up to null
257    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}