screen.rs

  1use std::{any::Any, ffi::c_void};
  2
  3use crate::platform;
  4use cocoa::{
  5    appkit::NSScreen,
  6    base::{id, nil},
  7    foundation::{NSArray, NSDictionary},
  8};
  9use core_foundation::{
 10    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
 11    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
 12};
 13use core_graphics::display::CGDirectDisplayID;
 14use pathfinder_geometry::rect::RectF;
 15use uuid::Uuid;
 16
 17use super::{geometry::NSRectExt, ns_string};
 18
 19#[link(name = "ApplicationServices", kind = "framework")]
 20extern "C" {
 21    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
 22}
 23
 24#[derive(Debug)]
 25pub struct Screen {
 26    pub(crate) native_screen: id,
 27}
 28
 29impl Screen {
 30    pub fn find_by_id(uuid: Uuid) -> Option<Self> {
 31        unsafe {
 32            let native_screens = NSScreen::screens(nil);
 33            (0..NSArray::count(native_screens))
 34                .into_iter()
 35                .map(|ix| Screen {
 36                    native_screen: native_screens.objectAtIndex(ix),
 37                })
 38                .find(|screen| platform::Screen::display_uuid(screen) == Some(uuid))
 39        }
 40    }
 41
 42    pub fn all() -> Vec<Self> {
 43        let mut screens = Vec::new();
 44        unsafe {
 45            let native_screens = NSScreen::screens(nil);
 46            for ix in 0..NSArray::count(native_screens) {
 47                screens.push(Screen {
 48                    native_screen: native_screens.objectAtIndex(ix),
 49                });
 50            }
 51        }
 52        screens
 53    }
 54}
 55
 56impl platform::Screen for Screen {
 57    fn as_any(&self) -> &dyn Any {
 58        self
 59    }
 60
 61    fn display_uuid(&self) -> Option<uuid::Uuid> {
 62        unsafe {
 63            // Screen ids are not stable. Further, the default device id is also unstable across restarts.
 64            // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
 65            // This approach is similar to that which winit takes
 66            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
 67            let device_description = self.native_screen.deviceDescription();
 68
 69            let key = ns_string("NSScreenNumber");
 70            let device_id_obj = device_description.objectForKey_(key);
 71            if device_id_obj.is_null() {
 72                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
 73                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
 74                return None;
 75            }
 76
 77            let mut device_id: u32 = 0;
 78            CFNumberGetValue(
 79                device_id_obj as CFNumberRef,
 80                kCFNumberIntType,
 81                (&mut device_id) as *mut _ as *mut c_void,
 82            );
 83            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
 84            if cfuuid.is_null() {
 85                return None;
 86            }
 87
 88            let bytes = CFUUIDGetUUIDBytes(cfuuid);
 89            Some(Uuid::from_bytes([
 90                bytes.byte0,
 91                bytes.byte1,
 92                bytes.byte2,
 93                bytes.byte3,
 94                bytes.byte4,
 95                bytes.byte5,
 96                bytes.byte6,
 97                bytes.byte7,
 98                bytes.byte8,
 99                bytes.byte9,
100                bytes.byte10,
101                bytes.byte11,
102                bytes.byte12,
103                bytes.byte13,
104                bytes.byte14,
105                bytes.byte15,
106            ]))
107        }
108    }
109
110    fn bounds(&self) -> RectF {
111        unsafe {
112            let frame = self.native_screen.frame();
113            frame.to_rectf()
114        }
115    }
116}