display_linker.rs

  1use std::{
  2    ffi::c_void,
  3    mem,
  4    sync::{Arc, Weak},
  5};
  6
  7use crate::DisplayId;
  8use collections::HashMap;
  9use parking_lot::Mutex;
 10pub use sys::CVSMPTETime as SmtpeTime;
 11pub use sys::CVTimeStamp as VideoTimestamp;
 12
 13pub(crate) struct MacDisplayLinker {
 14    links: HashMap<DisplayId, MacDisplayLink>,
 15}
 16
 17struct MacDisplayLink {
 18    system_link: sys::DisplayLink,
 19    _output_callback: Arc<OutputCallback>,
 20}
 21
 22impl MacDisplayLinker {
 23    pub fn new() -> Self {
 24        MacDisplayLinker {
 25            links: Default::default(),
 26        }
 27    }
 28}
 29
 30type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>>;
 31
 32impl MacDisplayLinker {
 33    pub fn set_output_callback(
 34        &mut self,
 35        display_id: DisplayId,
 36        output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
 37    ) {
 38        if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
 39            let callback = Arc::new(Mutex::new(output_callback));
 40            let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
 41            unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
 42
 43            self.links.insert(
 44                display_id,
 45                MacDisplayLink {
 46                    _output_callback: callback,
 47                    system_link,
 48                },
 49            );
 50        } else {
 51            log::warn!("DisplayLink could not be obtained for {:?}", display_id);
 52            return;
 53        }
 54    }
 55
 56    pub fn start(&mut self, display_id: DisplayId) {
 57        if let Some(link) = self.links.get_mut(&display_id) {
 58            unsafe {
 59                link.system_link.start();
 60            }
 61        } else {
 62            log::warn!("No DisplayLink callback registered for {:?}", display_id)
 63        }
 64    }
 65
 66    pub fn stop(&mut self, display_id: DisplayId) {
 67        if let Some(link) = self.links.get_mut(&display_id) {
 68            unsafe {
 69                link.system_link.stop();
 70            }
 71        } else {
 72            log::warn!("No DisplayLink callback registered for {:?}", display_id)
 73        }
 74    }
 75}
 76
 77unsafe extern "C" fn trampoline(
 78    _display_link_out: *mut sys::CVDisplayLink,
 79    current_time: *const sys::CVTimeStamp,
 80    output_time: *const sys::CVTimeStamp,
 81    _flags_in: i64,
 82    _flags_out: *mut i64,
 83    user_data: *mut c_void,
 84) -> i32 {
 85    if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
 86        let output_callback: Weak<OutputCallback> =
 87            Weak::from_raw(user_data as *mut OutputCallback);
 88        if let Some(output_callback) = output_callback.upgrade() {
 89            (output_callback.lock())(current_time, output_time)
 90        }
 91        mem::forget(output_callback);
 92    }
 93    0
 94}
 95
 96mod sys {
 97    //! Derived from display-link crate under the fololwing license:
 98    //! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
 99    //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
100    #![allow(dead_code, non_upper_case_globals)]
101
102    use foreign_types::{foreign_type, ForeignType};
103    use std::{
104        ffi::c_void,
105        fmt::{Debug, Formatter, Result},
106    };
107
108    #[derive(Debug)]
109    pub enum CVDisplayLink {}
110
111    foreign_type! {
112        type CType = CVDisplayLink;
113        fn drop = CVDisplayLinkRelease;
114        fn clone = CVDisplayLinkRetain;
115        pub struct DisplayLink;
116        pub struct DisplayLinkRef;
117    }
118
119    impl Debug for DisplayLink {
120        fn fmt(&self, formatter: &mut Formatter) -> Result {
121            formatter
122                .debug_tuple("DisplayLink")
123                .field(&self.as_ptr())
124                .finish()
125        }
126    }
127
128    #[repr(C)]
129    #[derive(Clone, Copy)]
130    pub struct CVTimeStamp {
131        pub version: u32,
132        pub video_time_scale: i32,
133        pub video_time: i64,
134        pub host_time: u64,
135        pub rate_scalar: f64,
136        pub video_refresh_period: i64,
137        pub smpte_time: CVSMPTETime,
138        pub flags: u64,
139        pub reserved: u64,
140    }
141
142    pub type CVTimeStampFlags = u64;
143
144    pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
145    pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
146    pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
147    pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
148    pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
149    pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
150    pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
151    pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
152        kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
153    pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
154        kCVTimeStampTopField | kCVTimeStampBottomField;
155
156    #[repr(C)]
157    #[derive(Clone, Copy, Default)]
158    pub struct CVSMPTETime {
159        pub subframes: i16,
160        pub subframe_divisor: i16,
161        pub counter: u32,
162        pub time_type: u32,
163        pub flags: u32,
164        pub hours: i16,
165        pub minutes: i16,
166        pub seconds: i16,
167        pub frames: i16,
168    }
169
170    pub type CVSMPTETimeType = u32;
171
172    pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
173    pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
174    pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
175    pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
176    pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
177    pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
178    pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
179    pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
180
181    pub type CVSMPTETimeFlags = u32;
182
183    pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
184    pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
185
186    pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
187        display_link_out: *mut CVDisplayLink,
188        // A pointer to the current timestamp. This represents the timestamp when the callback is called.
189        current_time: *const CVTimeStamp,
190        // A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
191        output_time: *const CVTimeStamp,
192        // Unused
193        flags_in: i64,
194        // Unused
195        flags_out: *mut i64,
196        // A pointer to app-defined data.
197        display_link_context: *mut c_void,
198    ) -> i32;
199
200    #[link(name = "CoreFoundation", kind = "framework")]
201    #[link(name = "CoreVideo", kind = "framework")]
202    #[allow(improper_ctypes)]
203    extern "C" {
204        pub fn CVDisplayLinkCreateWithActiveCGDisplays(
205            display_link_out: *mut *mut CVDisplayLink,
206        ) -> i32;
207        pub fn CVDisplayLinkCreateWithCGDisplay(
208            display_id: u32,
209            display_link_out: *mut *mut CVDisplayLink,
210        ) -> i32;
211        pub fn CVDisplayLinkSetOutputCallback(
212            display_link: &mut DisplayLinkRef,
213            callback: CVDisplayLinkOutputCallback,
214            user_info: *mut c_void,
215        ) -> i32;
216        pub fn CVDisplayLinkSetCurrentCGDisplay(
217            display_link: &mut DisplayLinkRef,
218            display_id: u32,
219        ) -> i32;
220        pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
221        pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
222        pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
223        pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
224    }
225
226    impl DisplayLink {
227        /// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
228        pub unsafe fn new() -> Option<Self> {
229            let mut display_link: *mut CVDisplayLink = 0 as _;
230            let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
231            if code == 0 {
232                Some(DisplayLink::from_ptr(display_link))
233            } else {
234                None
235            }
236        }
237
238        /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
239        pub unsafe fn on_display(display_id: u32) -> Option<Self> {
240            let mut display_link: *mut CVDisplayLink = 0 as _;
241            let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
242            if code == 0 {
243                Some(DisplayLink::from_ptr(display_link))
244            } else {
245                None
246            }
247        }
248    }
249
250    impl DisplayLinkRef {
251        /// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
252        pub unsafe fn set_output_callback(
253            &mut self,
254            callback: CVDisplayLinkOutputCallback,
255            user_info: *mut c_void,
256        ) {
257            assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
258        }
259
260        /// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
261        pub unsafe fn set_current_display(&mut self, display_id: u32) {
262            assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
263        }
264
265        /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
266        pub unsafe fn start(&mut self) {
267            assert_eq!(CVDisplayLinkStart(self), 0);
268        }
269
270        /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
271        pub unsafe fn stop(&mut self) {
272            assert_eq!(CVDisplayLinkStop(self), 0);
273        }
274    }
275}