use crate::compat::Vec;
use crate::core::Rect;
use crate::platform::types::{Display, DisplayId};
use cocoa::base::{id, nil};
use cocoa::foundation::{NSAutoreleasePool, NSRect, NSString};
use objc::{class, msg_send, sel, sel_impl};
const SCREEN_NUMBER_KEY: &str = "NSScreenNumber";
fn rect_of(rect: NSRect) -> Rect {
Rect::new(
rect.origin.x.round() as i32,
rect.origin.y.round() as i32,
rect.size.width.max(0.0).round() as u32,
rect.size.height.max(0.0).round() as u32,
)
}
unsafe fn screen_number(screen: id) -> Option<u32> {
let description: id = msg_send![screen, deviceDescription];
if description == nil {
return None;
}
let key = NSString::alloc(nil).init_str(SCREEN_NUMBER_KEY).autorelease();
let number: id = msg_send![description, objectForKey: key];
if number == nil {
return None;
}
Some(msg_send![number, unsignedIntValue])
}
unsafe fn display_id_at(index: usize, screen: id) -> DisplayId {
DisplayId::new(screen_number(screen).unwrap_or(index as u32) as u64)
}
pub(crate) fn displays() -> Vec<Display> {
if !super::types::is_main_thread() {
return Vec::new();
}
unsafe {
let screens: id = msg_send![class!(NSScreen), screens];
if screens == nil {
return Vec::new();
}
let count: usize = msg_send![screens, count];
let main_screen: id = msg_send![class!(NSScreen), mainScreen];
let pool = NSAutoreleasePool::new(nil);
let mut out = Vec::new();
for index in 0..count {
let screen: id = msg_send![screens, objectAtIndex: index];
if screen == nil {
continue;
}
let frame: NSRect = msg_send![screen, frame];
let visible: NSRect = msg_send![screen, visibleFrame];
let scale: f64 = msg_send![screen, backingScaleFactor];
out.push(Display {
id: display_id_at(index, screen),
bounds: rect_of(frame),
work_area: rect_of(visible),
scale: if scale > 0.0 { scale as f32 } else { 1.0 },
is_primary: screen == main_screen,
});
}
pool.drain();
out
}
}
pub(crate) unsafe fn window_display(window: id) -> Option<DisplayId> {
if window == nil {
return None;
}
let screen: id = msg_send![window, screen];
if screen == nil {
return None;
}
let screens: id = msg_send![class!(NSScreen), screens];
if screens == nil {
return None;
}
let count: usize = msg_send![screens, count];
let pool = NSAutoreleasePool::new(nil);
let mut found = None;
for index in 0..count {
let candidate: id = msg_send![screens, objectAtIndex: index];
if candidate == screen {
found = Some(display_id_at(index, screen));
break;
}
}
pool.drain();
found
}
pub(crate) fn generation() -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
if !super::types::is_main_thread() {
return 0;
}
let mut hash = FNV_OFFSET;
let mut mix = |value: u64| {
hash ^= value;
hash = hash.wrapping_mul(FNV_PRIME);
};
let displays = displays();
mix(displays.len() as u64);
for display in displays {
for part in [
display.id.get(),
display.bounds.x as i64 as u64,
display.bounds.y as i64 as u64,
display.bounds.width as u64,
display.bounds.height as u64,
display.work_area.x as i64 as u64,
display.work_area.y as i64 as u64,
display.work_area.width as u64,
display.work_area.height as u64,
display.scale.to_bits() as u64,
display.is_primary as u64,
] {
mix(part);
}
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_off_main_thread_is_answered_instead_of_messaging_appkit() {
let answer = std::thread::spawn(|| (displays(), generation()))
.join()
.expect("the probe thread must not panic");
assert_eq!(answer, (Vec::new(), 0));
}
}