screen.rs

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