use crate::main_thread_marker;
use anyhow::Result;
use core_foundation::base::CFRelease;
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
use core_graphics::display::{
CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGGetActiveDisplayList,
};
use gpui::{Bounds, DisplayId, Pixels, PlatformDisplay, point, px, refresh_interval_from_hz, size};
use objc2::{msg_send, rc::Retained, sel};
use objc2_app_kit::NSScreen;
use objc2_foundation::{NSObjectProtocol, ns_string};
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug)]
pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
unsafe impl Send for MacDisplay {}
impl MacDisplay {
pub fn find_by_id(id: DisplayId) -> Option<Self> {
Self::all().find(|screen| screen.id() == id)
}
pub fn primary() -> Self {
let main_thread = main_thread_marker().expect("displays require the main thread");
let screens = NSScreen::screens(main_thread);
let screen = screens.firstObject().expect("no primary screen");
let screen_number = screen
.deviceDescription()
.objectForKey(ns_string!("NSScreenNumber"))
.map(|number| unsafe { msg_send![&*number, unsignedIntValue] })
.unwrap_or_default();
Self(screen_number)
}
pub fn all() -> impl Iterator<Item = Self> {
let mut displays = Vec::with_capacity(32);
let mut display_count = 0;
let result = unsafe {
CGGetActiveDisplayList(
displays.capacity() as u32,
displays.as_mut_ptr(),
&mut display_count,
)
};
if result == 0 {
unsafe { displays.set_len(display_count as usize) };
} else {
log::error!("Failed to get active display list. Result: {result}");
}
displays.into_iter().map(MacDisplay)
}
}
#[link(name = "ApplicationServices", kind = "framework")]
unsafe extern "C" {
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
impl PlatformDisplay for MacDisplay {
fn id(&self) -> DisplayId {
DisplayId::new(self.0 as u64)
}
fn uuid(&self) -> Result<Uuid> {
let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
anyhow::ensure!(
!cfuuid.is_null(),
"AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
);
let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
unsafe { CFRelease(cfuuid as _) };
Ok(Uuid::from_bytes([
bytes.byte0,
bytes.byte1,
bytes.byte2,
bytes.byte3,
bytes.byte4,
bytes.byte5,
bytes.byte6,
bytes.byte7,
bytes.byte8,
bytes.byte9,
bytes.byte10,
bytes.byte11,
bytes.byte12,
bytes.byte13,
bytes.byte14,
bytes.byte15,
]))
}
fn bounds(&self) -> Bounds<Pixels> {
unsafe {
let bounds = CGDisplayBounds(self.0);
Bounds {
origin: Default::default(),
size: size(px(bounds.size.width as f32), px(bounds.size.height as f32)),
}
}
}
fn visible_bounds(&self) -> Bounds<Pixels> {
let Some(dominated_screen) = self.get_nsscreen() else {
return self.bounds();
};
let screen_frame = dominated_screen.frame();
let visible_frame = dominated_screen.visibleFrame();
let origin_y =
screen_frame.size.height - visible_frame.origin.y - visible_frame.size.height
+ screen_frame.origin.y;
Bounds {
origin: point(
px(visible_frame.origin.x as f32 - screen_frame.origin.x as f32),
px(origin_y as f32),
),
size: size(
px(visible_frame.size.width as f32),
px(visible_frame.size.height as f32),
),
}
}
fn refresh_interval(&self) -> Option<Duration> {
let screen_hertz = self
.get_nsscreen()
.filter(|screen| screen.respondsToSelector(sel!(maximumFramesPerSecond)))
.map_or(0.0, |screen| screen.maximumFramesPerSecond() as f64);
refresh_interval_from_hz(screen_hertz).or_else(|| {
CGDisplay::new(self.0)
.display_mode()
.and_then(|mode| refresh_interval_from_hz(mode.refresh_rate()))
})
}
}
impl MacDisplay {
fn get_nsscreen(&self) -> Option<Retained<NSScreen>> {
let main_thread = main_thread_marker().expect("displays require the main thread");
NSScreen::screens(main_thread).into_iter().find(|screen| {
screen
.deviceDescription()
.objectForKey(ns_string!("NSScreenNumber"))
.is_some_and(|number| {
let screen_id: CGDirectDisplayID =
unsafe { msg_send![&*number, unsignedIntValue] };
screen_id == self.0
})
})
}
}