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 following 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}