display.rs

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