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 }
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, Default)]
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}