display.rs

  1use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
  2use anyhow::Result;
  3use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
  4use core_graphics::{
  5    display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
  6    geometry::{CGPoint, CGRect, CGSize},
  7};
  8use std::any::Any;
  9use uuid::Uuid;
 10
 11#[derive(Debug)]
 12pub struct MacDisplay(pub(crate) CGDirectDisplayID);
 13
 14unsafe impl Send for MacDisplay {}
 15
 16impl MacDisplay {
 17    /// Get the screen with the given [DisplayId].
 18    pub fn find_by_id(id: DisplayId) -> Option<Self> {
 19        Self::all().find(|screen| screen.id() == id)
 20    }
 21
 22    /// Get the screen with the given persistent [Uuid].
 23    pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
 24        Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
 25    }
 26
 27    /// Get the primary screen - the one with the menu bar, and whose bottom left
 28    /// corner is at the origin of the AppKit coordinate system.
 29    pub fn primary() -> Self {
 30        Self::all().next().unwrap()
 31    }
 32
 33    /// Obtains an iterator over all currently active system displays.
 34    pub fn all() -> impl Iterator<Item = Self> {
 35        unsafe {
 36            let mut display_count: u32 = 0;
 37            let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
 38
 39            if result == 0 {
 40                let mut displays = Vec::with_capacity(display_count as usize);
 41                CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
 42                displays.set_len(display_count as usize);
 43
 44                displays.into_iter().map(|display| MacDisplay(display))
 45            } else {
 46                panic!("Failed to get active display list");
 47            }
 48        }
 49    }
 50}
 51
 52#[link(name = "ApplicationServices", kind = "framework")]
 53extern "C" {
 54    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
 55}
 56
 57/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
 58///
 59/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
 60/// with the Y axis pointing upwards.
 61///
 62/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
 63/// screen, with the Y axis pointing downwards.
 64pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
 65    let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
 66
 67    Bounds {
 68        origin: point(
 69            GlobalPixels(rect.origin.x as f32),
 70            GlobalPixels(
 71                primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
 72            ),
 73        ),
 74        size: size(
 75            GlobalPixels(rect.size.width as f32),
 76            GlobalPixels(rect.size.height as f32),
 77        ),
 78    }
 79}
 80
 81/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native 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_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
 89    let primary_screen_height = MacDisplay::primary().bounds().size.height;
 90
 91    CGRect::new(
 92        &CGPoint::new(
 93            bounds.origin.x.into(),
 94            (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
 95        ),
 96        &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
 97    )
 98}
 99
100impl PlatformDisplay for MacDisplay {
101    fn id(&self) -> DisplayId {
102        DisplayId(self.0)
103    }
104
105    fn uuid(&self) -> Result<Uuid> {
106        let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
107        anyhow::ensure!(
108            !cfuuid.is_null(),
109            "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
110        );
111
112        let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
113        Ok(Uuid::from_bytes([
114            bytes.byte0,
115            bytes.byte1,
116            bytes.byte2,
117            bytes.byte3,
118            bytes.byte4,
119            bytes.byte5,
120            bytes.byte6,
121            bytes.byte7,
122            bytes.byte8,
123            bytes.byte9,
124            bytes.byte10,
125            bytes.byte11,
126            bytes.byte12,
127            bytes.byte13,
128            bytes.byte14,
129            bytes.byte15,
130        ]))
131    }
132
133    fn as_any(&self) -> &dyn Any {
134        self
135    }
136
137    fn bounds(&self) -> Bounds<GlobalPixels> {
138        unsafe {
139            let native_bounds = CGDisplayBounds(self.0);
140            display_bounds_from_native(native_bounds)
141        }
142    }
143}