screen.rs

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