display-info 0.5.9

Cross-platform get display info
Documentation
use objc2::MainThreadMarker;
use objc2_app_kit::NSScreen;
use objc2_core_foundation::{CGPoint, CGRect};
use objc2_core_graphics::{
    CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyDisplayMode, CGDisplayIsBuiltin,
    CGDisplayIsMain, CGDisplayMode, CGDisplayRotation, CGDisplayScreenSize, CGError,
    CGGetActiveDisplayList, CGGetDisplaysWithPoint,
};
use objc2_foundation::{NSNumber, NSString};

use crate::{
    DisplayInfo,
    error::{DIError, DIResult},
};

pub type ScreenRawHandle = CGDirectDisplayID;

fn get_display_friendly_name(display_id: CGDirectDisplayID) -> DIResult<String> {
    let screens = NSScreen::screens(unsafe { MainThreadMarker::new_unchecked() });
    for screen in screens {
        let device_description = screen.deviceDescription();
        let screen_number = device_description
            .objectForKey(&NSString::from_str("NSScreenNumber"))
            .ok_or(DIError::new("Get NSScreenNumber failed"))?;

        let screen_id = screen_number
            .downcast::<NSNumber>()
            .map_err(|err| DIError::new(format!("{:?}", err)))?
            .unsignedIntValue();

        if screen_id == display_id {
            unsafe { return Ok(screen.localizedName().to_string()) };
        }
    }

    Err(DIError::new(format!(
        "Get display {} friendly name failed",
        display_id
    )))
}

impl DisplayInfo {
    fn new(id: CGDirectDisplayID) -> DIResult<Self> {
        unsafe {
            let CGRect { origin, size } = CGDisplayBounds(id);

            let rotation = CGDisplayRotation(id) as f32;

            let display_mode = CGDisplayCopyDisplayMode(id);
            let pixel_width = CGDisplayMode::pixel_width(display_mode.as_deref());
            let scale_factor = pixel_width as f32 / size.width as f32;
            let frequency = CGDisplayMode::refresh_rate(display_mode.as_deref()) as f32;

            let size_mm = CGDisplayScreenSize(id);
            let is_primary = CGDisplayIsMain(id);
            let is_builtin = CGDisplayIsBuiltin(id);

            Ok(DisplayInfo {
                id,
                name: format!("Display {id}"),
                friendly_name: get_display_friendly_name(id)
                    .unwrap_or(format!("Unknown Display {}", id)),
                raw_handle: id,
                x: origin.x as i32,
                y: origin.y as i32,
                width: size.width as u32,
                height: size.height as u32,
                width_mm: size_mm.width as i32,
                height_mm: size_mm.height as i32,
                rotation,
                frequency,
                scale_factor,
                is_primary,
                is_builtin,
            })
        }
    }

    pub fn all() -> DIResult<Vec<DisplayInfo>> {
        let max_displays: u32 = 16;
        let mut active_displays: Vec<CGDirectDisplayID> = vec![0; max_displays as usize];
        let mut display_count: u32 = 0;

        let cg_error = unsafe {
            CGGetActiveDisplayList(
                max_displays,
                active_displays.as_mut_ptr(),
                &mut display_count,
            )
        };

        if cg_error != CGError::Success {
            return Err(DIError::new(format!(
                "CGGetActiveDisplayList failed: {:?}",
                cg_error
            )));
        }

        active_displays.truncate(display_count as usize);

        let mut display_infos = Vec::with_capacity(active_displays.len());

        for display in active_displays {
            display_infos.push(DisplayInfo::new(display)?);
        }

        Ok(display_infos)
    }

    pub fn from_point(x: i32, y: i32) -> DIResult<DisplayInfo> {
        let point = CGPoint {
            x: x as f64,
            y: y as f64,
        };

        let max_displays: u32 = 16;
        let mut display_ids: Vec<CGDirectDisplayID> = vec![0; max_displays as usize];
        let mut display_count: u32 = 0;

        let cg_error = unsafe {
            CGGetDisplaysWithPoint(
                point,
                max_displays,
                display_ids.as_mut_ptr(),
                &mut display_count,
            )
        };

        if cg_error != CGError::Success {
            return Err(DIError::new(format!(
                "CGGetDisplaysWithPoint failed: {:?}",
                cg_error
            )));
        }

        if let Some(&display_id) = display_ids.first() {
            DisplayInfo::new(display_id)
        } else {
            Err(DIError::new("Display not found"))
        }
    }
}