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