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