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}