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    pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
112        available_monitors()
113            .into_iter()
114            .enumerate()
115            .map(|(id, handle)| {
116                Rc::new(WindowsDisplay::new_with_handle_and_id(
117                    handle,
118                    DisplayId(id as _),
119                )) as Rc<dyn PlatformDisplay>
120            })
121            .collect()
122    }
123
124    pub(crate) fn frequency(&self) -> Option<u32> {
125        get_monitor_info(self.handle).ok().and_then(|info| {
126            let mut devmode = DEVMODEW::default();
127            unsafe {
128                EnumDisplaySettingsW(
129                    PCWSTR(info.szDevice.as_ptr()),
130                    ENUM_CURRENT_SETTINGS,
131                    &mut devmode,
132                )
133            }
134            .as_bool()
135            .then(|| devmode.dmDisplayFrequency)
136        })
137    }
138}
139
140impl PlatformDisplay for WindowsDisplay {
141    fn id(&self) -> DisplayId {
142        self.display_id
143    }
144
145    fn uuid(&self) -> anyhow::Result<Uuid> {
146        Ok(self.uuid)
147    }
148
149    fn bounds(&self) -> Bounds<DevicePixels> {
150        self.bounds
151    }
152}
153
154fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
155    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
156    unsafe {
157        EnumDisplayMonitors(
158            HDC::default(),
159            None,
160            Some(monitor_enum_proc),
161            LPARAM(&mut monitors as *mut _ as _),
162        )
163        .ok()
164        .log_err();
165    }
166    monitors
167}
168
169unsafe extern "system" fn monitor_enum_proc(
170    hmonitor: HMONITOR,
171    _hdc: HDC,
172    _place: *mut RECT,
173    data: LPARAM,
174) -> BOOL {
175    let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
176    unsafe { (*monitors).push(hmonitor) };
177    BOOL(1)
178}
179
180fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
181    let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
182    monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
183    let status = unsafe {
184        GetMonitorInfoW(
185            hmonitor,
186            &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
187        )
188    };
189    if status.as_bool() {
190        Ok(monitor_info)
191    } else {
192        Err(anyhow::anyhow!(std::io::Error::last_os_error()))
193    }
194}
195
196fn generate_uuid(device_name: &[u16]) -> Uuid {
197    let name = device_name
198        .iter()
199        .flat_map(|&a| a.to_be_bytes().to_vec())
200        .collect_vec();
201    Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
202}