screen.rs

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