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