display.rs

  1use itertools::Itertools;
  2use smallvec::SmallVec;
  3use std::rc::Rc;
  4use util::ResultExt;
  5use uuid::Uuid;
  6use windows::{
  7    core::*,
  8    Win32::{Foundation::*, Graphics::Gdi::*},
  9};
 10
 11use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point, Size};
 12
 13#[derive(Debug, Clone, Copy)]
 14pub(crate) struct WindowsDisplay {
 15    pub handle: HMONITOR,
 16    pub display_id: DisplayId,
 17    bounds: Bounds<DevicePixels>,
 18    uuid: Uuid,
 19}
 20
 21impl WindowsDisplay {
 22    pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
 23        let Some(screen) = available_monitors().into_iter().nth(display_id.0 as _) else {
 24            return None;
 25        };
 26        let Ok(info) = get_monitor_info(screen).inspect_err(|e| log::error!("{}", e)) else {
 27            return None;
 28        };
 29        let size = info.monitorInfo.rcMonitor;
 30        let uuid = generate_uuid(&info.szDevice);
 31
 32        Some(WindowsDisplay {
 33            handle: screen,
 34            display_id,
 35            bounds: Bounds {
 36                origin: Point {
 37                    x: DevicePixels(size.left),
 38                    y: DevicePixels(size.top),
 39                },
 40                size: Size {
 41                    width: DevicePixels(size.right - size.left),
 42                    height: DevicePixels(size.bottom - size.top),
 43                },
 44            },
 45            uuid,
 46        })
 47    }
 48
 49    pub fn new_with_handle(monitor: HMONITOR) -> Self {
 50        let info = get_monitor_info(monitor).expect("unable to get monitor info");
 51        let size = info.monitorInfo.rcMonitor;
 52        let uuid = generate_uuid(&info.szDevice);
 53        let display_id = available_monitors()
 54            .iter()
 55            .position(|handle| handle.0 == monitor.0)
 56            .unwrap();
 57
 58        WindowsDisplay {
 59            handle: monitor,
 60            display_id: DisplayId(display_id as _),
 61            bounds: Bounds {
 62                origin: Point {
 63                    x: DevicePixels(size.left as i32),
 64                    y: DevicePixels(size.top as i32),
 65                },
 66                size: Size {
 67                    width: DevicePixels((size.right - size.left) as i32),
 68                    height: DevicePixels((size.bottom - size.top) as i32),
 69                },
 70            },
 71            uuid,
 72        }
 73    }
 74
 75    fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
 76        let info = get_monitor_info(handle).expect("unable to get monitor info");
 77        let size = info.monitorInfo.rcMonitor;
 78        let uuid = generate_uuid(&info.szDevice);
 79
 80        WindowsDisplay {
 81            handle,
 82            display_id,
 83            bounds: Bounds {
 84                origin: Point {
 85                    x: DevicePixels(size.left as i32),
 86                    y: DevicePixels(size.top as i32),
 87                },
 88                size: Size {
 89                    width: DevicePixels((size.right - size.left) as i32),
 90                    height: DevicePixels((size.bottom - size.top) as i32),
 91                },
 92            },
 93            uuid,
 94        }
 95    }
 96
 97    pub fn primary_monitor() -> Option<Self> {
 98        // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
 99        const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
100        let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
101        if monitor.is_invalid() {
102            log::error!(
103                "can not find the primary monitor: {}",
104                std::io::Error::last_os_error()
105            );
106            return None;
107        }
108        Some(WindowsDisplay::new_with_handle(monitor))
109    }
110
111    /// Check if the center point of given bounds is inside this monitor
112    pub fn check_given_bounds(&self, bounds: Bounds<DevicePixels>) -> bool {
113        let center = bounds.center();
114        let center = POINT {
115            x: center.x.0,
116            y: center.y.0,
117        };
118        let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
119        if monitor.is_invalid() {
120            false
121        } else {
122            let display = WindowsDisplay::new_with_handle(monitor);
123            display.uuid == self.uuid
124        }
125    }
126
127    pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
128        available_monitors()
129            .into_iter()
130            .enumerate()
131            .map(|(id, handle)| {
132                Rc::new(WindowsDisplay::new_with_handle_and_id(
133                    handle,
134                    DisplayId(id as _),
135                )) as Rc<dyn PlatformDisplay>
136            })
137            .collect()
138    }
139
140    pub(crate) fn frequency(&self) -> Option<u32> {
141        get_monitor_info(self.handle).ok().and_then(|info| {
142            let mut devmode = DEVMODEW::default();
143            unsafe {
144                EnumDisplaySettingsW(
145                    PCWSTR(info.szDevice.as_ptr()),
146                    ENUM_CURRENT_SETTINGS,
147                    &mut devmode,
148                )
149            }
150            .as_bool()
151            .then(|| devmode.dmDisplayFrequency)
152        })
153    }
154
155    /// Check if this monitor is still online
156    pub fn is_connected(hmonitor: HMONITOR) -> bool {
157        available_monitors().iter().contains(&hmonitor)
158    }
159}
160
161impl PlatformDisplay for WindowsDisplay {
162    fn id(&self) -> DisplayId {
163        self.display_id
164    }
165
166    fn uuid(&self) -> anyhow::Result<Uuid> {
167        Ok(self.uuid)
168    }
169
170    fn bounds(&self) -> Bounds<DevicePixels> {
171        self.bounds
172    }
173}
174
175fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
176    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
177    unsafe {
178        EnumDisplayMonitors(
179            HDC::default(),
180            None,
181            Some(monitor_enum_proc),
182            LPARAM(&mut monitors as *mut _ as _),
183        )
184        .ok()
185        .log_err();
186    }
187    monitors
188}
189
190unsafe extern "system" fn monitor_enum_proc(
191    hmonitor: HMONITOR,
192    _hdc: HDC,
193    _place: *mut RECT,
194    data: LPARAM,
195) -> BOOL {
196    let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
197    unsafe { (*monitors).push(hmonitor) };
198    BOOL(1)
199}
200
201fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
202    let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
203    monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
204    let status = unsafe {
205        GetMonitorInfoW(
206            hmonitor,
207            &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
208        )
209    };
210    if status.as_bool() {
211        Ok(monitor_info)
212    } else {
213        Err(anyhow::anyhow!(std::io::Error::last_os_error()))
214    }
215}
216
217fn generate_uuid(device_name: &[u16]) -> Uuid {
218    let name = device_name
219        .iter()
220        .flat_map(|&a| a.to_be_bytes().to_vec())
221        .collect_vec();
222    Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
223}