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, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
 11
 12#[derive(Debug)]
 13pub(crate) struct WindowsDisplay {
 14    pub handle: HMONITOR,
 15    pub display_id: DisplayId,
 16    bounds: Bounds<GlobalPixels>,
 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: GlobalPixels(size.left as f32),
 37                    y: GlobalPixels(size.top as f32),
 38                },
 39                size: Size {
 40                    width: GlobalPixels((size.right - size.left) as f32),
 41                    height: GlobalPixels((size.bottom - size.top) as f32),
 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: GlobalPixels(size.left as f32),
 63                    y: GlobalPixels(size.top as f32),
 64                },
 65                size: Size {
 66                    width: GlobalPixels((size.right - size.left) as f32),
 67                    height: GlobalPixels((size.bottom - size.top) as f32),
 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: GlobalPixels(size.left as f32),
 85                    y: GlobalPixels(size.top as f32),
 86                },
 87                size: Size {
 88                    width: GlobalPixels((size.right - size.left) as f32),
 89                    height: GlobalPixels((size.bottom - size.top) as f32),
 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        available_monitors()
125            .get(self.display_id.0 as usize)
126            .and_then(|hmonitor| get_monitor_info(*hmonitor).ok())
127            .and_then(|info| {
128                let mut devmode = DEVMODEW::default();
129                unsafe {
130                    EnumDisplaySettingsW(
131                        PCWSTR(info.szDevice.as_ptr()),
132                        ENUM_CURRENT_SETTINGS,
133                        &mut devmode,
134                    )
135                }
136                .as_bool()
137                .then(|| devmode.dmDisplayFrequency)
138            })
139    }
140}
141
142impl PlatformDisplay for WindowsDisplay {
143    fn id(&self) -> DisplayId {
144        self.display_id
145    }
146
147    fn uuid(&self) -> anyhow::Result<Uuid> {
148        Ok(self.uuid)
149    }
150
151    fn bounds(&self) -> Bounds<GlobalPixels> {
152        self.bounds
153    }
154}
155
156fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
157    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
158    unsafe {
159        EnumDisplayMonitors(
160            HDC::default(),
161            None,
162            Some(monitor_enum_proc),
163            LPARAM(&mut monitors as *mut _ as _),
164        );
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}