display.rs

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