screen.rs

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