use crate::{MonitorId, MonitorInfo};
use super::ffi::{self, Id, NSRect, Sel};
use super::geometry::{self, Flip};
use super::shell::AppKitShell;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Screen {
pub id: MonitorId,
pub frame: NSRect,
pub visible: NSRect,
pub scale: f64,
}
pub(super) unsafe fn enumerate(shell: &mut AppKitShell) -> (Vec<Screen>, Vec<MonitorInfo>) {
let Some(class) = ffi::class(c"NSScreen") else {
return (Vec::new(), Vec::new());
};
let array = unsafe { ffi::msg(class, ffi::sel(c"screens")) };
if array.is_null() {
return (Vec::new(), Vec::new());
}
let count = unsafe { ffi::msg_usize(array, ffi::sel(c"count")) };
if count == 0 {
return (Vec::new(), Vec::new());
}
let primary = unsafe {
let object_at: unsafe extern "C" fn(Id, Sel, usize) -> Id = ffi::msg_send();
let first = object_at(array, ffi::sel(c"objectAtIndex:"), 0);
ffi::msg_rect(first, ffi::sel(c"frame"))
};
let flip = Flip::about_primary(primary);
let mut screens = Vec::with_capacity(count);
let mut monitors = Vec::with_capacity(count);
for index in 0..count {
let screen = unsafe {
let object_at: unsafe extern "C" fn(Id, Sel, usize) -> Id = ffi::msg_send();
object_at(array, ffi::sel(c"objectAtIndex:"), index)
};
if screen.is_null() {
continue;
}
let (frame, visible, scale) = unsafe {
(
ffi::msg_rect(screen, ffi::sel(c"frame")),
ffi::msg_rect(screen, ffi::sel(c"visibleFrame")),
ffi::msg_f64(screen, ffi::sel(c"backingScaleFactor")),
)
};
let scale = geometry::usable_scale(scale);
let display = unsafe { display_id(screen) };
let id = shell.monitor_id_for(display);
let name = unsafe { name_of(screen) }
.unwrap_or_else(|| format!("display {}", display.unwrap_or_default()));
screens.push(Screen {
id,
frame,
visible,
scale,
});
monitors.push(MonitorInfo {
id,
name,
bounds: geometry::physical_rect(flip.rect(frame), scale),
work_area: geometry::physical_rect(flip.rect(visible), scale),
scale_factor: scale,
refresh_millihertz: display.map_or(0, refresh_millihertz),
is_primary: index == 0,
});
}
(screens, monitors)
}
pub(super) unsafe fn display_id(screen: Id) -> Option<u32> {
let description = unsafe { ffi::msg(screen, ffi::sel(c"deviceDescription")) };
if description.is_null() {
return None;
}
let number = unsafe {
let key = ffi::nsstring("NSScreenNumber")?;
ffi::msg1(description, ffi::sel(c"objectForKey:"), key)
};
if number.is_null() {
return None;
}
let send: unsafe extern "C" fn(Id, Sel) -> u32 = unsafe { ffi::msg_send() };
Some(unsafe { send(number, ffi::sel(c"unsignedIntValue")) })
}
unsafe fn name_of(screen: Id) -> Option<String> {
let selector = ffi::sel(c"localizedName");
if !unsafe { ffi::responds_to(screen, selector) } {
return None;
}
let string = unsafe { ffi::msg(screen, selector) };
unsafe { ffi::string_from_nsstring(string) }
}
pub(super) fn refresh_millihertz(display: u32) -> u32 {
let mode = unsafe { ffi::CGDisplayCopyDisplayMode(display) };
if mode.is_null() {
return 0;
}
let hertz = unsafe { ffi::CGDisplayModeGetRefreshRate(mode) };
unsafe { ffi::CGDisplayModeRelease(mode) };
if !hertz.is_finite() || hertz <= 0.0 {
return 0;
}
let millihertz = (hertz * 1000.0).round();
if millihertz > f64::from(u32::MAX) {
return u32::MAX;
}
millihertz as u32
}
#[must_use]
pub(super) fn main_display() -> u32 {
unsafe { ffi::CGMainDisplayID() }
}
impl AppKitShell {
pub(super) fn monitor_id_for(&mut self, display: Option<u32>) -> MonitorId {
let display = display.unwrap_or_else(main_display);
if let Some((_, id)) = self.monitor_ids.iter().find(|(known, _)| *known == display) {
return *id;
}
let id = MonitorId(self.next_monitor_id);
self.next_monitor_id += 1;
self.monitor_ids.push((display, id));
id
}
}