display.rs

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