117 lines
4 KiB
Rust
117 lines
4 KiB
Rust
use crate::{Bounds, DisplayId, Pixels, PlatformDisplay, px, size};
|
|
use anyhow::Result;
|
|
use cocoa::{
|
|
appkit::NSScreen,
|
|
base::{id, nil},
|
|
foundation::{NSDictionary, NSString},
|
|
};
|
|
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
|
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
|
use objc::{msg_send, sel, sel_impl};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
|
|
|
|
unsafe impl Send for MacDisplay {}
|
|
|
|
impl MacDisplay {
|
|
/// Get the screen with the given [`DisplayId`].
|
|
pub fn find_by_id(id: DisplayId) -> Option<Self> {
|
|
Self::all().find(|screen| screen.id() == id)
|
|
}
|
|
|
|
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
|
/// corner is at the origin of the AppKit coordinate system.
|
|
pub fn primary() -> Self {
|
|
// Instead of iterating through all active systems displays via `all()` we use the first
|
|
// NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList`
|
|
// will always return a list of active displays (machine might be sleeping).
|
|
//
|
|
// The following is what Chromium does too:
|
|
//
|
|
// https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56
|
|
unsafe {
|
|
let screens = NSScreen::screens(nil);
|
|
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0);
|
|
let device_description = NSScreen::deviceDescription(screen);
|
|
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
|
let screen_number = device_description.objectForKey_(screen_number_key);
|
|
let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
|
|
Self(screen_number)
|
|
}
|
|
}
|
|
|
|
/// Obtains an iterator over all currently active system displays.
|
|
pub fn all() -> impl Iterator<Item = Self> {
|
|
unsafe {
|
|
// We're assuming there aren't more than 32 displays connected to the system.
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[link(name = "ApplicationServices", kind = "framework")]
|
|
unsafe extern "C" {
|
|
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
|
}
|
|
|
|
impl PlatformDisplay for MacDisplay {
|
|
fn id(&self) -> DisplayId {
|
|
DisplayId(self.0)
|
|
}
|
|
|
|
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 {
|
|
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
|
// the top left of the primary display.
|
|
let bounds = CGDisplayBounds(self.0);
|
|
|
|
Bounds {
|
|
origin: Default::default(),
|
|
size: size(px(bounds.size.width as f32), px(bounds.size.height as f32)),
|
|
}
|
|
}
|
|
}
|
|
}
|