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