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