1use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
2use anyhow::Result;
3use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
4use core_graphics::{
5 display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
6 geometry::{CGPoint, CGRect, CGSize},
7};
8use std::any::Any;
9use uuid::Uuid;
10
11#[derive(Debug)]
12pub struct MacDisplay(pub(crate) CGDirectDisplayID);
13
14unsafe impl Send for MacDisplay {}
15
16impl MacDisplay {
17 /// Get the screen with the given [`DisplayId`].
18 pub fn find_by_id(id: DisplayId) -> Option<Self> {
19 Self::all().find(|screen| screen.id() == id)
20 }
21
22 /// Get the screen with the given persistent [`Uuid`].
23 pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
24 Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
25 }
26
27 /// Get the primary screen - the one with the menu bar, and whose bottom left
28 /// corner is at the origin of the AppKit coordinate system.
29 pub fn primary() -> Self {
30 Self::all().next().unwrap()
31 }
32
33 /// Obtains an iterator over all currently active system displays.
34 pub fn all() -> impl Iterator<Item = Self> {
35 unsafe {
36 let mut display_count: u32 = 0;
37 let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
38
39 if result == 0 {
40 let mut displays = Vec::with_capacity(display_count as usize);
41 CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
42 displays.set_len(display_count as usize);
43
44 displays.into_iter().map(MacDisplay)
45 } else {
46 panic!("Failed to get active display list");
47 }
48 }
49 }
50}
51
52#[link(name = "ApplicationServices", kind = "framework")]
53extern "C" {
54 fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
55}
56
57/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
58///
59/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
60/// with the Y axis pointing upwards.
61///
62/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
63/// screen, with the Y axis pointing downwards.
64pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
65 let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
66
67 Bounds {
68 origin: point(
69 GlobalPixels(rect.origin.x as f32),
70 GlobalPixels(
71 primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
72 ),
73 ),
74 size: size(
75 GlobalPixels(rect.size.width as f32),
76 GlobalPixels(rect.size.height as f32),
77 ),
78 }
79}
80
81/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
82///
83/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
84/// with the Y axis pointing upwards.
85///
86/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
87/// screen, with the Y axis pointing downwards.
88pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
89 let primary_screen_height = MacDisplay::primary().bounds().size.height;
90
91 CGRect::new(
92 &CGPoint::new(
93 bounds.origin.x.into(),
94 (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
95 ),
96 &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
97 )
98}
99
100impl PlatformDisplay for MacDisplay {
101 fn id(&self) -> DisplayId {
102 DisplayId(self.0)
103 }
104
105 fn uuid(&self) -> Result<Uuid> {
106 let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
107 anyhow::ensure!(
108 !cfuuid.is_null(),
109 "AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
110 );
111
112 let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
113 Ok(Uuid::from_bytes([
114 bytes.byte0,
115 bytes.byte1,
116 bytes.byte2,
117 bytes.byte3,
118 bytes.byte4,
119 bytes.byte5,
120 bytes.byte6,
121 bytes.byte7,
122 bytes.byte8,
123 bytes.byte9,
124 bytes.byte10,
125 bytes.byte11,
126 bytes.byte12,
127 bytes.byte13,
128 bytes.byte14,
129 bytes.byte15,
130 ]))
131 }
132
133 fn as_any(&self) -> &dyn Any {
134 self
135 }
136
137 fn bounds(&self) -> Bounds<GlobalPixels> {
138 unsafe {
139 let native_bounds = CGDisplayBounds(self.0);
140 display_bounds_from_native(native_bounds)
141 }
142 }
143}