display.rs

  1use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
  2use anyhow::Result;
  3use cocoa::{
  4    appkit::NSScreen,
  5    base::{id, nil},
  6    foundation::{NSDictionary, NSString},
  7};
  8use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
  9use core_graphics::{
 10    display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
 11    geometry::{CGPoint, CGRect, CGSize},
 12};
 13use objc::{msg_send, sel, sel_impl};
 14use std::any::Any;
 15use uuid::Uuid;
 16
 17#[derive(Debug)]
 18pub struct MacDisplay(pub(crate) CGDirectDisplayID);
 19
 20unsafe impl Send for MacDisplay {}
 21
 22impl MacDisplay {
 23    /// Get the screen with the given [`DisplayId`].
 24    pub fn find_by_id(id: DisplayId) -> Option<Self> {
 25        Self::all().find(|screen| screen.id() == id)
 26    }
 27
 28    /// Get the screen with the given persistent [`Uuid`].
 29    pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
 30        Self::all().find(|screen| screen.uuid().ok() == 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    pub fn primary() -> Self {
 36        // Instead of iterating through all active systems displays via `all()` we use the first
 37        // NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList`
 38        // will always return a list of active displays (machine might be sleeping).
 39        //
 40        // The following is what Chromium does too:
 41        //
 42        // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56
 43        unsafe {
 44            let screens = NSScreen::screens(nil);
 45            let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0);
 46            let device_description = NSScreen::deviceDescription(screen);
 47            let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
 48            let screen_number = device_description.objectForKey_(screen_number_key);
 49            let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
 50            Self(screen_number)
 51        }
 52    }
 53
 54    /// Obtains an iterator over all currently active system displays.
 55    pub fn all() -> impl Iterator<Item = Self> {
 56        unsafe {
 57            // We're assuming there aren't more than 32 displays connected to the system.
 58            let mut displays = Vec::with_capacity(32);
 59            let mut display_count = 0;
 60            let result = CGGetActiveDisplayList(
 61                displays.capacity() as u32,
 62                displays.as_mut_ptr(),
 63                &mut display_count,
 64            );
 65
 66            if result == 0 {
 67                displays.set_len(display_count as usize);
 68                displays.into_iter().map(MacDisplay)
 69            } else {
 70                panic!("Failed to get active display list. Result: {result}");
 71            }
 72        }
 73    }
 74}
 75
 76#[link(name = "ApplicationServices", kind = "framework")]
 77extern "C" {
 78    fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
 79}
 80
 81/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
 82///
 83/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
 84/// with the Y axis pointing upwards.
 85///
 86/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
 87/// screen, with the Y axis pointing downwards.
 88pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
 89    let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
 90
 91    Bounds {
 92        origin: point(
 93            GlobalPixels(rect.origin.x as f32),
 94            GlobalPixels(
 95                primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
 96            ),
 97        ),
 98        size: size(
 99            GlobalPixels(rect.size.width as f32),
100            GlobalPixels(rect.size.height as f32),
101        ),
102    }
103}
104
105/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
106///
107/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
108/// with the Y axis pointing upwards.
109///
110/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
111/// screen, with the Y axis pointing downwards.
112pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
113    let primary_screen_height = MacDisplay::primary().bounds().size.height;
114
115    CGRect::new(
116        &CGPoint::new(
117            bounds.origin.x.into(),
118            (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
119        ),
120        &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
121    )
122}
123
124impl PlatformDisplay for MacDisplay {
125    fn id(&self) -> DisplayId {
126        DisplayId(self.0)
127    }
128
129    fn uuid(&self) -> Result<Uuid> {
130        let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
131        anyhow::ensure!(
132            !cfuuid.is_null(),
133            "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
134        );
135
136        let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
137        Ok(Uuid::from_bytes([
138            bytes.byte0,
139            bytes.byte1,
140            bytes.byte2,
141            bytes.byte3,
142            bytes.byte4,
143            bytes.byte5,
144            bytes.byte6,
145            bytes.byte7,
146            bytes.byte8,
147            bytes.byte9,
148            bytes.byte10,
149            bytes.byte11,
150            bytes.byte12,
151            bytes.byte13,
152            bytes.byte14,
153            bytes.byte15,
154        ]))
155    }
156
157    fn as_any(&self) -> &dyn Any {
158        self
159    }
160
161    fn bounds(&self) -> Bounds<GlobalPixels> {
162        unsafe {
163            let native_bounds = CGDisplayBounds(self.0);
164            display_bounds_from_native(native_bounds)
165        }
166    }
167}