Skip to main content

core_graphics2/
display_stream.rs

1use std::{ptr::NonNull, slice::from_raw_parts};
2
3use block::{Block, ConcreteBlock, RcBlock};
4use core_foundation::{
5    base::{CFType, CFTypeID, TCFType},
6    declare_TCFType,
7    dictionary::{CFDictionary, CFDictionaryRef},
8    impl_CFTypeDescription, impl_TCFType,
9    runloop::{CFRunLoopSource, CFRunLoopSourceRef},
10    string::{CFString, CFStringRef},
11};
12use dispatch2::{DispatchObject, DispatchQueue};
13use libc::{c_void, size_t};
14use objc2_core_foundation::CFRetained;
15use objc2_io_surface::IOSurfaceRef;
16
17#[allow(non_camel_case_types)]
18pub type dispatch_queue_t = *mut c_void;
19
20use crate::{base::CGFloat, display::CGDirectDisplayID, error::CGError, geometry::CGRect};
21
22#[repr(C)]
23pub struct __CGDisplayStream(c_void);
24
25pub type CGDisplayStreamRef = *mut __CGDisplayStream;
26
27#[repr(C)]
28pub struct __CGDisplayStreamUpdate(c_void);
29
30pub type CGDisplayStreamUpdateRef = *const __CGDisplayStreamUpdate;
31
32#[repr(i32)]
33#[derive(PartialEq, Eq, Debug, Clone, Copy)]
34pub enum CGDisplayStreamUpdateRectType {
35    #[doc(alias = "kCGDisplayStreamUpdateRefreshedRects")]
36    RefreshedRects    = 0,
37    #[doc(alias = "kCGDisplayStreamUpdateMovedRects")]
38    MovedRects        = 1,
39    #[doc(alias = "kCGDisplayStreamUpdateDirtyRects")]
40    DirtyRects        = 2,
41    #[doc(alias = "kCGDisplayStreamUpdateReducedDirtyRects")]
42    ReducedDirtyRects = 3,
43}
44
45#[repr(i32)]
46#[derive(PartialEq, Eq, Debug, Clone, Copy)]
47pub enum CGDisplayStreamFrameStatus {
48    #[doc(alias = "kCGDisplayStreamFrameStatusFrameComplete")]
49    FrameComplete = 0,
50    #[doc(alias = "kCGDisplayStreamFrameStatusFrameIdle")]
51    FrameIdle     = 1,
52    #[doc(alias = "kCGDisplayStreamFrameStatusFrameBlank")]
53    FrameBlank    = 2,
54    #[doc(alias = "kCGDisplayStreamFrameStatusStopped")]
55    Stopped       = 3,
56}
57
58pub type CGDisplayStreamFrameAvailableHandler = *const Block<(CGDisplayStreamFrameStatus, u64, *const IOSurfaceRef, CGDisplayStreamUpdateRef), ()>;
59
60extern "C" {
61    pub fn CGDisplayStreamUpdateGetTypeID() -> CFTypeID;
62    pub fn CGDisplayStreamUpdateGetRects(
63        update: CGDisplayStreamUpdateRef,
64        rectType: CGDisplayStreamUpdateRectType,
65        rectCount: *mut usize,
66    ) -> *const CGRect;
67    pub fn CGDisplayStreamUpdateCreateMergedUpdate(
68        firstUpdate: CGDisplayStreamUpdateRef,
69        secondUpdate: CGDisplayStreamUpdateRef,
70    ) -> CGDisplayStreamUpdateRef;
71    pub fn CGDisplayStreamUpdateGetMovedRectsDelta(updateRef: CGDisplayStreamUpdateRef, dx: *mut CGFloat, dy: *mut CGFloat);
72    pub fn CGDisplayStreamUpdateGetDropCount(updateRef: CGDisplayStreamUpdateRef) -> size_t;
73
74    pub static kCGDisplayStreamSourceRect: CFStringRef;
75    pub static kCGDisplayStreamDestinationRect: CFStringRef;
76    pub static kCGDisplayStreamPreserveAspectRatio: CFStringRef;
77    pub static kCGDisplayStreamColorSpace: CFStringRef;
78    pub static kCGDisplayStreamMinimumFrameTime: CFStringRef;
79    pub static kCGDisplayStreamShowCursor: CFStringRef;
80    pub static kCGDisplayStreamQueueDepth: CFStringRef;
81    pub static kCGDisplayStreamYCbCrMatrix: CFStringRef;
82    pub static kCGDisplayStreamYCbCrMatrix_ITU_R_709_2: CFStringRef;
83    pub static kCGDisplayStreamYCbCrMatrix_ITU_R_601_4: CFStringRef;
84    pub static kCGDisplayStreamYCbCrMatrix_SMPTE_240M_1995: CFStringRef;
85
86    pub fn CGDisplayStreamGetTypeID() -> CFTypeID;
87    pub fn CGDisplayStreamCreate(
88        display: CGDirectDisplayID,
89        outputWidth: size_t,
90        outputHeight: size_t,
91        pixelFormat: i32,
92        properties: CFDictionaryRef,
93        handler: CGDisplayStreamFrameAvailableHandler,
94    ) -> CGDisplayStreamRef;
95    pub fn CGDisplayStreamCreateWithDispatchQueue(
96        display: CGDirectDisplayID,
97        outputWidth: size_t,
98        outputHeight: size_t,
99        pixelFormat: i32,
100        properties: CFDictionaryRef,
101        queue: dispatch_queue_t,
102        handler: CGDisplayStreamFrameAvailableHandler,
103    ) -> CGDisplayStreamRef;
104    pub fn CGDisplayStreamStart(stream: CGDisplayStreamRef) -> CGError;
105    pub fn CGDisplayStreamStop(stream: CGDisplayStreamRef) -> CGError;
106    pub fn CGDisplayStreamGetRunLoopSource(stream: CGDisplayStreamRef) -> CFRunLoopSourceRef;
107}
108
109declare_TCFType!(CGDisplayStreamUpdate, CGDisplayStreamUpdateRef);
110impl_TCFType!(CGDisplayStreamUpdate, CGDisplayStreamUpdateRef, CGDisplayStreamUpdateGetTypeID);
111impl_CFTypeDescription!(CGDisplayStreamUpdate);
112
113impl CGDisplayStreamUpdate {
114    pub fn new_merged_update(&self, other: &CGDisplayStreamUpdate) -> Result<CGDisplayStreamUpdate, ()> {
115        unsafe {
116            let update = CGDisplayStreamUpdateCreateMergedUpdate(self.as_concrete_TypeRef(), other.as_concrete_TypeRef());
117            if update.is_null() {
118                Err(())
119            } else {
120                Ok(TCFType::wrap_under_create_rule(update))
121            }
122        }
123    }
124
125    pub fn rects(&self, rect_type: CGDisplayStreamUpdateRectType) -> &[CGRect] {
126        unsafe {
127            let mut rect_count = 0;
128            let rects = CGDisplayStreamUpdateGetRects(self.as_concrete_TypeRef(), rect_type, &mut rect_count);
129            if rects.is_null() || rect_count == 0 {
130                &[]
131            } else {
132                from_raw_parts(rects, rect_count)
133            }
134        }
135    }
136
137    pub fn moved_rects_delta(&self) -> (CGFloat, CGFloat) {
138        unsafe {
139            let mut dx = 0.0;
140            let mut dy = 0.0;
141            CGDisplayStreamUpdateGetMovedRectsDelta(self.as_concrete_TypeRef(), &mut dx, &mut dy);
142            (dx, dy)
143        }
144    }
145
146    pub fn drop_count(&self) -> usize {
147        unsafe { CGDisplayStreamUpdateGetDropCount(self.as_concrete_TypeRef()) }
148    }
149}
150
151declare_TCFType!(CGDisplayStream, CGDisplayStreamRef);
152impl_TCFType!(CGDisplayStream, CGDisplayStreamRef, CGDisplayStreamGetTypeID);
153impl_CFTypeDescription!(CGDisplayStream);
154
155impl CGDisplayStream {
156    fn new_frame_available_handler<F>(closure: F) -> RcBlock<(CGDisplayStreamFrameStatus, u64, *const IOSurfaceRef, CGDisplayStreamUpdateRef), ()>
157    where
158        F: Fn(CGDisplayStreamFrameStatus, u64, Option<CFRetained<IOSurfaceRef>>, Option<CGDisplayStreamUpdate>) + 'static,
159    {
160        ConcreteBlock::new(
161            move |status: CGDisplayStreamFrameStatus, timestamp: u64, surface: *const IOSurfaceRef, update: CGDisplayStreamUpdateRef| {
162                // The surface follows the Core Foundation "Get" rule, so retain it to keep an
163                // owned handle.
164                let surface = NonNull::new(surface.cast_mut()).map(|surface| unsafe { CFRetained::retain(surface) });
165                let update = if update.is_null() {
166                    None
167                } else {
168                    Some(unsafe { CGDisplayStreamUpdate::wrap_under_get_rule(update as CGDisplayStreamUpdateRef) })
169                };
170                closure(status, timestamp, surface, update);
171            },
172        )
173        .copy()
174    }
175
176    pub fn new<F>(
177        display: CGDirectDisplayID,
178        output_width: size_t,
179        output_height: size_t,
180        pixel_format: i32,
181        properties: &CFDictionary<CFString, CFType>,
182        closure: F,
183    ) -> Result<CGDisplayStream, ()>
184    where
185        F: Fn(CGDisplayStreamFrameStatus, u64, Option<CFRetained<IOSurfaceRef>>, Option<CGDisplayStreamUpdate>) + 'static,
186    {
187        let stream = unsafe {
188            CGDisplayStreamCreate(
189                display,
190                output_width,
191                output_height,
192                pixel_format,
193                properties.as_concrete_TypeRef(),
194                &*Self::new_frame_available_handler(closure),
195            )
196        };
197        if stream.is_null() {
198            Err(())
199        } else {
200            Ok(unsafe { TCFType::wrap_under_create_rule(stream) })
201        }
202    }
203
204    pub fn new_with_dispatch_queue<F>(
205        display: CGDirectDisplayID,
206        output_width: size_t,
207        output_height: size_t,
208        pixel_format: i32,
209        properties: &CFDictionary<CFString, CFType>,
210        queue: &DispatchQueue,
211        closure: F,
212    ) -> Result<CGDisplayStream, ()>
213    where
214        F: Fn(CGDisplayStreamFrameStatus, u64, Option<CFRetained<IOSurfaceRef>>, Option<CGDisplayStreamUpdate>) + 'static,
215    {
216        let stream = unsafe {
217            CGDisplayStreamCreateWithDispatchQueue(
218                display,
219                output_width,
220                output_height,
221                pixel_format,
222                properties.as_concrete_TypeRef(),
223                queue.as_raw().as_ptr() as dispatch_queue_t,
224                &*Self::new_frame_available_handler(closure),
225            )
226        };
227        if stream.is_null() {
228            Err(())
229        } else {
230            Ok(unsafe { TCFType::wrap_under_create_rule(stream) })
231        }
232    }
233
234    pub fn start(&self) -> CGError {
235        unsafe { CGDisplayStreamStart(self.as_concrete_TypeRef()) }
236    }
237
238    pub fn stop(&self) -> CGError {
239        unsafe { CGDisplayStreamStop(self.as_concrete_TypeRef()) }
240    }
241
242    pub fn run_loop_source(&self) -> Option<CFRunLoopSource> {
243        unsafe {
244            let source = CGDisplayStreamGetRunLoopSource(self.as_concrete_TypeRef());
245            if source.is_null() {
246                None
247            } else {
248                Some(TCFType::wrap_under_create_rule(source))
249            }
250        }
251    }
252}
253
254pub enum CGDisplayStreamProperties {
255    SourceRect,
256    DestinationRect,
257    PreserveAspectRatio,
258    ColorSpace,
259    MinimumFrameTime,
260    ShowCursor,
261    QueueDepth,
262    YCbCrMatrix,
263}
264
265impl From<CGDisplayStreamProperties> for CFStringRef {
266    fn from(key: CGDisplayStreamProperties) -> Self {
267        unsafe {
268            match key {
269                CGDisplayStreamProperties::SourceRect => kCGDisplayStreamSourceRect,
270                CGDisplayStreamProperties::DestinationRect => kCGDisplayStreamDestinationRect,
271                CGDisplayStreamProperties::PreserveAspectRatio => kCGDisplayStreamPreserveAspectRatio,
272                CGDisplayStreamProperties::ColorSpace => kCGDisplayStreamColorSpace,
273                CGDisplayStreamProperties::MinimumFrameTime => kCGDisplayStreamMinimumFrameTime,
274                CGDisplayStreamProperties::ShowCursor => kCGDisplayStreamShowCursor,
275                CGDisplayStreamProperties::QueueDepth => kCGDisplayStreamQueueDepth,
276                CGDisplayStreamProperties::YCbCrMatrix => kCGDisplayStreamYCbCrMatrix,
277            }
278        }
279    }
280}
281
282impl From<CGDisplayStreamProperties> for CFString {
283    fn from(key: CGDisplayStreamProperties) -> Self {
284        unsafe { CFString::wrap_under_get_rule(CFStringRef::from(key)) }
285    }
286}
287
288pub enum CGDisplayStreamYCbCrMatrices {
289    ITU_R_709_2,
290    ITU_R_601_4,
291    SMPTE_240M_1995,
292}
293
294impl From<CGDisplayStreamYCbCrMatrices> for CFStringRef {
295    fn from(matrix: CGDisplayStreamYCbCrMatrices) -> Self {
296        unsafe {
297            match matrix {
298                CGDisplayStreamYCbCrMatrices::ITU_R_709_2 => kCGDisplayStreamYCbCrMatrix_ITU_R_709_2,
299                CGDisplayStreamYCbCrMatrices::ITU_R_601_4 => kCGDisplayStreamYCbCrMatrix_ITU_R_601_4,
300                CGDisplayStreamYCbCrMatrices::SMPTE_240M_1995 => kCGDisplayStreamYCbCrMatrix_SMPTE_240M_1995,
301            }
302        }
303    }
304}
305
306impl From<CGDisplayStreamYCbCrMatrices> for CFString {
307    fn from(matrix: CGDisplayStreamYCbCrMatrices) -> Self {
308        unsafe { CFString::wrap_under_get_rule(CFStringRef::from(matrix)) }
309    }
310}