display.rs

  1use itertools::Itertools;
  2use smallvec::SmallVec;
  3use std::rc::Rc;
  4use util::ResultExt;
  5use uuid::Uuid;
  6use windows::{
  7    core::*,
  8    Win32::{
  9        Foundation::*,
 10        Graphics::Gdi::*,
 11        UI::{
 12            HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},
 13            WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,
 14        },
 15    },
 16};
 17
 18use crate::{logical_point, point, size, Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay};
 19
 20#[derive(Debug, Clone, Copy)]
 21pub(crate) struct WindowsDisplay {
 22    pub handle: HMONITOR,
 23    pub display_id: DisplayId,
 24    scale_factor: f32,
 25    bounds: Bounds<Pixels>,
 26    physical_bounds: Bounds<DevicePixels>,
 27    uuid: Uuid,
 28}
 29
 30impl WindowsDisplay {
 31    pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
 32        let screen = available_monitors().into_iter().nth(display_id.0 as _)?;
 33        let info = get_monitor_info(screen).log_err()?;
 34        let monitor_size = info.monitorInfo.rcMonitor;
 35        let uuid = generate_uuid(&info.szDevice);
 36        let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
 37        let physical_size = size(
 38            (monitor_size.right - monitor_size.left).into(),
 39            (monitor_size.bottom - monitor_size.top).into(),
 40        );
 41
 42        Some(WindowsDisplay {
 43            handle: screen,
 44            display_id,
 45            scale_factor,
 46            bounds: Bounds {
 47                origin: logical_point(
 48                    monitor_size.left as f32,
 49                    monitor_size.top as f32,
 50                    scale_factor,
 51                ),
 52                size: physical_size.to_pixels(scale_factor),
 53            },
 54            physical_bounds: Bounds {
 55                origin: point(monitor_size.left.into(), monitor_size.top.into()),
 56                size: physical_size,
 57            },
 58            uuid,
 59        })
 60    }
 61
 62    pub fn new_with_handle(monitor: HMONITOR) -> Self {
 63        let info = get_monitor_info(monitor).expect("unable to get monitor info");
 64        let monitor_size = info.monitorInfo.rcMonitor;
 65        let uuid = generate_uuid(&info.szDevice);
 66        let display_id = available_monitors()
 67            .iter()
 68            .position(|handle| handle.0 == monitor.0)
 69            .unwrap();
 70        let scale_factor =
 71            get_scale_factor_for_monitor(monitor).expect("unable to get scale factor for monitor");
 72        let physical_size = size(
 73            (monitor_size.right - monitor_size.left).into(),
 74            (monitor_size.bottom - monitor_size.top).into(),
 75        );
 76
 77        WindowsDisplay {
 78            handle: monitor,
 79            display_id: DisplayId(display_id as _),
 80            scale_factor,
 81            bounds: Bounds {
 82                origin: logical_point(
 83                    monitor_size.left as f32,
 84                    monitor_size.top as f32,
 85                    scale_factor,
 86                ),
 87                size: physical_size.to_pixels(scale_factor),
 88            },
 89            physical_bounds: Bounds {
 90                origin: point(monitor_size.left.into(), monitor_size.top.into()),
 91                size: physical_size,
 92            },
 93            uuid,
 94        }
 95    }
 96
 97    fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {
 98        let info = get_monitor_info(handle).expect("unable to get monitor info");
 99        let monitor_size = info.monitorInfo.rcMonitor;
100        let uuid = generate_uuid(&info.szDevice);
101        let scale_factor =
102            get_scale_factor_for_monitor(handle).expect("unable to get scale factor for monitor");
103        let physical_size = size(
104            (monitor_size.right - monitor_size.left).into(),
105            (monitor_size.bottom - monitor_size.top).into(),
106        );
107
108        WindowsDisplay {
109            handle,
110            display_id,
111            scale_factor,
112            bounds: Bounds {
113                origin: logical_point(
114                    monitor_size.left as f32,
115                    monitor_size.top as f32,
116                    scale_factor,
117                ),
118                size: physical_size.to_pixels(scale_factor),
119            },
120            physical_bounds: Bounds {
121                origin: point(monitor_size.left.into(), monitor_size.top.into()),
122                size: physical_size,
123            },
124            uuid,
125        }
126    }
127
128    pub fn primary_monitor() -> Option<Self> {
129        // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
130        const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
131        let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };
132        if monitor.is_invalid() {
133            log::error!(
134                "can not find the primary monitor: {}",
135                std::io::Error::last_os_error()
136            );
137            return None;
138        }
139        Some(WindowsDisplay::new_with_handle(monitor))
140    }
141
142    /// Check if the center point of given bounds is inside this monitor
143    pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {
144        let center = bounds.center();
145        let center = POINT {
146            x: (center.x.0 * self.scale_factor) as i32,
147            y: (center.y.0 * self.scale_factor) as i32,
148        };
149        let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };
150        if monitor.is_invalid() {
151            false
152        } else {
153            let display = WindowsDisplay::new_with_handle(monitor);
154            display.uuid == self.uuid
155        }
156    }
157
158    pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
159        available_monitors()
160            .into_iter()
161            .enumerate()
162            .map(|(id, handle)| {
163                Rc::new(WindowsDisplay::new_with_handle_and_id(
164                    handle,
165                    DisplayId(id as _),
166                )) as Rc<dyn PlatformDisplay>
167            })
168            .collect()
169    }
170
171    pub(crate) fn frequency(&self) -> Option<u32> {
172        get_monitor_info(self.handle).ok().and_then(|info| {
173            let mut devmode = DEVMODEW::default();
174            unsafe {
175                EnumDisplaySettingsW(
176                    PCWSTR(info.szDevice.as_ptr()),
177                    ENUM_CURRENT_SETTINGS,
178                    &mut devmode,
179                )
180            }
181            .as_bool()
182            .then(|| devmode.dmDisplayFrequency)
183        })
184    }
185
186    /// Check if this monitor is still online
187    pub fn is_connected(hmonitor: HMONITOR) -> bool {
188        available_monitors().iter().contains(&hmonitor)
189    }
190
191    pub fn physical_bounds(&self) -> Bounds<DevicePixels> {
192        self.physical_bounds
193    }
194}
195
196impl PlatformDisplay for WindowsDisplay {
197    fn id(&self) -> DisplayId {
198        self.display_id
199    }
200
201    fn uuid(&self) -> anyhow::Result<Uuid> {
202        Ok(self.uuid)
203    }
204
205    fn bounds(&self) -> Bounds<Pixels> {
206        self.bounds
207    }
208}
209
210fn available_monitors() -> SmallVec<[HMONITOR; 4]> {
211    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();
212    unsafe {
213        EnumDisplayMonitors(
214            HDC::default(),
215            None,
216            Some(monitor_enum_proc),
217            LPARAM(&mut monitors as *mut _ as _),
218        )
219        .ok()
220        .log_err();
221    }
222    monitors
223}
224
225unsafe extern "system" fn monitor_enum_proc(
226    hmonitor: HMONITOR,
227    _hdc: HDC,
228    _place: *mut RECT,
229    data: LPARAM,
230) -> BOOL {
231    let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;
232    unsafe { (*monitors).push(hmonitor) };
233    BOOL(1)
234}
235
236fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {
237    let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };
238    monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;
239    let status = unsafe {
240        GetMonitorInfoW(
241            hmonitor,
242            &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,
243        )
244    };
245    if status.as_bool() {
246        Ok(monitor_info)
247    } else {
248        Err(anyhow::anyhow!(std::io::Error::last_os_error()))
249    }
250}
251
252fn generate_uuid(device_name: &[u16]) -> Uuid {
253    let name = device_name
254        .iter()
255        .flat_map(|&a| a.to_be_bytes().to_vec())
256        .collect_vec();
257    Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)
258}
259
260fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {
261    let mut dpi_x = 0;
262    let mut dpi_y = 0;
263    unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;
264    assert_eq!(dpi_x, dpi_y);
265    Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)
266}