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}