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