270 lines · 8.4 KB · Rust
RawDownload
use itertools::Itertools;use smallvec::SmallVec;use std::rc::Rc;use util::ResultExt;use uuid::Uuid;use windows::{    core::*,    Win32::{        Foundation::*,        Graphics::Gdi::*,        UI::{            HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},            WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,        },    },}; use crate::{logical_point, point, size, Bounds, DevicePixels, DisplayId, Pixels, PlatformDisplay}; #[derive(Debug, Clone, Copy)]pub(crate) struct WindowsDisplay {    pub handle: HMONITOR,    pub display_id: DisplayId,    scale_factor: f32,    bounds: Bounds<Pixels>,    physical_bounds: Bounds<DevicePixels>,    uuid: Uuid,} // The `HMONITOR` is thread-safe.unsafe impl Send for WindowsDisplay {}unsafe impl Sync for WindowsDisplay {} impl WindowsDisplay {    pub(crate) fn new(display_id: DisplayId) -> Option<Self> {        let screen = available_monitors().into_iter().nth(display_id.0 as _)?;        let info = get_monitor_info(screen).log_err()?;        let monitor_size = info.monitorInfo.rcMonitor;        let uuid = generate_uuid(&info.szDevice);        let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;        let physical_size = size(            (monitor_size.right - monitor_size.left).into(),            (monitor_size.bottom - monitor_size.top).into(),        );         Some(WindowsDisplay {            handle: screen,            display_id,            scale_factor,            bounds: Bounds {                origin: logical_point(                    monitor_size.left as f32,                    monitor_size.top as f32,                    scale_factor,                ),                size: physical_size.to_pixels(scale_factor),            },            physical_bounds: Bounds {                origin: point(monitor_size.left.into(), monitor_size.top.into()),                size: physical_size,            },            uuid,        })    }     pub fn new_with_handle(monitor: HMONITOR) -> Self {        let info = get_monitor_info(monitor).expect("unable to get monitor info");        let monitor_size = info.monitorInfo.rcMonitor;        let uuid = generate_uuid(&info.szDevice);        let display_id = available_monitors()            .iter()            .position(|handle| handle.0 == monitor.0)            .unwrap();        let scale_factor =            get_scale_factor_for_monitor(monitor).expect("unable to get scale factor for monitor");        let physical_size = size(            (monitor_size.right - monitor_size.left).into(),            (monitor_size.bottom - monitor_size.top).into(),        );         WindowsDisplay {            handle: monitor,            display_id: DisplayId(display_id as _),            scale_factor,            bounds: Bounds {                origin: logical_point(                    monitor_size.left as f32,                    monitor_size.top as f32,                    scale_factor,                ),                size: physical_size.to_pixels(scale_factor),            },            physical_bounds: Bounds {                origin: point(monitor_size.left.into(), monitor_size.top.into()),                size: physical_size,            },            uuid,        }    }     fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> Self {        let info = get_monitor_info(handle).expect("unable to get monitor info");        let monitor_size = info.monitorInfo.rcMonitor;        let uuid = generate_uuid(&info.szDevice);        let scale_factor =            get_scale_factor_for_monitor(handle).expect("unable to get scale factor for monitor");        let physical_size = size(            (monitor_size.right - monitor_size.left).into(),            (monitor_size.bottom - monitor_size.top).into(),        );         WindowsDisplay {            handle,            display_id,            scale_factor,            bounds: Bounds {                origin: logical_point(                    monitor_size.left as f32,                    monitor_size.top as f32,                    scale_factor,                ),                size: physical_size.to_pixels(scale_factor),            },            physical_bounds: Bounds {                origin: point(monitor_size.left.into(), monitor_size.top.into()),                size: physical_size,            },            uuid,        }    }     pub fn primary_monitor() -> Option<Self> {        // https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643        const POINT_ZERO: POINT = POINT { x: 0, y: 0 };        let monitor = unsafe { MonitorFromPoint(POINT_ZERO, MONITOR_DEFAULTTOPRIMARY) };        if monitor.is_invalid() {            log::error!(                "can not find the primary monitor: {}",                std::io::Error::last_os_error()            );            return None;        }        Some(WindowsDisplay::new_with_handle(monitor))    }     /// Check if the center point of given bounds is inside this monitor    pub fn check_given_bounds(&self, bounds: Bounds<Pixels>) -> bool {        let center = bounds.center();        let center = POINT {            x: (center.x.0 * self.scale_factor) as i32,            y: (center.y.0 * self.scale_factor) as i32,        };        let monitor = unsafe { MonitorFromPoint(center, MONITOR_DEFAULTTONULL) };        if monitor.is_invalid() {            false        } else {            let display = WindowsDisplay::new_with_handle(monitor);            display.uuid == self.uuid        }    }     pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {        available_monitors()            .into_iter()            .enumerate()            .map(|(id, handle)| {                Rc::new(WindowsDisplay::new_with_handle_and_id(                    handle,                    DisplayId(id as _),                )) as Rc<dyn PlatformDisplay>            })            .collect()    }     pub(crate) fn frequency(&self) -> Option<u32> {        get_monitor_info(self.handle).ok().and_then(|info| {            let mut devmode = DEVMODEW::default();            unsafe {                EnumDisplaySettingsW(                    PCWSTR(info.szDevice.as_ptr()),                    ENUM_CURRENT_SETTINGS,                    &mut devmode,                )            }            .as_bool()            .then(|| devmode.dmDisplayFrequency)        })    }     /// Check if this monitor is still online    pub fn is_connected(hmonitor: HMONITOR) -> bool {        available_monitors().iter().contains(&hmonitor)    }     pub fn physical_bounds(&self) -> Bounds<DevicePixels> {        self.physical_bounds    }} impl PlatformDisplay for WindowsDisplay {    fn id(&self) -> DisplayId {        self.display_id    }     fn uuid(&self) -> anyhow::Result<Uuid> {        Ok(self.uuid)    }     fn bounds(&self) -> Bounds<Pixels> {        self.bounds    }} fn available_monitors() -> SmallVec<[HMONITOR; 4]> {    let mut monitors: SmallVec<[HMONITOR; 4]> = SmallVec::new();    unsafe {        EnumDisplayMonitors(            HDC::default(),            None,            Some(monitor_enum_proc),            LPARAM(&mut monitors as *mut _ as _),        )        .ok()        .log_err();    }    monitors} unsafe extern "system" fn monitor_enum_proc(    hmonitor: HMONITOR,    _hdc: HDC,    _place: *mut RECT,    data: LPARAM,) -> BOOL {    let monitors = data.0 as *mut SmallVec<[HMONITOR; 4]>;    unsafe { (*monitors).push(hmonitor) };    BOOL(1)} fn get_monitor_info(hmonitor: HMONITOR) -> anyhow::Result<MONITORINFOEXW> {    let mut monitor_info: MONITORINFOEXW = unsafe { std::mem::zeroed() };    monitor_info.monitorInfo.cbSize = std::mem::size_of::<MONITORINFOEXW>() as u32;    let status = unsafe {        GetMonitorInfoW(            hmonitor,            &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO,        )    };    if status.as_bool() {        Ok(monitor_info)    } else {        Err(anyhow::anyhow!(std::io::Error::last_os_error()))    }} fn generate_uuid(device_name: &[u16]) -> Uuid {    let name = device_name        .iter()        .flat_map(|&a| a.to_be_bytes().to_vec())        .collect_vec();    Uuid::new_v5(&Uuid::NAMESPACE_DNS, &name)} fn get_scale_factor_for_monitor(monitor: HMONITOR) -> Result<f32> {    let mut dpi_x = 0;    let mut dpi_y = 0;    unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) }?;    assert_eq!(dpi_x, dpi_y);    Ok(dpi_x as f32 / USER_DEFAULT_SCREEN_DPI as f32)}