Windows: impl `WindowsDisplay` (#9287)

张小白 created

Tested on my laptop, and I've noticed that when I move the window,
`WindowsPlatform::displays()` is being continuously called. Is this
intended?

Release Notes:

- N/A

Change summary

Cargo.lock                                   |   7 
crates/gpui/Cargo.toml                       |   2 
crates/gpui/src/platform/windows/display.rs  | 175 ++++++++++++++++++---
crates/gpui/src/platform/windows/platform.rs |  17 +
crates/gpui/src/platform/windows/window.rs   |   7 
5 files changed, 172 insertions(+), 36 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -8729,6 +8729,12 @@ dependencies = [
  "digest 0.10.7",
 ]
 
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
 [[package]]
 name = "sha2"
 version = "0.9.9"
@@ -11034,6 +11040,7 @@ checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
 dependencies = [
  "getrandom 0.2.10",
  "serde",
+ "sha1_smol",
 ]
 
 [[package]]

crates/gpui/Cargo.toml 🔗

@@ -70,7 +70,7 @@ time.workspace = true
 tiny-skia = "0.5"
 usvg = { version = "0.14", features = [] }
 util.workspace = true
-uuid = { version = "1.1.2", features = ["v4"] }
+uuid = { version = "1.1.2", features = ["v4", "v5"] }
 waker-fn = "1.1.0"
 
 [dev-dependencies]

crates/gpui/src/platform/windows/display.rs 🔗

@@ -1,48 +1,169 @@
-use anyhow::{anyhow, Result};
+use std::rc::Rc;
+
+use itertools::Itertools;
+use smallvec::SmallVec;
 use uuid::Uuid;
-use windows::{
-    core::PCSTR,
-    Win32::Graphics::Gdi::{EnumDisplaySettingsA, DEVMODEA, ENUM_CURRENT_SETTINGS},
+use windows::Win32::{
+    Foundation::{BOOL, LPARAM, POINT, RECT},
+    Graphics::Gdi::{
+        EnumDisplayMonitors, GetMonitorInfoW, MonitorFromPoint, HDC, HMONITOR, MONITORINFO,
+        MONITORINFOEXW, MONITOR_DEFAULTTOPRIMARY,
+    },
 };
 
 use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
 
 #[derive(Debug)]
-pub(crate) struct WindowsDisplay;
+pub(crate) struct WindowsDisplay {
+    pub display_id: DisplayId,
+    bounds: Bounds<GlobalPixels>,
+    uuid: Uuid,
+}
 
 impl WindowsDisplay {
-    pub(crate) fn new() -> Self {
-        Self
+    pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
+        let Some(screen) = available_monitors().into_iter().nth(display_id.0 as _) else {
+            return None;
+        };
+        let Ok(info) = get_monitor_info(screen).inspect_err(|e| log::error!("{}", e)) else {
+            return None;
+        };
+        let size = info.monitorInfo.rcMonitor;
+        let uuid = generate_uuid(&info.szDevice);
+
+        Some(WindowsDisplay {
+            display_id,
+            bounds: Bounds {
+                origin: Point {
+                    x: GlobalPixels(size.left as f32),
+                    y: GlobalPixels(size.top as f32),
+                },
+                size: Size {
+                    width: GlobalPixels((size.right - size.left) as f32),
+                    height: GlobalPixels((size.bottom - size.top) as f32),
+                },
+            },
+            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 size = info.monitorInfo.rcMonitor;
+        let uuid = generate_uuid(&info.szDevice);
+
+        WindowsDisplay {
+            display_id,
+            bounds: Bounds {
+                origin: Point {
+                    x: GlobalPixels(size.left as f32),
+                    y: GlobalPixels(size.top as f32),
+                },
+                size: Size {
+                    width: GlobalPixels((size.right - size.left) as f32),
+                    height: GlobalPixels((size.bottom - size.top) as f32),
+                },
+            },
+            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;
+        }
+        let Some(display_id) = available_monitors()
+            .iter()
+            .position(|handle| handle.0 == monitor.0)
+        else {
+            return None;
+        };
+
+        Some(WindowsDisplay::new_with_handle_and_id(
+            monitor,
+            DisplayId(display_id as _),
+        ))
+    }
+
+    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()
     }
 }
 
 impl PlatformDisplay for WindowsDisplay {
-    // todo(windows)
     fn id(&self) -> DisplayId {
-        DisplayId(1)
+        self.display_id
     }
 
-    // todo(windows)
-    fn uuid(&self) -> Result<Uuid> {
-        Err(anyhow!("not implemented yet."))
+    fn uuid(&self) -> anyhow::Result<Uuid> {
+        Ok(self.uuid)
     }
 
     fn bounds(&self) -> Bounds<GlobalPixels> {
-        let mut dev = DEVMODEA {
-            dmSize: std::mem::size_of::<DEVMODEA>() as _,
-            ..unsafe { std::mem::zeroed() }
-        };
-        unsafe { EnumDisplaySettingsA(PCSTR::null(), ENUM_CURRENT_SETTINGS, &mut dev) };
-        let w = dev.dmPelsWidth;
-        let h = dev.dmPelsHeight;
-
-        log::debug!("Screen size: {w} {h}");
-        Bounds::new(
-            Point::new(0.0.into(), 0.0.into()),
-            Size {
-                width: GlobalPixels(w as f32),
-                height: GlobalPixels(h as f32),
-            },
+        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 _),
+        );
+    }
+    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)
+}

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -284,19 +284,24 @@ impl Platform for WindowsPlatform {
         unimplemented!()
     }
 
-    // todo(windows)
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
-        vec![Rc::new(WindowsDisplay::new())]
+        WindowsDisplay::displays()
     }
 
-    // todo(windows)
     fn display(&self, id: crate::DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        Some(Rc::new(WindowsDisplay::new()))
+        if let Some(display) = WindowsDisplay::new(id) {
+            Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
+        } else {
+            None
+        }
     }
 
-    // todo(windows)
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
-        Some(Rc::new(WindowsDisplay::new()))
+        if let Some(display) = WindowsDisplay::primary_monitor() {
+            Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
+        } else {
+            None
+        }
     }
 
     // todo(windows)

crates/gpui/src/platform/windows/window.rs 🔗

@@ -637,6 +637,7 @@ struct Callbacks {
 pub(crate) struct WindowsWindow {
     inner: Rc<WindowsWindowInner>,
     drag_drop_handler: IDropTarget,
+    display: Rc<WindowsDisplay>,
 }
 
 struct WindowCreateContext {
@@ -701,9 +702,12 @@ impl WindowsWindow {
             };
             drag_drop_handler
         };
+        // todo(windows) move window to target monitor
+        // options.display_id
         let wnd = Self {
             inner: context.inner.unwrap(),
             drag_drop_handler,
+            display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
         };
         platform_inner
             .raw_window_handles
@@ -780,9 +784,8 @@ impl PlatformWindow for WindowsWindow {
         WindowAppearance::Dark
     }
 
-    // todo(windows)
     fn display(&self) -> Rc<dyn PlatformDisplay> {
-        Rc::new(WindowsDisplay::new())
+        self.display.clone()
     }
 
     fn mouse_position(&self) -> Point<Pixels> {