1use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, px, size};
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::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}