screen.rs

  1use super::ns_string;
  2use crate::{platform, point, px, size, Bounds, Pixels, PlatformScreen};
  3use cocoa::{
  4    appkit::NSScreen,
  5    base::{id, nil},
  6    foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
  7};
  8use core_foundation::{
  9    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
 10    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
 11};
 12use core_graphics::display::CGDirectDisplayID;
 13use std::{any::Any, ffi::c_void};
 14use uuid::Uuid;
 15
 16#[link(name = "ApplicationServices", kind = "framework")]
 17extern "C" {
 18    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
 19}
 20
 21#[derive(Debug)]
 22pub struct MacScreen {
 23    pub(crate) native_screen: id,
 24}
 25
 26impl MacScreen {
 27    /// Get the screen with the given UUID.
 28    pub fn find_by_id(uuid: Uuid) -> Option<Self> {
 29        Self::all().find(|screen| platform::MacScreen::display_uuid(screen) == Some(uuid))
 30    }
 31
 32    /// Get the primary screen - the one with the menu bar, and whose bottom left
 33    /// corner is at the origin of the AppKit coordinate system.
 34    fn primary() -> Self {
 35        Self::all().next().unwrap()
 36    }
 37
 38    pub fn all() -> impl Iterator<Item = Self> {
 39        unsafe {
 40            let native_screens = NSScreen::screens(nil);
 41            (0..NSArray::count(native_screens)).map(move |ix| MacScreen {
 42                native_screen: native_screens.objectAtIndex(ix),
 43            })
 44        }
 45    }
 46
 47    /// Convert the given rectangle in screen coordinates from GPUI's
 48    /// coordinate system to the AppKit coordinate system.
 49    ///
 50    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
 51    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
 52    /// bottom left of the primary screen, with the Y axis pointing upward.
 53    pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
 54        let primary_screen_height =
 55            px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
 56
 57        NSRect::new(
 58            NSPoint::new(
 59                bounds.origin.x.into(),
 60                (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
 61            ),
 62            NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
 63        )
 64    }
 65
 66    /// Convert the given rectangle in screen coordinates from the AppKit
 67    /// coordinate system to GPUI's coordinate system.
 68    ///
 69    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
 70    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
 71    /// bottom left of the primary screen, with the Y axis pointing upward.
 72    pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
 73        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
 74        Bounds {
 75            origin: point(
 76                px(rect.origin.x as f32),
 77                px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
 78            ),
 79            size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
 80        }
 81    }
 82}
 83
 84impl PlatformScreen for MacScreen {
 85    fn as_any(&self) -> &dyn Any {
 86        self
 87    }
 88
 89    fn display_uuid(&self) -> Option<uuid::Uuid> {
 90        unsafe {
 91            // Screen ids are not stable. Further, the default device id is also unstable across restarts.
 92            // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
 93            // This approach is similar to that which winit takes
 94            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
 95            let device_description = self.native_screen.deviceDescription();
 96
 97            let key = ns_string("NSScreenNumber");
 98            let device_id_obj = device_description.objectForKey_(key);
 99            if device_id_obj.is_null() {
100                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
101                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
102                return None;
103            }
104
105            let mut device_id: u32 = 0;
106            CFNumberGetValue(
107                device_id_obj as CFNumberRef,
108                kCFNumberIntType,
109                (&mut device_id) as *mut _ as *mut c_void,
110            );
111            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
112            if cfuuid.is_null() {
113                return None;
114            }
115
116            let bytes = CFUUIDGetUUIDBytes(cfuuid);
117            Some(Uuid::from_bytes([
118                bytes.byte0,
119                bytes.byte1,
120                bytes.byte2,
121                bytes.byte3,
122                bytes.byte4,
123                bytes.byte5,
124                bytes.byte6,
125                bytes.byte7,
126                bytes.byte8,
127                bytes.byte9,
128                bytes.byte10,
129                bytes.byte11,
130                bytes.byte12,
131                bytes.byte13,
132                bytes.byte14,
133                bytes.byte15,
134            ]))
135        }
136    }
137
138    fn bounds(&self) -> Bounds<Pixels> {
139        unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
140    }
141
142    fn content_bounds(&self) -> Bounds<Pixels> {
143        unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
144    }
145}