1use super::ns_string;
2use crate::{platform, point, px, size, Bounds, Pixels, PlatformScreen};
3use cocoa::{
4 appkit::NSScreen,
5 base::{id, nil},
6 foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
7};
8use core_foundation::{
9 number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
10 uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
11};
12use core_graphics::display::CGDirectDisplayID;
13use std::{any::Any, ffi::c_void};
14use uuid::Uuid;
15
16#[link(name = "ApplicationServices", kind = "framework")]
17extern "C" {
18 pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
19}
20
21#[derive(Debug)]
22pub struct MacScreen {
23 pub(crate) native_screen: id,
24}
25
26impl MacScreen {
27 /// Get the screen with the given UUID.
28 pub fn find_by_id(uuid: Uuid) -> Option<Self> {
29 Self::all().find(|screen| platform::MacScreen::display_uuid(screen) == Some(uuid))
30 }
31
32 /// Get the primary screen - the one with the menu bar, and whose bottom left
33 /// corner is at the origin of the AppKit coordinate system.
34 fn primary() -> Self {
35 Self::all().next().unwrap()
36 }
37
38 pub fn all() -> impl Iterator<Item = Self> {
39 unsafe {
40 let native_screens = NSScreen::screens(nil);
41 (0..NSArray::count(native_screens)).map(move |ix| MacScreen {
42 native_screen: native_screens.objectAtIndex(ix),
43 })
44 }
45 }
46
47 /// Convert the given rectangle in screen coordinates from GPUI's
48 /// coordinate system to the AppKit coordinate system.
49 ///
50 /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
51 /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
52 /// bottom left of the primary screen, with the Y axis pointing upward.
53 pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
54 let primary_screen_height =
55 px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
56
57 NSRect::new(
58 NSPoint::new(
59 bounds.origin.x.into(),
60 (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
61 ),
62 NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
63 )
64 }
65
66 /// Convert the given rectangle in screen coordinates from the AppKit
67 /// coordinate system to GPUI's coordinate system.
68 ///
69 /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
70 /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
71 /// bottom left of the primary screen, with the Y axis pointing upward.
72 pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
73 let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
74 Bounds {
75 origin: point(
76 px(rect.origin.x as f32),
77 px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
78 ),
79 size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
80 }
81 }
82}
83
84impl PlatformScreen for MacScreen {
85 fn as_any(&self) -> &dyn Any {
86 self
87 }
88
89 fn display_uuid(&self) -> Option<uuid::Uuid> {
90 unsafe {
91 // Screen ids are not stable. Further, the default device id is also unstable across restarts.
92 // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
93 // This approach is similar to that which winit takes
94 // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
95 let device_description = self.native_screen.deviceDescription();
96
97 let key = ns_string("NSScreenNumber");
98 let device_id_obj = device_description.objectForKey_(key);
99 if device_id_obj.is_null() {
100 // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
101 // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
102 return None;
103 }
104
105 let mut device_id: u32 = 0;
106 CFNumberGetValue(
107 device_id_obj as CFNumberRef,
108 kCFNumberIntType,
109 (&mut device_id) as *mut _ as *mut c_void,
110 );
111 let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
112 if cfuuid.is_null() {
113 return None;
114 }
115
116 let bytes = CFUUIDGetUUIDBytes(cfuuid);
117 Some(Uuid::from_bytes([
118 bytes.byte0,
119 bytes.byte1,
120 bytes.byte2,
121 bytes.byte3,
122 bytes.byte4,
123 bytes.byte5,
124 bytes.byte6,
125 bytes.byte7,
126 bytes.byte8,
127 bytes.byte9,
128 bytes.byte10,
129 bytes.byte11,
130 bytes.byte12,
131 bytes.byte13,
132 bytes.byte14,
133 bytes.byte15,
134 ]))
135 }
136 }
137
138 fn bounds(&self) -> Bounds<Pixels> {
139 unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
140 }
141
142 fn content_bounds(&self) -> Bounds<Pixels> {
143 unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
144 }
145}