display.rs

  1use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, px, size};
  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::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
 10use objc::{msg_send, sel, sel_impl};
 11use uuid::Uuid;
 12
 13#[derive(Debug)]
 14pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
 15
 16unsafe impl Send for MacDisplay {}
 17
 18impl MacDisplay {
 19    /// Get the screen with the given [`DisplayId`].
 20    pub fn find_by_id(id: DisplayId) -> Option<Self> {
 21        Self::all().find(|screen| screen.id() == id)
 22    }
 23
 24    /// Get the primary screen - the one with the menu bar, and whose bottom left
 25    /// corner is at the origin of the AppKit coordinate system.
 26    pub fn primary() -> Self {
 27        // Instead of iterating through all active systems displays via `all()` we use the first
 28        // NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList`
 29        // will always return a list of active displays (machine might be sleeping).
 30        //
 31        // The following is what Chromium does too:
 32        //
 33        // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56
 34        unsafe {
 35            let screens = NSScreen::screens(nil);
 36            let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0);
 37            let device_description = NSScreen::deviceDescription(screen);
 38            let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
 39            let screen_number = device_description.objectForKey_(screen_number_key);
 40            let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
 41            Self(screen_number)
 42        }
 43    }
 44
 45    /// Obtains an iterator over all currently active system displays.
 46    pub fn all() -> impl Iterator<Item = Self> {
 47        unsafe {
 48            // We're assuming there aren't more than 32 displays connected to the system.
 49            let mut displays = Vec::with_capacity(32);
 50            let mut display_count = 0;
 51            let result = CGGetActiveDisplayList(
 52                displays.capacity() as u32,
 53                displays.as_mut_ptr(),
 54                &mut display_count,
 55            );
 56
 57            if result == 0 {
 58                displays.set_len(display_count as usize);
 59                displays.into_iter().map(MacDisplay)
 60            } else {
 61                panic!("Failed to get active display list. Result: {result}");
 62            }
 63        }
 64    }
 65}
 66
 67#[link(name = "ApplicationServices", kind = "framework")]
 68unsafe extern "C" {
 69    fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
 70}
 71
 72impl PlatformDisplay for MacDisplay {
 73    fn id(&self) -> DisplayId {
 74        DisplayId(self.0)
 75    }
 76
 77    fn uuid(&self) -> Result<Uuid> {
 78        let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
 79        anyhow::ensure!(
 80            !cfuuid.is_null(),
 81            "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
 82        );
 83
 84        let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
 85        Ok(Uuid::from_bytes([
 86            bytes.byte0,
 87            bytes.byte1,
 88            bytes.byte2,
 89            bytes.byte3,
 90            bytes.byte4,
 91            bytes.byte5,
 92            bytes.byte6,
 93            bytes.byte7,
 94            bytes.byte8,
 95            bytes.byte9,
 96            bytes.byte10,
 97            bytes.byte11,
 98            bytes.byte12,
 99            bytes.byte13,
100            bytes.byte14,
101            bytes.byte15,
102        ]))
103    }
104
105    fn bounds(&self) -> Bounds<Pixels> {
106        unsafe {
107            // CGDisplayBounds is in "global display" coordinates, where 0 is
108            // the top left of the primary display.
109            let bounds = CGDisplayBounds(self.0);
110
111            Bounds {
112                origin: Default::default(),
113                size: size(px(bounds.size.width as f32), px(bounds.size.height as f32)),
114            }
115        }
116    }
117}