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