display.rs

  1use std::rc::Rc;
  2
  3use itertools::Itertools;
  4use smallvec::SmallVec;
  5use uuid::Uuid;
  6use windows::Win32::{
  7    Foundation::{BOOL, LPARAM, POINT, RECT},
  8    Graphics::Gdi::{
  9        EnumDisplayMonitors, GetMonitorInfoW, MonitorFromPoint, HDC, HMONITOR, MONITORINFO,
 10        MONITORINFOEXW, MONITOR_DEFAULTTOPRIMARY,
 11    },
 12};
 13
 14use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
 15
 16#[derive(Debug)]
 17pub(crate) struct WindowsDisplay {
 18    pub display_id: DisplayId,
 19    bounds: Bounds<GlobalPixels>,
 20    uuid: Uuid,
 21}
 22
 23impl WindowsDisplay {
 24    pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
 25        let Some(screen) = available_monitors().into_iter().nth(display_id.0 as _) else {
 26            return None;
 27        };
 28        let Ok(info) = get_monitor_info(screen).inspect_err(|e| log::error!("{}", e)) else {
 29            return None;
 30        };
 31        let size = info.monitorInfo.rcMonitor;
 32        let uuid = generate_uuid(&info.szDevice);
 33
 34        Some(WindowsDisplay {
 35            display_id,
 36            bounds: Bounds {
 37                origin: Point {
 38                    x: GlobalPixels(size.left as f32),
 39                    y: GlobalPixels(size.top as f32),
 40                },
 41                size: Size {
 42                    width: GlobalPixels((size.right - size.left) as f32),
 43                    height: GlobalPixels((size.bottom - size.top) as f32),
 44                },
 45            },
 46            uuid,
 47        })
 48    }
 49
 50    fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
 51        let info = get_monitor_info(handle).expect("unable to get monitor info");
 52        let size = info.monitorInfo.rcMonitor;
 53        let uuid = generate_uuid(&info.szDevice);
 54
 55        WindowsDisplay {
 56            display_id,
 57            bounds: Bounds {
 58                origin: Point {
 59                    x: GlobalPixels(size.left as f32),
 60                    y: GlobalPixels(size.top as f32),
 61                },
 62                size: Size {
 63                    width: GlobalPixels((size.right - size.left) as f32),
 64                    height: GlobalPixels((size.bottom - size.top) as f32),
 65                },
 66            },
 67            uuid,
 68        }
 69    }
 70
 71    pub fn primary_monitor() -> Option<Self> {
 72        // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
 73        const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
 74        let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
 75        if monitor.is_invalid() {
 76            log::error!(
 77                "can not find the primary monitor: {}",
 78                std::io::Error::last_os_error()
 79            );
 80            return None;
 81        }
 82        let Some(display_id) = available_monitors()
 83            .iter()
 84            .position(|handle| handle.0 == monitor.0)
 85        else {
 86            return None;
 87        };
 88
 89        Some(WindowsDisplay::new_with_handle_and_id(
 90            monitor,
 91            DisplayId(display_id as _),
 92        ))
 93    }
 94
 95    pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
 96        available_monitors()
 97            .into_iter()
 98            .enumerate()
 99            .map(|(id, handle)| {
100                Rc::new(WindowsDisplay::new_with_handle_and_id(
101                    handle,
102                    DisplayId(id as _),
103                )) as Rc<dyn PlatformDisplay>
104            })
105            .collect()
106    }
107}
108
109impl PlatformDisplay for WindowsDisplay {
110    fn id(&self) -> DisplayId {
111        self.display_id
112    }
113
114    fn uuid(&self) -> anyhow::Result<Uuid> {
115        Ok(self.uuid)
116    }
117
118    fn bounds(&self) -> Bounds<GlobalPixels> {
119        self.bounds
120    }
121}
122
123fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
124    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
125    unsafe {
126        EnumDisplayMonitors(
127            HDC::default(),
128            None,
129            Some(monitor_enum_proc),
130            LPARAM(&mut monitors as *mut _ as _),
131        );
132    }
133    monitors
134}
135
136unsafe extern "system" fn monitor_enum_proc(
137    hmonitor: HMONITOR,
138    _hdc: HDC,
139    _place: *mut RECT,
140    data: LPARAM,
141) -> BOOL {
142    let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
143    unsafe { (*monitors).push(hmonitor) };
144    BOOL(1)
145}
146
147fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
148    let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
149    monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
150    let status = unsafe {
151        GetMonitorInfoW(
152            hmonitor,
153            &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
154        )
155    };
156    if status.as_bool() {
157        Ok(monitor_info)
158    } else {
159        Err(anyhow::anyhow!(std::io::Error::last_os_error()))
160    }
161}
162
163fn generate_uuid(device_name: &[u16]) -> Uuid {
164    let name = device_name
165        .iter()
166        .flat_map(|&a| a.to_be_bytes().to_vec())
167        .collect_vec();
168    Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
169}