open-gpui-macos 0.2.0

macOS platform backend for Open GPUI.
Documentation
use core_graphics::display::CGDirectDisplayID;
use dispatch2::{
    _dispatch_source_type_data_add, DispatchObject, DispatchQueue, DispatchRetained, DispatchSource,
};
use std::{error::Error, ffi::c_void, fmt};

const K_CV_RETURN_INVALID_ARGUMENT: i32 = -6661;
const K_CV_RETURN_UNSUPPORTED: i32 = -6663;
const K_CV_RETURN_DISPLAY_LINK_NOT_RUNNING: i32 = -6672;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DisplayLinkError {
    Create { code: i32 },
    SetOutputCallback { code: i32 },
    AssignDisplay { code: i32 },
    Start { code: i32 },
    Stop { code: i32 },
    MissingDisplayLink,
}

impl DisplayLinkError {
    pub(crate) fn is_transient_create_failure(self) -> bool {
        matches!(
            self,
            Self::Create {
                code: K_CV_RETURN_INVALID_ARGUMENT | K_CV_RETURN_UNSUPPORTED
            }
        )
    }

    fn is_not_running(self) -> bool {
        matches!(
            self,
            Self::Stop {
                code: K_CV_RETURN_DISPLAY_LINK_NOT_RUNNING
            }
        )
    }
}

impl fmt::Display for DisplayLinkError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Create { code } => {
                write!(formatter, "could not create display link, code: {code}")
            }
            Self::SetOutputCallback { code } => {
                write!(
                    formatter,
                    "could not set display link output callback, code: {code}"
                )
            }
            Self::AssignDisplay { code } => {
                write!(
                    formatter,
                    "could not assign display to display link, code: {code}"
                )
            }
            Self::Start { code } => {
                write!(formatter, "could not start display link, code: {code}")
            }
            Self::Stop { code } => {
                write!(formatter, "could not stop display link, code: {code}")
            }
            Self::MissingDisplayLink => formatter.write_str("display link handle is missing"),
        }
    }
}

impl Error for DisplayLinkError {}

pub struct DisplayLink {
    display_link: Option<sys::DisplayLink>,
    frame_requests: DispatchRetained<DispatchSource>,
    started: bool,
}

impl DisplayLink {
    pub fn new(
        display_id: CGDirectDisplayID,
        data: *mut c_void,
        callback: extern "C" fn(*mut c_void),
    ) -> Result<DisplayLink, DisplayLinkError> {
        unsafe extern "C" fn display_link_callback(
            _display_link_out: *mut sys::CVDisplayLink,
            _current_time: *const sys::CVTimeStamp,
            _output_time: *const sys::CVTimeStamp,
            _flags_in: i64,
            _flags_out: *mut i64,
            frame_requests: *mut c_void,
        ) -> i32 {
            unsafe {
                let frame_requests = &*(frame_requests as *const DispatchSource);
                frame_requests.merge_data(1);
                0
            }
        }

        unsafe {
            let frame_requests = DispatchSource::new(
                &raw const _dispatch_source_type_data_add as *mut _,
                0,
                0,
                Some(DispatchQueue::main()),
            );
            frame_requests.set_context(data);
            frame_requests.set_event_handler_f(callback);
            frame_requests.resume();

            let display_link = sys::DisplayLink::new(
                display_id,
                display_link_callback,
                &*frame_requests as *const DispatchSource as *mut c_void,
            )?;

            Ok(Self {
                display_link: Some(display_link),
                frame_requests,
                started: false,
            })
        }
    }

    pub fn start(&mut self) -> Result<(), DisplayLinkError> {
        unsafe {
            self.display_link
                .as_mut()
                .ok_or(DisplayLinkError::MissingDisplayLink)?
                .start()?;
        }
        self.started = true;
        Ok(())
    }

    pub fn stop(&mut self) -> Result<(), DisplayLinkError> {
        if !self.started {
            return Ok(());
        }
        unsafe {
            self.display_link
                .as_mut()
                .ok_or(DisplayLinkError::MissingDisplayLink)?
                .stop()?;
        }
        self.started = false;
        Ok(())
    }
}

impl Drop for DisplayLink {
    fn drop(&mut self) {
        if let Err(error) = self.stop()
            && !error.is_not_running()
        {
            log::warn!("{error}");
        }
        // We see occasional segfaults on the CVDisplayLink thread.
        //
        // It seems possible that this happens because CVDisplayLinkRelease releases the CVDisplayLink
        // on the main thread immediately, but the background thread that CVDisplayLink uses for timers
        // is still accessing it.
        //
        // We might also want to upgrade to CADisplayLink, but that requires dropping old macOS support.
        std::mem::forget(self.display_link.take());
        self.frame_requests.cancel();
    }
}

mod sys {
    //! Derived from display-link crate under the following license:
    //! <https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT>
    //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
    #![allow(dead_code, non_upper_case_globals)]

