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