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