    use super::{DisplayLinkError, K_CV_RETURN_INVALID_ARGUMENT};
    use core_graphics::display::CGDirectDisplayID;
    use foreign_types::{ForeignType, foreign_type};
    use std::{
        ffi::c_void,
        fmt::{self, Debug, Formatter},
    };

    #[derive(Debug)]
    pub enum CVDisplayLink {}

    foreign_type! {
        pub unsafe type DisplayLink {
            type CType = CVDisplayLink;
            fn drop = CVDisplayLinkRelease;
            fn clone = CVDisplayLinkRetain;
        }
    }

    impl Debug for DisplayLink {
        fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
            formatter
                .debug_tuple("DisplayLink")
                .field(&self.as_ptr())
                .finish()
        }
    }

    #[repr(C)]
    #[derive(Clone, Copy)]
    pub(crate) struct CVTimeStamp {
        pub version: u32,
        pub video_time_scale: i32,
        pub video_time: i64,
        pub host_time: u64,
        pub rate_scalar: f64,
        pub video_refresh_period: i64,
        pub smpte_time: CVSMPTETime,
        pub flags: u64,
        pub reserved: u64,
    }

    pub type CVTimeStampFlags = u64;

    pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
    pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
    pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
    pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
    pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
    pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
    pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
    pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
        kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
    pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
        kCVTimeStampTopField | kCVTimeStampBottomField;

    #[repr(C)]
    #[derive(Clone, Copy, Default)]
    pub(crate) struct CVSMPTETime {
        pub subframes: i16,
        pub subframe_divisor: i16,
        pub counter: u32,
        pub time_type: u32,
        pub flags: u32,
        pub hours: i16,
        pub minutes: i16,
        pub seconds: i16,
        pub frames: i16,
    }

    pub type CVSMPTETimeType = u32;

    pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
    pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
    pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
    pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
    pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
    pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
    pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
    pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;

    pub type CVSMPTETimeFlags = u32;

    pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
    pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;

    pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
        display_link_out: *mut CVDisplayLink,
        // A pointer to the current timestamp. This represents the timestamp when the callback is called.
        current_time: *const CVTimeStamp,
        // A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
        output_time: *const CVTimeStamp,
        // Unused
        flags_in: i64,
        // Unused
        flags_out: *mut i64,
        // A pointer to app-defined data.
        display_link_context: *mut c_void,
    ) -> i32;

    #[link(name = "CoreFoundation", kind = "framework")]
    #[link(name = "CoreVideo", kind = "framework")]
    #[allow(improper_ctypes, unknown_lints, clippy::duplicated_attributes)]
    unsafe extern "C" {
        pub fn CVDisplayLinkCreateWithActiveCGDisplays(
            display_link_out: *mut *mut CVDisplayLink,
        ) -> i32;
        pub fn CVDisplayLinkSetCurrentCGDisplay(
            display_link: &mut DisplayLinkRef,
            display_id: u32,
        ) -> i32;
        pub fn CVDisplayLinkSetOutputCallback(
            display_link: &mut DisplayLinkRef,
            callback: CVDisplayLinkOutputCallback,
            user_info: *mut c_void,
        ) -> i32;
        pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
        pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
        pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
        pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
    }

    impl DisplayLink {
        /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
        pub unsafe fn new(
            display_id: CGDirectDisplayID,
            callback: CVDisplayLinkOutputCallback,
            user_info: *mut c_void,
        ) -> Result<Self, DisplayLinkError> {
            unsafe {
                let mut display_link: *mut CVDisplayLink = 0 as _;

                let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
                if code != 0 {
                    return Err(DisplayLinkError::Create { code });
                }
                if display_link.is_null() {
                    return Err(DisplayLinkError::Create {
                        code: K_CV_RETURN_INVALID_ARGUMENT,
                    });
                }

                let mut display_link = DisplayLink::from_ptr(display_link);

                let code = CVDisplayLinkSetOutputCallback(&mut display_link, callback, user_info);
                if code != 0 {
                    return Err(DisplayLinkError::SetOutputCallback { code });
                }

                let code = CVDisplayLinkSetCurrentCGDisplay(&mut display_link, display_id);
                if code != 0 {
                    return Err(DisplayLinkError::AssignDisplay { code });
                }

                Ok(display_link)
            }
        }
    }

    impl DisplayLinkRef {
        /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
        pub unsafe fn start(&mut self) -> Result<(), DisplayLinkError> {
            unsafe {
                let code = CVDisplayLinkStart(self);
                if code != 0 {
                    return Err(DisplayLinkError::Start { code });
                }
                Ok(())
            }
        }

        /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
        pub unsafe fn stop(&mut self) -> Result<(), DisplayLinkError> {
            unsafe {
                let code = CVDisplayLinkStop(self);
                if code != 0 {
                    return Err(DisplayLinkError::Stop { code });
                }
                Ok(())
            }
        }
    }
}