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