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