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