display.rs

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