use crate::main_thread_marker;
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, Point, TrayAnchor, point, px, size};
use anyhow::Result;
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
use objc2::runtime::AnyObject;
use objc2_app_kit::NSScreen;
use objc2_foundation::{NSNumber, NSPoint, NSRect, NSString};
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 screens = NSScreen::screens(main_thread_marker());
let screen = screens
.firstObject()
.expect("AppKit returned no screens for NSScreen::screens");
Self(screen_number(&screen))
}
pub fn all() -> impl Iterator<Item = Self> {
unsafe {
let mut displays = Vec::with_capacity(32);
let mut display_count = 0;
let result = CGGetActiveDisplayList(
displays.capacity() as u32,
displays.as_mut_ptr(),
&mut display_count,
);
if result == 0 {
displays.set_len(display_count as usize);
displays.into_iter().map(MacDisplay)
} else {
panic!("Failed to get active display list. Result: {result}");
}
}
}
}
unsafe fn as_screen<'a>(screen: *mut AnyObject) -> Option<&'a NSScreen> {
unsafe { screen.cast::<NSScreen>().as_ref() }
}
fn screen_number(screen: &NSScreen) -> CGDirectDisplayID {
let device_description = screen.deviceDescription();
let screen_number_key = NSString::from_str("NSScreenNumber");
let screen_number = device_description
.objectForKey(&screen_number_key)
.expect("NSScreen deviceDescription missing NSScreenNumber")
.downcast::<NSNumber>()
.expect("NSScreenNumber should be an NSNumber");
screen_number.as_u32()
}
pub(crate) fn display_id_for_ns_screen(screen: &NSScreen) -> DisplayId {
DisplayId::new(screen_number(screen) as u64)
}
#[cfg(test)]
pub(crate) unsafe fn display_id_for_screen(screen: *mut AnyObject) -> Option<DisplayId> {
unsafe {
let screen = as_screen(screen)?;
Some(display_id_for_ns_screen(screen))
}
}
pub(crate) unsafe fn primary_screen_frame() -> Option<NSRect> {
let screens = NSScreen::screens(main_thread_marker());
screens.firstObject().map(|screen| screen.frame())
}
pub(crate) unsafe fn global_point_to_native_screen_point(
position: Point<Pixels>,
) -> Option<NSPoint> {
unsafe {
let primary_frame = primary_screen_frame()?;
Some(NSPoint::new(
f64::from(position.x),
primary_frame.size.height - f64::from(position.y),
))
}
}
pub(crate) unsafe fn screen_frame_to_tray_anchor(
screen: *mut AnyObject,
frame: NSRect,
) -> Option<TrayAnchor> {
unsafe {
let screen = as_screen(screen)?;
let screen_frame = screen.frame();
let local_x = frame.origin.x - screen_frame.origin.x;
let local_y =
screen_frame.origin.y + screen_frame.size.height - frame.origin.y - frame.size.height;
Some(TrayAnchor {
display_id: DisplayId::new(screen_number(screen) as u64),
bounds: Bounds::new(
point(px(local_x as f32), px(local_y as f32)),
size(px(frame.size.width as f32), px(frame.size.height as f32)),
),
})
}
}
#[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) };
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> {
unsafe {
let Some(screen) = self.ns_screen() else {
return self.bounds();
};
let screen_frame = screen.frame();
let visible_frame = 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),
),
}
}
}
}
impl MacDisplay {
unsafe fn ns_screen(&self) -> Option<objc2::rc::Retained<NSScreen>> {
let screens = NSScreen::screens(main_thread_marker());
for i in 0..screens.len() {
let screen = screens.objectAtIndex(i);
if screen_number(&screen) == self.0 {
return Some(screen);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_id_for_screen_returns_none_for_null_screen() {
assert_eq!(unsafe { display_id_for_screen(std::ptr::null_mut()) }, None);
}
}