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}");
}
std::mem::forget(self.display_link.take());
self.frame_requests.cancel();
}
}
mod sys {
#![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,
current_time: *const CVTimeStamp,
output_time: *const CVTimeStamp,
flags_in: i64,
flags_out: *mut i64,
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 {
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 {
pub unsafe fn start(&mut self) -> Result<(), DisplayLinkError> {
unsafe {
let code = CVDisplayLinkStart(self);
if code != 0 {
return Err(DisplayLinkError::Start { code });
}
Ok(())
}
}
pub unsafe fn stop(&mut self) -> Result<(), DisplayLinkError> {
unsafe {
let code = CVDisplayLinkStop(self);
if code != 0 {
return Err(DisplayLinkError::Stop { code });
}
Ok(())
}
}
}
}