1use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
2use anyhow::Result;
3use cocoa::{
4 appkit::NSScreen,
5 base::{id, nil},
6 foundation::{NSDictionary, NSString},
7};
8use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
9use core_graphics::{
10 display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
11 geometry::{CGPoint, CGRect, CGSize},
12};
13use objc::{msg_send, sel, sel_impl};
14use uuid::Uuid;
15
16#[derive(Debug)]
17pub struct MacDisplay(pub(crate) CGDirectDisplayID);
18
19unsafe impl Send for MacDisplay {}
20
21impl MacDisplay {
22 /// Get the screen with the given [`DisplayId`].
23 pub fn find_by_id(id: DisplayId) -> Option<Self> {
24 Self::all().find(|screen| screen.id() == id)
25 }
26
27 /// Get the screen with the given persistent [`Uuid`].
28 pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
29 Self::all().find(|screen| screen.uuid().ok() == 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 pub fn primary() -> Self {
35 // Instead of iterating through all active systems displays via `all()` we use the first
36 // NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList`
37 // will always return a list of active displays (machine might be sleeping).
38 //
39 // The following is what Chromium does too:
40 //
41 // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56
42 unsafe {
43 let screens = NSScreen::screens(nil);
44 let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0);
45 let device_description = NSScreen::deviceDescription(screen);
46 let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
47 let screen_number = device_description.objectForKey_(screen_number_key);
48 let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
49 Self(screen_number)
50 }
51 }
52
53 /// Obtains an iterator over all currently active system displays.
54 pub fn all() -> impl Iterator<Item = Self> {
55 unsafe {
56 // We're assuming there aren't more than 32 displays connected to the system.
57 let mut displays = Vec::with_capacity(32);
58 let mut display_count = 0;
59 let result = CGGetActiveDisplayList(
60 displays.capacity() as u32,
61 displays.as_mut_ptr(),
62 &mut display_count,
63 );
64
65 if result == 0 {
66 displays.set_len(display_count as usize);
67 displays.into_iter().map(MacDisplay)
68 } else {
69 panic!("Failed to get active display list. Result: {result}");
70 }
71 }
72 }
73}
74
75#[link(name = "ApplicationServices", kind = "framework")]
76extern "C" {
77 fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
78}
79
80/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
81///
82/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
83/// with the Y axis pointing upwards.
84///
85/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
86/// screen, with the Y axis pointing downwards.
87pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
88 let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
89
90 Bounds {
91 origin: point(
92 GlobalPixels(rect.origin.x as f32),
93 GlobalPixels(
94 primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
95 ),
96 ),
97 size: size(
98 GlobalPixels(rect.size.width as f32),
99 GlobalPixels(rect.size.height as f32),
100 ),
101 }
102}
103
104/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
105///
106/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
107/// with the Y axis pointing upwards.
108///
109/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
110/// screen, with the Y axis pointing downwards.
111pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
112 let primary_screen_height = MacDisplay::primary().bounds().size.height;
113
114 CGRect::new(
115 &CGPoint::new(
116 bounds.origin.x.into(),
117 (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
118 ),
119 &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
120 )
121}
122
123impl PlatformDisplay for MacDisplay {
124 fn id(&self) -> DisplayId {
125 DisplayId(self.0)
126 }
127
128 fn uuid(&self) -> Result<Uuid> {
129 let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
130 anyhow::ensure!(
131 !cfuuid.is_null(),
132 "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
133 );
134
135 let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
136 Ok(Uuid::from_bytes([
137 bytes.byte0,
138 bytes.byte1,
139 bytes.byte2,
140 bytes.byte3,
141 bytes.byte4,
142 bytes.byte5,
143 bytes.byte6,
144 bytes.byte7,
145 bytes.byte8,
146 bytes.byte9,
147 bytes.byte10,
148 bytes.byte11,
149 bytes.byte12,
150 bytes.byte13,
151 bytes.byte14,
152 bytes.byte15,
153 ]))
154 }
155
156 fn bounds(&self) -> Bounds<GlobalPixels> {
157 unsafe {
158 let native_bounds = CGDisplayBounds(self.0);
159 display_bounds_from_native(native_bounds)
160 }
161 }
162}