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