Skip to main content

screen_capture_kit/
stream.rs

1use std::ptr::null_mut;
2
3use block2::RcBlock;
4use core_foundation::{base::TCFType, string::CFStringRef};
5use core_graphics::{color::CGColor, geometry::CGRect};
6use core_media::{sample_buffer::CMSampleBufferRef, time::CMTime, OSType};
7use dispatch2::{DispatchObject, DispatchQueue};
8use libc::size_t;
9use objc2::{
10    encode::{Encode, Encoding},
11    extern_class, extern_protocol, msg_send,
12    rc::{Allocated, Retained},
13    runtime::ProtocolObject,
14    ClassType,
15};
16use objc2_foundation::{NSArray, NSError, NSInteger, NSObject, NSObjectProtocol, NSString};
17
18use crate::{
19    encode,
20    shareable_content::{SCDisplay, SCRunningApplication, SCWindow},
21};
22
23#[repr(transparent)]
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct SCStreamOutputType(pub NSInteger);
26
27impl SCStreamOutputType {
28    #[doc(alias = "SCStreamOutputTypeScreen")]
29    pub const Screen: Self = Self(0);
30    #[doc(alias = "SCStreamOutputTypeAudio")]
31    pub const Audio: Self = Self(1);
32}
33
34unsafe impl Encode for SCStreamOutputType {
35    const ENCODING: Encoding = Encoding::Int;
36}
37
38#[repr(transparent)]
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct SCFrameStatus(pub NSInteger);
41
42impl SCFrameStatus {
43    #[doc(alias = "SCFrameStatusComplete")]
44    pub const Complete: Self = Self(0);
45    #[doc(alias = "SCFrameStatusIdle")]
46    pub const Idle: Self = Self(1);
47    #[doc(alias = "SCFrameStatusBlank")]
48    pub const Blank: Self = Self(2);
49    #[doc(alias = "SCFrameStatusSuspended")]
50    pub const Suspended: Self = Self(3);
51    #[doc(alias = "SCFrameStatusStarted")]
52    pub const Started: Self = Self(4);
53    #[doc(alias = "SCFrameStatusStopped")]
54    pub const Stopped: Self = Self(5);
55}
56
57extern_class!(
58    #[unsafe(super(NSObject))]
59    #[derive(Debug, PartialEq, Eq, Hash)]
60    pub struct SCContentFilter;
61);
62
63unsafe impl NSObjectProtocol for SCContentFilter {}
64
65impl SCContentFilter {
66    pub fn new() -> Retained<Self> {
67        unsafe { msg_send![SCContentFilter::class(), new] }
68    }
69
70    pub fn init_with_desktop_independent_window(this: Allocated<Self>, window: &SCWindow) -> Retained<Self> {
71        unsafe { msg_send![this, initWithDesktopIndependentWindow: window] }
72    }
73
74    pub fn init_with_display_exclude_windows(this: Allocated<Self>, display: &SCDisplay, excluded: &NSArray<SCWindow>) -> Retained<Self> {
75        unsafe { msg_send![this, initWithDisplay: display, excludingWindows: excluded] }
76    }
77
78    pub fn init_with_display_include_windows(this: Allocated<Self>, display: &SCDisplay, included: &NSArray<SCWindow>) -> Retained<Self> {
79        unsafe { msg_send![this, initWithDisplay: display, includingWindows: included] }
80    }
81
82    pub fn init_with_display_exclude_applications(
83        this: Allocated<Self>,
84        display: &SCDisplay,
85        applications: &NSArray<SCRunningApplication>,
86        excepting_windows: &NSArray<SCWindow>,
87    ) -> Retained<Self> {
88        unsafe { msg_send![this, initWithDisplay: display, excludingApplications: applications, exceptingWindows: excepting_windows] }
89    }
90
91    pub fn init_with_display_include_applications(
92        this: Allocated<Self>,
93        display: &SCDisplay,
94        applications: &NSArray<SCRunningApplication>,
95        excepting_windows: &NSArray<SCWindow>,
96    ) -> Retained<Self> {
97        unsafe { msg_send![this, initWithDisplay: display, includingApplications: applications, exceptingWindows: excepting_windows] }
98    }
99}
100
101extern_class!(
102    #[unsafe(super(NSObject))]
103    #[derive(Debug, PartialEq, Eq, Hash)]
104    pub struct SCStreamConfiguration;
105);
106
107unsafe impl NSObjectProtocol for SCStreamConfiguration {}
108
109impl SCStreamConfiguration {
110    pub fn new() -> Retained<Self> {
111        unsafe { msg_send![SCStreamConfiguration::class(), new] }
112    }
113
114    pub fn get_height(&self) -> size_t {
115        unsafe { msg_send![self, height] }
116    }
117
118    pub fn set_height(&self, height: size_t) {
119        unsafe { msg_send![self, setHeight: height] }
120    }
121
122    pub fn get_width(&self) -> size_t {
123        unsafe { msg_send![self, width] }
124    }
125
126    pub fn set_width(&self, width: size_t) {
127        unsafe { msg_send![self, setWidth: width] }
128    }
129
130    pub fn get_minimum_frame_interval(&self) -> CMTime {
131        unsafe { msg_send![self, minimumFrameInterval] }
132    }
133
134    pub fn set_minimum_frame_interval(&self, interval: CMTime) {
135        unsafe { msg_send![self, setMinimumFrameInterval: interval] }
136    }
137
138    pub fn get_pixel_format(&self) -> OSType {
139        unsafe { msg_send![self, pixelFormat] }
140    }
141
142    pub fn set_pixel_format(&self, format: OSType) {
143        unsafe { msg_send![self, setPixelFormat: format] }
144    }
145
146    pub fn get_scales_to_fit(&self) -> bool {
147        unsafe { msg_send![self, scalesToFit] }
148    }
149
150    pub fn set_scales_to_fit(&self, scales_to_fit: bool) {
151        unsafe { msg_send![self, setScalesToFit: scales_to_fit] }
152    }
153
154    pub fn get_show_cursor(&self) -> bool {
155        unsafe { msg_send![self, showCursor] }
156    }
157
158    pub fn set_show_cursor(&self, show_cursor: bool) {
159        unsafe { msg_send![self, setShowCursor: show_cursor] }
160    }
161
162    pub fn get_background_color(&self) -> CGColor {
163        unsafe { CGColor::wrap_under_get_rule(msg_send![self, backgroundColor]) }
164    }
165
166    pub fn set_background_color(&self, color: CGColor) {
167        unsafe { msg_send![self, setBackgroundColor: color.as_concrete_TypeRef()] }
168    }
169
170    pub fn get_source_rect(&self) -> CGRect {
171        unsafe { msg_send![self, sourceRect] }
172    }
173
174    pub fn set_source_rect(&self, rect: CGRect) {
175        unsafe { msg_send![self, setSourceRect: rect] }
176    }
177
178    pub fn get_destination_rect(&self) -> CGRect {
179        unsafe { msg_send![self, destinationRect] }
180    }
181
182    pub fn set_destination_rect(&self, rect: CGRect) {
183        unsafe { msg_send![self, setDestinationRect: rect] }
184    }
185
186    pub fn get_queue_depth(&self) -> NSInteger {
187        unsafe { msg_send![self, queueDepth] }
188    }
189
190    pub fn set_queue_depth(&self, depth: NSInteger) {
191        unsafe { msg_send![self, setQueueDepth: depth] }
192    }
193
194    pub fn get_color_matrix(&self) -> CFStringRef {
195        unsafe {
196            let color_matrix: encode::CFStringRef = msg_send![self, colorMatrix];
197            color_matrix as CFStringRef
198        }
199    }
200
201    pub fn set_color_matrix(&self, matrix: CFStringRef) {
202        unsafe { msg_send![self, setColorMatrix: matrix as encode::CFStringRef] }
203    }
204
205    pub fn get_color_space_name(&self) -> CFStringRef {
206        unsafe {
207            let color_space_name: encode::CFStringRef = msg_send![self, colorSpaceName];
208            color_space_name as CFStringRef
209        }
210    }
211
212    pub fn set_color_space_name(&self, name: CFStringRef) {
213        unsafe { msg_send![self, setColorSpaceName: name as encode::CFStringRef] }
214    }
215
216    pub fn get_captures_audio(&self) -> bool {
217        unsafe { msg_send![self, capturesAudio] }
218    }
219
220    pub fn set_captures_audio(&self, captures_audio: bool) {
221        unsafe { msg_send![self, setCapturesAudio: captures_audio] }
222    }
223
224    pub fn get_sample_rate(&self) -> f64 {
225        unsafe { msg_send![self, sampleRate] }
226    }
227
228    pub fn set_sample_rate(&self, rate: f64) {
229        unsafe { msg_send![self, setSampleRate: rate] }
230    }
231
232    pub fn get_channel_count(&self) -> size_t {
233        unsafe { msg_send![self, channelCount] }
234    }
235
236    pub fn set_channel_count(&self, count: size_t) {
237        unsafe { msg_send![self, setChannelCount: count] }
238    }
239
240    pub fn get_excludes_current_process_audio(&self) -> bool {
241        unsafe { msg_send![self, excludesCurrentProcessAudio] }
242    }
243
244    pub fn set_excludes_current_process_audio(&self, excludes_current_process_audio: bool) {
245        unsafe { msg_send![self, setExcludesCurrentProcessAudio: excludes_current_process_audio] }
246    }
247}
248
249pub type SCStreamFrameInfo = NSString;
250
251extern "C" {
252    pub static SCStreamFrameInfoStatus: &'static NSString;
253    pub static SCStreamFrameInfoDisplayTime: &'static NSString;
254    pub static SCStreamFrameInfoScaleFactor: &'static NSString;
255    pub static SCStreamFrameInfoContentScale: &'static NSString;
256    pub static SCStreamFrameInfoContentRect: &'static NSString;
257    pub static SCStreamFrameInfoDirtyRects: &'static NSString;
258    pub static SCStreamFrameInfoScreenRect: &'static NSString;
259}
260
261extern_class!(
262    #[unsafe(super(NSObject))]
263    #[derive(Debug, PartialEq, Eq, Hash)]
264    pub struct SCStream;
265);
266
267unsafe impl NSObjectProtocol for SCStream {}
268
269type CompletionHandler = RcBlock<dyn Fn(*mut NSError)>;
270
271impl SCStream {
272    pub fn new() -> Retained<Self> {
273        unsafe { msg_send![SCStream::class(), new] }
274    }
275
276    pub fn init_with_filter(
277        this: Allocated<Self>,
278        filter: &SCContentFilter,
279        configuration: &SCStreamConfiguration,
280        delegate: &ProtocolObject<dyn SCStreamDelegate>,
281    ) -> Retained<Self> {
282        unsafe { msg_send![this, initWithFilter: filter, configuration: configuration, delegate: delegate] }
283    }
284
285    pub fn add_stream_output(
286        &self,
287        output: &ProtocolObject<dyn SCStreamOutput>,
288        output_type: SCStreamOutputType,
289        queue: &DispatchQueue,
290    ) -> Result<bool, Retained<NSError>> {
291        let mut error: *mut NSError = null_mut();
292        let result = unsafe {
293            msg_send![self, addStreamOutput: output, type: output_type.0, sampleHandlerQueue: queue.as_raw().as_ptr() as *const NSObject, error: &mut error]
294        };
295        if result {
296            Ok(result)
297        } else {
298            Err(unsafe { Retained::retain(error).unwrap() })
299        }
300    }
301
302    pub fn remove_stream_output(
303        &self,
304        output: &ProtocolObject<dyn SCStreamOutput>,
305        output_type: SCStreamOutputType,
306    ) -> Result<bool, Retained<NSError>> {
307        let mut error: *mut NSError = null_mut();
308        let result = unsafe { msg_send![self, removeStreamOutput: output, type: output_type.0, error: &mut error] };
309        if result {
310            Ok(result)
311        } else {
312            Err(unsafe { Retained::retain(error).unwrap() })
313        }
314    }
315
316    fn new_completion_handler<F>(closure: F) -> CompletionHandler
317    where
318        F: Fn(Option<Retained<NSError>>) + 'static,
319    {
320        RcBlock::new(move |error: *mut NSError| {
321            closure(if error.is_null() {
322                None
323            } else {
324                unsafe { Retained::retain(error) }
325            });
326        })
327    }
328
329    pub fn update_content_filter<F>(&self, content_filter: &SCContentFilter, closure: F)
330    where
331        F: Fn(Option<Retained<NSError>>) + 'static,
332    {
333        let handler = Self::new_completion_handler(closure);
334        unsafe { msg_send![self, updateContentFilter: content_filter, completionHandler: &*handler] }
335    }
336
337    pub fn update_configuration<F>(&self, stream_config: &SCStreamConfiguration, closure: F)
338    where
339        F: Fn(Option<Retained<NSError>>) + 'static,
340    {
341        let handler = Self::new_completion_handler(closure);
342        unsafe { msg_send![self, updateConfiguration: stream_config, completionHandler: &*handler] }
343    }
344
345    pub fn start_capture<F>(&self, closure: F)
346    where
347        F: Fn(Option<Retained<NSError>>) + 'static,
348    {
349        let handler = Self::new_completion_handler(closure);
350        unsafe { msg_send![self, startCaptureWithCompletionHandler: &*handler] }
351    }
352
353    pub fn stop_capture<F>(&self, closure: F)
354    where
355        F: Fn(Option<Retained<NSError>>) + 'static,
356    {
357        let handler = Self::new_completion_handler(closure);
358        unsafe { msg_send![self, stopCaptureWithCompletionHandler: &*handler] }
359    }
360}
361
362extern_protocol!(
363    pub unsafe trait SCStreamOutput: NSObjectProtocol {
364        #[unsafe(method(stream:didOutputSampleBuffer:ofType:))]
365        #[optional]
366        unsafe fn stream_did_output_sample_buffer(&self, stream: &SCStream, sample_buffer: CMSampleBufferRef, of_type: SCStreamOutputType);
367    }
368);
369
370extern_protocol!(
371    pub unsafe trait SCStreamDelegate: NSObjectProtocol {
372        #[unsafe(method(stream:didStopWithError:))]
373        #[optional]
374        unsafe fn stream_did_stop_with_error(&self, stream: &SCStream, error: &NSError);
375    }
376);