display.rs

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