Skip to main content

cidre/sc/
stream.rs

1use crate::{api, arc, cg, cm, define_cls, define_obj_type, dispatch, ns, objc, sc};
2
3#[cfg(any(target_os = "macos", target_abi = "macabi"))]
4use crate::{cf, cv};
5
6#[cfg(feature = "blocks")]
7use crate::blocks;
8
9/// Denotes the status of frame sample buffer.
10#[doc(alias = "SCFrameStatus")]
11#[derive(Debug, PartialEq, Eq, Copy, Clone)]
12#[repr(isize)]
13pub enum FrameStatus {
14    Complete,
15    Idle,
16    Blank,
17    Suspended,
18    Started,
19    Stopped,
20}
21
22define_obj_type!(
23    /// Keys you use to retrieve metadata from a frame the system captures.
24    #[doc(alias = "SCStreamFrameInfo")]
25    pub FrameInfo(ns::String)
26);
27
28impl FrameInfo {
29    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] that denotes the frames [`sc::FrameStatus`]
30    #[doc(alias = "SCStreamFrameInfoStatus")]
31    #[inline]
32    #[api::available(
33        macos = 12.3,
34        maccatalyst = 18.2,
35        ios = 27.0,
36        visionos = 27.0,
37        tvos = 27.0
38    )]
39    pub fn status() -> &'static Self {
40        unsafe { SCStreamFrameInfoStatus }
41    }
42
43    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the mach absolute
44    /// time when the event occurred. For a frame event, this is when the frame was displayed by the window server.
45    #[doc(alias = "SCStreamFrameInfoDisplayTime")]
46    #[inline]
47    #[api::available(
48        macos = 12.3,
49        maccatalyst = 18.2,
50        ios = 27.0,
51        visionos = 27.0,
52        tvos = 27.0
53    )]
54    pub fn display_time() -> &'static Self {
55        unsafe { SCStreamFrameInfoDisplayTime }
56    }
57
58    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the display resolution
59    /// associated with the frame. Display resolution is the pixel to point scaling factor.
60    /// It should be in the range of \[1, 4\].
61    #[doc(alias = "SCStreamFrameInfoScaleFactor")]
62    #[inline]
63    #[api::available(
64        macos = 12.3,
65        maccatalyst = 18.2,
66        ios = 27.0,
67        visionos = 27.0,
68        tvos = 27.0
69    )]
70    pub fn scale_factor() -> &'static Self {
71        unsafe { SCStreamFrameInfoScaleFactor }
72    }
73
74    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the content scale
75    /// associated with the frame. Content scale is the scaling factor from original content
76    /// size to its size in surface.
77    #[doc(alias = "SCStreamFrameInfoContentScale")]
78    #[inline]
79    #[api::available(
80        macos = 12.3,
81        maccatalyst = 18.2,
82        ios = 27.0,
83        visionos = 27.0,
84        tvos = 27.0
85    )]
86    pub fn content_scale() -> &'static Self {
87        unsafe { SCStreamFrameInfoContentScale }
88    }
89
90    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the content rect
91    /// associated with the frame. Content rect is the size and location of content in points in surface.
92    #[doc(alias = "SCStreamFrameInfoContentRect")]
93    #[inline]
94    #[api::available(
95        macos = 12.3,
96        maccatalyst = 18.2,
97        ios = 27.0,
98        visionos = 27.0,
99        tvos = 27.0
100    )]
101    pub fn content_rect() -> &'static Self {
102        unsafe { SCStreamFrameInfoContentRect }
103    }
104
105    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for an array of rectangles
106    /// that is the union of both rectangles that were redrawn and rectangles that were moved.
107    /// This is an array of [`cg::Rect`] in [`ns::Value`]. The [`cg::Rect`]s elements are specified in pixels.
108    #[doc(alias = "SCStreamFrameInfoDirtyRects")]
109    #[inline]
110    #[api::available(macos = 12.3, maccatalyst = 18.2)]
111    pub fn dirty_rects() -> &'static Self {
112        unsafe { SCStreamFrameInfoDirtyRects }
113    }
114
115    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the onscreen location
116    /// of the captured content
117    #[doc(alias = "SCStreamFrameInfoScreenRect")]
118    #[inline]
119    #[api::available(
120        macos = 13.1,
121        maccatalyst = 18.2,
122        ios = 27.0,
123        visionos = 27.0,
124        tvos = 27.0
125    )]
126    pub fn screen_rect() -> &'static Self {
127        unsafe { SCStreamFrameInfoScreenRect }
128    }
129
130    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the bounding rect
131    /// associated with the frame. Bounding rect is the size and location of smallest bounding box
132    /// containing all captured windows in points and in surface coordinates.
133    #[doc(alias = "SCStreamFrameInfoBoundingRect")]
134    #[inline]
135    #[api::available(macos = 14.0, maccatalyst = 18.2)]
136    pub fn bounding_rect() -> &'static Self {
137        unsafe { SCStreamFrameInfoBoundingRect }
138    }
139
140    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the content rect associated
141    /// with the frame while in presenter overlay.  In presenter overlay small, this content rect is the size
142    /// and location of smallest bounding box containing all captured windows plus small overlay window
143    /// in points and in surface coordinates. If presenter overlay large, this content rect is the size and
144    /// location of shared content in points and in surface coordinates.
145    #[doc(alias = "SCStreamFrameInfoPresenterOverlayContentRect")]
146    #[inline]
147    #[api::available(macos = 14.2, maccatalyst = 18.2)]
148    pub fn presenter_overlay_content_rect() -> &'static Self {
149        unsafe { SCStreamFrameInfoPresenterOverlayContentRect }
150    }
151
152    /// The key for the [`cf::Dictionary`] attached to the [`cm::SampleBuf`] for the video orientation.
153    #[doc(alias = "SCStreamFrameInfoVideoOrientation")]
154    #[inline]
155    #[api::available(macos = 27.0, maccatalyst = 27.0, ios = 27.0, visionos = 27.0)]
156    pub fn video_orientation() -> &'static Self {
157        unsafe { SCStreamFrameInfoVideoOrientation }
158    }
159}
160
161#[doc(alias = "SCStreamOutputType")]
162#[derive(Debug, PartialEq, Eq, Copy, Clone)]
163#[repr(isize)]
164pub enum OutputType {
165    /// Video frames from captured screen content.
166    #[doc(alias = "SCStreamOutputTypeScreen")]
167    Screen,
168    /// System audio samples.
169    #[doc(alias = "SCStreamOutputTypeAudio")]
170    Audio,
171    /// Microphone audio samples.
172    #[doc(alias = "SCStreamOutputTypeMicrophone")]
173    Mic,
174}
175
176/// Denotes the setting that can be set to determine when to show the presenter overlay
177/// alert for any stream
178#[doc(alias = "SCPresenterOverlayAlertSetting")]
179#[derive(Debug, Eq, PartialEq, Copy, Clone)]
180#[repr(isize)]
181pub enum PresenterOverlayAlertSetting {
182    /// Allow the system to determine when to show the presenter overlay privacy alert.
183    System,
184
185    /// Never show the presenter overlay privacy alert.
186    Never,
187
188    /// Always show the presenter overlay privacy alert.
189    Always,
190}
191
192#[doc(alias = "SCCaptureResolutionType")]
193#[derive(Debug, Copy, Clone, Eq, PartialEq)]
194#[repr(isize)]
195pub enum CaptureResolution {
196    /// Let ScreenCaptureKit choose the capture resolution.
197    Automatic,
198    /// Capture at the best available resolution.
199    Best,
200    /// Capture at the nominal resolution.
201    Nominal,
202}
203
204define_obj_type!(
205    #[doc(alias = "SCStreamConfiguration")]
206    pub Cfg(ns::Id),
207    SC_STREAM_CONFIGURATION
208);
209
210/// Client can use sc::StreamCfgPreset to create sc::StreamCfg with suggested values of properties for various use cases
211#[doc(alias = "SCStreamConfigurationPreset")]
212#[derive(Debug, Copy, Clone, Eq, PartialEq)]
213#[repr(isize)]
214pub enum CfgPreset {
215    /// HDR stream capture optimized for the local display.
216    #[doc(alias = "SCStreamConfigurationPresetCaptureHDRStreamLocalDisplay")]
217    CaptureHdrStreamLocalDisplay,
218    /// HDR stream capture optimized for a canonical display.
219    #[doc(alias = "SCStreamConfigurationPresetCaptureHDRStreamCanonicalDisplay")]
220    CaptureHdrStreamCanoncalDisplay,
221    /// HDR screenshot capture optimized for the local display.
222    #[doc(alias = "SCStreamConfigurationPresetCaptureHDRScreenshotLocalDisplay")]
223    CaptureHdrScreenshotLocalDisplay,
224    /// HDR screenshot capture optimized for a canonical display.
225    #[doc(alias = "SCStreamConfigurationPresetCaptureHDRScreenshotCanonicalDisplay")]
226    CaptureHdrScreenshotCanoncalDisplay,
227    /// HDR recording capture that preserves SDR content in an HDR10 recording.
228    #[doc(alias = "SCStreamConfigurationPresetCaptureHDRRecordingPreservedSDRHDR10")]
229    CaptureHdrRecordingPreservedSdrHdr10,
230}
231
232impl Cfg {
233    /// The output width in pixels.
234    ///
235    /// ```
236    /// use cidre::{sc, cm, cv};
237    ///
238    /// let mut cfg = sc::StreamCfg::new();
239    ///
240    /// cfg.set_width(200);
241    /// assert_eq!(200, cfg.width());
242    /// cfg.set_height(300);
243    /// assert_eq!(300, cfg.height());
244    ///
245    /// cfg.set_minimum_frame_interval(cm::Time::new(1, 60));
246    /// cfg.set_pixel_format(cv::PixelFormat::_32_BGRA);
247    /// cfg.set_scales_to_fit(false);
248    /// cfg.set_shows_cursor(false);
249    ///
250    /// ```
251    #[objc::msg_send(width)]
252    #[api::available(macos = 13.0, maccatalyst = 18.2, ios = 27.0, tvos = 27.0)]
253    pub fn width(&self) -> usize;
254
255    /// Sets the output width in pixels.
256    #[objc::msg_send(setWidth:)]
257    #[api::available(macos = 13.0, maccatalyst = 18.2, ios = 27.0, tvos = 27.0)]
258    pub fn set_width(&mut self, val: usize);
259
260    /// The output height in pixels.
261    #[objc::msg_send(height)]
262    #[api::available(macos = 13.0, maccatalyst = 18.2, ios = 27.0, tvos = 27.0)]
263    pub fn height(&self) -> usize;
264
265    /// Sets the output height in pixels.
266    #[objc::msg_send(setHeight:)]
267    #[api::available(macos = 13.0, maccatalyst = 18.2, ios = 27.0, tvos = 27.0)]
268    pub fn set_height(&mut self, val: usize);
269
270    #[objc::msg_send(minimumFrameInterval)]
271    #[api::available(macos = 12.3, maccatalyst = 18.2)]
272    pub fn minimum_frame_interval(&self) -> cm::Time;
273
274    #[objc::msg_send(setMinimumFrameInterval:)]
275    #[api::available(macos = 12.3, maccatalyst = 18.2)]
276    pub fn set_minimum_frame_interval(&mut self, val: cm::Time);
277
278    /// 'BGRA': Packed Little Endian ARGB8888
279    /// 'l10r': Packed Little Endian ARGB2101010
280    /// '420v': 2-plane "video" range YCbCr 4:2:0
281    /// '420f': 2-plane "full" range YCbCr 4:2:0
282    /// Since macos 15.0
283    /// 'xf44': 2 plane "full" range YCbCr10 4:4:4
284    /// 'RGhA': 64 bit RGBA IEEE half-precision float, 16-bit little-endian
285    #[objc::msg_send(pixelFormat)]
286    #[api::available(macos = 12.3, maccatalyst = 18.2)]
287    pub fn pixel_format(&self) -> cv::PixelFormat;
288
289    #[objc::msg_send(setPixelFormat:)]
290    #[api::available(macos = 12.3, maccatalyst = 18.2)]
291    pub fn set_pixel_format(&mut self, val: cv::PixelFormat);
292
293    #[objc::msg_send(scalesToFit)]
294    #[api::available(macos = 12.3, maccatalyst = 18.2)]
295    pub fn scales_to_fit(&self) -> bool;
296
297    #[objc::msg_send(setScalesToFit:)]
298    #[api::available(macos = 12.3, maccatalyst = 18.2)]
299    pub fn set_scales_to_fit(&mut self, val: bool);
300
301    #[objc::msg_send(preservesAspectRatio)]
302    #[api::available(macos = 14.0, maccatalyst = 18.2)]
303    pub fn preserves_aspect_ratio(&self) -> bool;
304
305    #[objc::msg_send(setPreservesAspectRatio:)]
306    #[api::available(macos = 14.0, maccatalyst = 18.2)]
307    pub fn set_preserves_aspect_ratio(&self, val: bool) -> bool;
308
309    #[objc::msg_send(streamName)]
310    #[api::available(macos = 14.0, maccatalyst = 18.2)]
311    pub fn stream_name(&self) -> Option<arc::R<ns::String>>;
312
313    #[objc::msg_send(setStreamName:)]
314    #[api::available(macos = 14.0, maccatalyst = 18.2)]
315    pub fn set_stream_name(&mut self, val: Option<&ns::String>);
316
317    #[objc::msg_send(showsCursor)]
318    #[api::available(macos = 12.3, maccatalyst = 18.2)]
319    pub fn shows_cursor(&self) -> bool;
320
321    #[objc::msg_send(setShowsCursor:)]
322    #[api::available(macos = 12.3, maccatalyst = 18.2)]
323    pub fn set_shows_cursor(&mut self, val: bool);
324
325    /// SCStreamProperty that specifies whether to draw a circle around the cursor
326    /// click, default is false. This property will not be affected by shows_cursor.
327    /// This property currently applies when pixelFormat is set to BGRA.
328    #[objc::msg_send(showMouseClicks)]
329    #[api::available(macos = 15.0, maccatalyst = 18.2)]
330    pub fn show_mouse_clicks(&self) -> bool;
331
332    /// SCStreamProperty that specifies whether to draw a circle around the cursor
333    /// click, default is false. This property will not be affected by shows_cursor.
334    /// This property currently applies when pixelFormat is set to BGRA.
335    #[objc::msg_send(setShowMouseClicks:)]
336    #[api::available(macos = 15.0, maccatalyst = 18.2)]
337    pub fn set_show_mouse_clicks(&mut self, val: bool);
338
339    #[objc::msg_send(backgroundColor)]
340    #[api::available(macos = 12.3, maccatalyst = 18.2)]
341    pub fn background_color(&self) -> &cg::Color;
342
343    #[objc::msg_send(setBackgroundColor:)]
344    #[api::available(macos = 12.3, maccatalyst = 18.2)]
345    pub fn set_background_color(&mut self, val: &cg::Color);
346
347    #[objc::msg_send(sourceRect)]
348    #[api::available(macos = 12.3, maccatalyst = 18.2)]
349    pub fn src_rect(&self) -> cg::Rect;
350
351    #[objc::msg_send(setSourceRect:)]
352    #[api::available(macos = 12.3, maccatalyst = 18.2)]
353    pub fn set_src_rect(&mut self, val: cg::Rect);
354
355    #[objc::msg_send(destinationRect)]
356    #[api::available(macos = 12.3, maccatalyst = 18.2)]
357    pub fn dst_rect(&self) -> cg::Rect;
358
359    #[objc::msg_send(setDestinationRect:)]
360    #[api::available(macos = 12.3, maccatalyst = 18.2)]
361    pub fn set_dst_rect(&self, val: cg::Rect);
362
363    #[objc::msg_send(queueDepth)]
364    #[api::available(macos = 12.3, maccatalyst = 18.2)]
365    pub fn queue_depth(&self) -> isize;
366
367    #[objc::msg_send(setQueueDepth:)]
368    #[api::available(macos = 12.3, maccatalyst = 18.2)]
369    pub fn set_queue_depth(&mut self, val: isize);
370
371    /// Specifies the YCbCr matrix applied to the output surface.
372    /// The value must be one of the strings specified
373    /// [in](https://developer.apple.com/documentation/coregraphics/quartz_display_services/display_stream_ycbcr_to_rgb_conversion_matrix_options)
374    /// Should only be used if your pixel format is 420v or 420f.
375    #[objc::msg_send(colorMatrix)]
376    #[api::available(macos = 12.3, maccatalyst = 18.2)]
377    pub fn color_matrix(&self) -> &cf::String;
378
379    #[objc::msg_send(setColorMatrix:)]
380    #[api::available(macos = 12.3, maccatalyst = 18.2)]
381    pub fn set_color_matrix(&self, val: &cf::String);
382
383    /// The color space of the output buffer.  If not set the output buffer uses the same color
384    /// space as the display. The value must be one of the strings specified
385    /// [in](https://developer.apple.com/documentation/coregraphics/cgcolorspace/color_space_names)
386    #[objc::msg_send(colorSpaceName)]
387    #[api::available(macos = 12.3, maccatalyst = 18.2)]
388    pub fn color_space_name(&self) -> &cf::String;
389
390    #[objc::msg_send(setColorSpaceName:)]
391    #[api::available(macos = 12.3, maccatalyst = 18.2)]
392    pub fn set_color_space_name(&mut self, val: &cf::String);
393
394    /// Specifies whether the audio will be captured.  By default audio is not captured.
395    #[objc::msg_send(capturesAudio)]
396    #[api::available(
397        macos = 13.0,
398        maccatalyst = 18.2,
399        ios = 27.0,
400        visionos = 27.0,
401        tvos = 27.0
402    )]
403    pub fn captures_audio(&self) -> bool;
404
405    #[objc::msg_send(setCapturesAudio:)]
406    #[api::available(
407        macos = 13.0,
408        maccatalyst = 18.2,
409        ios = 27.0,
410        visionos = 27.0,
411        tvos = 27.0
412    )]
413    pub fn set_captures_audio(&mut self, val: bool);
414
415    /// The sample rate for audio. Default is set to 48000.
416    #[objc::msg_send(sampleRate)]
417    #[api::available(
418        macos = 13.0,
419        maccatalyst = 18.2,
420        ios = 27.0,
421        visionos = 27.0,
422        tvos = 27.0
423    )]
424    pub fn sample_rate(&self) -> i64;
425
426    #[objc::msg_send(setSampleRate:)]
427    #[api::available(
428        macos = 13.0,
429        maccatalyst = 18.2,
430        ios = 27.0,
431        visionos = 27.0,
432        tvos = 27.0
433    )]
434    pub fn set_sample_rate(&mut self, val: i64);
435
436    /// Channel count. Default is set to two.
437    #[objc::msg_send(channelCount)]
438    #[api::available(
439        macos = 13.0,
440        maccatalyst = 18.2,
441        ios = 27.0,
442        visionos = 27.0,
443        tvos = 27.0
444    )]
445    pub fn channel_count(&self) -> i64;
446
447    #[objc::msg_send(setChannelCount:)]
448    #[api::available(
449        macos = 13.0,
450        maccatalyst = 18.2,
451        ios = 27.0,
452        visionos = 27.0,
453        tvos = 27.0
454    )]
455    pub fn set_channel_count(&mut self, val: i64);
456
457    /// Whether to exclude audio from current process. Default is set to false.
458    #[objc::msg_send(excludesCurrentProcessAudio)]
459    #[api::available(
460        macos = 13.0,
461        maccatalyst = 18.2,
462        ios = 27.0,
463        visionos = 27.0,
464        tvos = 27.0
465    )]
466    pub fn excludes_current_process_audio(&self) -> bool;
467
468    #[objc::msg_send(setExcludesCurrentProcessAudio:)]
469    #[api::available(
470        macos = 13.0,
471        maccatalyst = 18.2,
472        ios = 27.0,
473        visionos = 27.0,
474        tvos = 27.0
475    )]
476    pub fn set_excludes_current_process_audio(&mut self, val: bool);
477
478    /// Ignore framing on windows in the display sharing case (will ignore shadows).
479    #[objc::msg_send(ignoreShadowsDisplay)]
480    #[api::available(macos = 14.0, maccatalyst = 18.2)]
481    pub fn ignore_shadows_display(&self) -> bool;
482
483    #[objc::msg_send(setIgnoreShadowsDisplay:)]
484    #[api::available(macos = 14.0, maccatalyst = 18.2)]
485    pub fn set_ignore_shadows_display(&mut self, val: bool);
486
487    /// Ignore framing on windows in the single window sharing case (will ignore shadows).
488    #[objc::msg_send(ignoreShadowsSingleWindow)]
489    #[api::available(macos = 14.0, maccatalyst = 18.2)]
490    pub fn ignore_shadows_single_window(&self) -> bool;
491
492    #[objc::msg_send(setIgnoreShadowsSingleWindow:)]
493    #[api::available(macos = 14.0, maccatalyst = 18.2)]
494    pub fn set_ignore_shadows_single_window(&mut self, val: bool);
495
496    #[objc::msg_send(captureResolution)]
497    #[api::available(macos = 14.0, maccatalyst = 18.2)]
498    pub fn capture_resolution(&self) -> sc::CaptureResolution;
499
500    #[objc::msg_send(setCaptureResolution:)]
501    #[api::available(macos = 14.0, maccatalyst = 18.2)]
502    pub fn set_capture_resolution(&mut self, val: sc::CaptureResolution);
503
504    #[objc::msg_send(capturesShadowsOnly)]
505    #[api::available(macos = 14.0, maccatalyst = 18.2)]
506    pub fn captures_shadows_only(&self) -> bool;
507
508    #[objc::msg_send(setCapturesShadowsOnly:)]
509    #[api::available(macos = 14.0, maccatalyst = 18.2)]
510    pub fn set_captures_shadows_only(&mut self, val: bool);
511
512    /// Ensure partially transparent areas on windows are backed by
513    /// a solid white color so that the resulting image is fully opaque.
514    #[objc::msg_send(shouldBeOpaque)]
515    #[api::available(macos = 14.0, maccatalyst = 18.2)]
516    pub fn should_be_opaque(&self) -> bool;
517
518    #[objc::msg_send(setShouldBeOpaque:)]
519    #[api::available(macos = 14.0, maccatalyst = 18.2)]
520    pub fn set_should_be_opaque(&mut self, val: bool);
521
522    #[objc::msg_send(ignoreGlobalClipDisplay)]
523    #[api::available(macos = 14.0, maccatalyst = 18.2)]
524    pub fn ignore_global_clip_display(&self) -> bool;
525
526    #[objc::msg_send(setIgnoreGlobalClipDisplay:)]
527    #[api::available(macos = 14.0, maccatalyst = 18.2)]
528    pub fn set_ignore_global_clip_display(&mut self, val: bool);
529
530    /// Ignore framing on windows in the single window sharing case (will ignore shadows).
531    #[objc::msg_send(ignoreGlobalClipSingleWindow)]
532    #[api::available(macos = 14.0, maccatalyst = 18.2)]
533    pub fn ignore_global_clip_single_window(&self) -> bool;
534
535    #[objc::msg_send(setIgnoreGlobalClipSingleWindow:)]
536    #[api::available(macos = 14.0, maccatalyst = 18.2)]
537    pub fn set_ignore_global_clip_single_window(&mut self, val: bool);
538
539    /// Informs the system if a privacy alert should be shown when using presenter overlay
540    /// for a stream. Defaults to 'sc::PresenterOverlayAlertSetting::System';
541    #[objc::msg_send(presenterOverlayPrivacyAlertSetting)]
542    #[api::available(macos = 14.0, maccatalyst = 18.2)]
543    pub fn presenter_overlay_privacy_alert_setting(&self) -> PresenterOverlayAlertSetting;
544
545    #[objc::msg_send(setPresenterOverlayPrivacyAlertSetting:)]
546    #[api::available(macos = 14.0, maccatalyst = 18.2)]
547    pub fn set_presenter_overlay_privacy_alert_setting(
548        &mut self,
549        val: PresenterOverlayAlertSetting,
550    );
551
552    /// Show the child windows in display bound windows and applications sharing.
553    /// Child windows are included by default.
554    #[objc::msg_send(includeChildWindows)]
555    #[api::available(macos = 14.2, maccatalyst = 18.2)]
556    pub fn include_child_windows(&self) -> bool;
557
558    #[objc::msg_send(setIncludeChildWindows:)]
559    #[api::available(macos = 14.2, maccatalyst = 18.2)]
560    pub fn set_include_child_windows(&mut self, val: bool);
561
562    #[objc::msg_send(captureMicrophone)]
563    #[api::available(macos = 15.0, maccatalyst = 18.2)]
564    pub fn capture_mic(&self) -> bool;
565
566    #[objc::msg_send(setCaptureMicrophone:)]
567    #[api::available(macos = 15.0, maccatalyst = 18.2)]
568    pub fn set_capture_mic(&mut self, val: bool);
569
570    #[objc::msg_send(microphoneCaptureDeviceID)]
571    #[api::available(macos = 15.0)]
572    pub fn mic_capture_device_id(&self) -> Option<arc::R<ns::String>>;
573
574    #[objc::msg_send(setMicrophoneCaptureDeviceID:)]
575    #[api::available(macos = 15.0)]
576    pub fn set_mic_capture_device_id(&mut self, val: Option<&ns::String>);
577
578    /// The dynamic range used for captured video content.
579    #[objc::msg_send(captureDynamicRange)]
580    #[api::available(macos = 15.0, maccatalyst = 18.2, ios = 27.0)]
581    pub fn capture_dynamic_range(&self) -> CaptureDynamicRange;
582
583    /// Sets the dynamic range used for captured video content.
584    #[objc::msg_send(setCaptureDynamicRange:)]
585    #[api::available(macos = 15.0, maccatalyst = 18.2, ios = 27.0)]
586    pub fn set_capture_dynamic_range(&mut self, val: CaptureDynamicRange);
587
588    /// Creates a stream configuration using one of ScreenCaptureKit's presets.
589    #[objc::msg_send(streamConfigurationWithPreset:)]
590    #[api::available(macos = 15.0, maccatalyst = 18.2, ios = 27.0)]
591    pub fn with_preset(preset: CfgPreset) -> arc::R<Self>;
592}
593
594unsafe extern "C" {
595    static SC_STREAM_CONFIGURATION: &'static objc::Class<Cfg>;
596    static SC_CONTENT_FILTER: &'static objc::Class<ContentFilter>;
597    static SC_STREAM: &'static objc::Class<Stream>;
598}
599
600#[api::weak]
601unsafe extern "C" {
602    #[api::available(
603        macos = 12.3,
604        maccatalyst = 18.2,
605        ios = 27.0,
606        visionos = 27.0,
607        tvos = 27.0
608    )]
609    static SCStreamFrameInfoStatus: &'static FrameInfo;
610    #[api::available(
611        macos = 12.3,
612        maccatalyst = 18.2,
613        ios = 27.0,
614        visionos = 27.0,
615        tvos = 27.0
616    )]
617    static SCStreamFrameInfoDisplayTime: &'static FrameInfo;
618    #[api::available(
619        macos = 12.3,
620        maccatalyst = 18.2,
621        ios = 27.0,
622        visionos = 27.0,
623        tvos = 27.0
624    )]
625    static SCStreamFrameInfoScaleFactor: &'static FrameInfo;
626    #[api::available(
627        macos = 12.3,
628        maccatalyst = 18.2,
629        ios = 27.0,
630        visionos = 27.0,
631        tvos = 27.0
632    )]
633    static SCStreamFrameInfoContentScale: &'static FrameInfo;
634    #[api::available(
635        macos = 12.3,
636        maccatalyst = 18.2,
637        ios = 27.0,
638        visionos = 27.0,
639        tvos = 27.0
640    )]
641    static SCStreamFrameInfoContentRect: &'static FrameInfo;
642    #[api::available(macos = 12.3, maccatalyst = 18.2)]
643    static SCStreamFrameInfoDirtyRects: &'static FrameInfo;
644    #[api::available(
645        macos = 13.1,
646        maccatalyst = 18.2,
647        ios = 27.0,
648        visionos = 27.0,
649        tvos = 27.0
650    )]
651    static SCStreamFrameInfoScreenRect: &'static FrameInfo;
652    #[api::available(macos = 14.0, maccatalyst = 18.2)]
653    static SCStreamFrameInfoBoundingRect: &'static FrameInfo;
654    #[api::available(macos = 14.2, maccatalyst = 18.2)]
655    static SCStreamFrameInfoPresenterOverlayContentRect: &'static FrameInfo;
656    #[api::available(macos = 27.0, maccatalyst = 27.0, ios = 27.0, visionos = 27.0)]
657    static SCStreamFrameInfoVideoOrientation: &'static FrameInfo;
658}
659
660#[doc(alias = "SCCaptureDynamicRange")]
661#[derive(Debug, Copy, Clone, Eq, PartialEq)]
662#[repr(isize)]
663pub enum CaptureDynamicRange {
664    /// Standard dynamic range capture.
665    #[doc(alias = "SCCaptureDynamicRangeSDR")]
666    Sdr,
667    /// High dynamic range capture for the local display.
668    #[doc(alias = "SCCaptureDynamicRangeHDRLocalDisplay")]
669    HdrLocalDisplay,
670    /// High dynamic range capture for a canonical display.
671    #[doc(alias = "SCCaptureDynamicRangeHDRCanonicalDisplay")]
672    HdrCanonicalDisplay,
673}
674
675define_obj_type!(
676    #[doc(alias = "SCContentFilter")]
677    pub ContentFilter(ns::Id)
678);
679
680impl arc::A<ContentFilter> {
681    #[objc::msg_send(initWithDesktopIndependentWindow:)]
682    #[api::available(macos = 12.3, maccatalyst = 18.2)]
683    pub fn init_with_desktop_independent_window(
684        self,
685        window: &sc::Window,
686    ) -> arc::Retained<ContentFilter>;
687
688    #[objc::msg_send(initWithDisplay:excludingWindows:)]
689    #[api::available(macos = 12.3, maccatalyst = 18.2)]
690    pub fn init_with_display_excluding_windows(
691        self,
692        display: &sc::Display,
693        windows: &ns::Array<sc::Window>,
694    ) -> arc::Retained<ContentFilter>;
695
696    /// Creates a content filter that includes only the specified windows on the given display.
697    ///
698    /// Use this initializer to capture specific windows from a display. Unlike
699    /// `init_with_desktop_independent_window`, this method works reliably for all
700    /// window types including regular application windows.
701    #[objc::msg_send(initWithDisplay:includingWindows:)]
702    #[api::available(macos = 12.3, maccatalyst = 18.2)]
703    pub fn init_with_display_including_windows(
704        self,
705        display: &sc::Display,
706        windows: &ns::Array<sc::Window>,
707    ) -> arc::Retained<ContentFilter>;
708
709    /// Creates a content filter that includes content from the specified applications,
710    /// optionally excluding specific windows.
711    #[objc::msg_send(initWithDisplay:includingApplications:exceptingWindows:)]
712    #[api::available(macos = 12.3, maccatalyst = 18.2)]
713    pub fn init_with_display_including_apps_excepting_windows(
714        self,
715        display: &sc::Display,
716        apps: &ns::Array<sc::RunningApp>,
717        excepting_windows: &ns::Array<sc::Window>,
718    ) -> arc::Retained<ContentFilter>;
719
720    /// Creates a content filter that excludes content from the specified applications,
721    /// optionally excepting specific windows that should still be included.
722    #[objc::msg_send(initWithDisplay:excludingApplications:exceptingWindows:)]
723    #[api::available(macos = 12.3, maccatalyst = 18.2)]
724    pub fn init_with_display_excluding_apps_excepting_windows(
725        self,
726        display: &sc::Display,
727        apps: &ns::Array<sc::RunningApp>,
728        excepting_windows: &ns::Array<sc::Window>,
729    ) -> arc::Retained<ContentFilter>;
730}
731
732impl ContentFilter {
733    define_cls!(SC_CONTENT_FILTER);
734
735    /// Creates a content filter that captures just the independent window passed in.
736    ///
737    /// Note: This is intended for "desktop independent" windows like overlays or HUDs.
738    /// For regular application windows, use `with_display_including_windows` instead.
739    #[api::available(macos = 12.3, maccatalyst = 18.2)]
740    pub fn with_desktop_independent_window(window: &sc::Window) -> arc::R<ContentFilter> {
741        Self::alloc().init_with_desktop_independent_window(window)
742    }
743
744    /// Creates a content filter for a display, excluding the specified windows.
745    #[api::available(macos = 12.3, maccatalyst = 18.2)]
746    pub fn with_display_excluding_windows(
747        display: &sc::Display,
748        windows: &ns::Array<sc::Window>,
749    ) -> arc::R<Self> {
750        Self::alloc().init_with_display_excluding_windows(display, windows)
751    }
752
753    /// Creates a content filter that includes only the specified windows on the given display.
754    ///
755    /// This is the recommended method for capturing specific application windows.
756    /// Unlike `with_desktop_independent_window`, this works reliably for all window types.
757    ///
758    /// # Example
759    /// ```no_run
760    /// use cidre::{arc, ns, sc};
761    ///
762    /// async fn capture_window() {
763    ///     let content = sc::ShareableContent::current().await.unwrap();
764    ///     let displays = content.displays();
765    ///     let windows = content.windows();
766    ///
767    ///     if let (Some(display), Some(window)) = (displays.first(), windows.first()) {
768    ///         let window_array = ns::Array::from_slice_retained(&[window.retained()]);
769    ///         let filter = sc::ContentFilter::with_display_including_windows(display, &window_array);
770    ///         // Use filter with SCStream or SCScreenshotManager
771    ///     }
772    /// }
773    /// ```
774    #[api::available(macos = 12.3, maccatalyst = 18.2)]
775    pub fn with_display_including_windows(
776        display: &sc::Display,
777        windows: &ns::Array<sc::Window>,
778    ) -> arc::R<Self> {
779        Self::alloc().init_with_display_including_windows(display, windows)
780    }
781
782    /// Creates a content filter that includes content from the specified applications,
783    /// optionally excluding specific windows.
784    ///
785    /// Use this to capture all windows from specific applications while optionally
786    /// excluding certain windows.
787    #[api::available(macos = 12.3, maccatalyst = 18.2)]
788    pub fn with_display_including_apps_excepting_windows(
789        display: &sc::Display,
790        apps: &ns::Array<sc::RunningApp>,
791        excepting_windows: &ns::Array<sc::Window>,
792    ) -> arc::R<Self> {
793        Self::alloc().init_with_display_including_apps_excepting_windows(
794            display,
795            apps,
796            excepting_windows,
797        )
798    }
799
800    /// Creates a content filter that excludes content from the specified applications,
801    /// optionally excepting specific windows that should still be included.
802    ///
803    /// Use this to capture a display while hiding content from certain applications.
804    #[api::available(macos = 12.3, maccatalyst = 18.2)]
805    pub fn with_display_excluding_apps_excepting_windows(
806        display: &sc::Display,
807        apps: &ns::Array<sc::RunningApp>,
808        excepting_windows: &ns::Array<sc::Window>,
809    ) -> arc::R<Self> {
810        Self::alloc().init_with_display_excluding_apps_excepting_windows(
811            display,
812            apps,
813            excepting_windows,
814        )
815    }
816
817    #[objc::msg_send(style)]
818    #[api::available(
819        macos = 14.0,
820        maccatalyst = 18.2,
821        ios = 27.0,
822        visionos = 27.0,
823        tvos = 27.0
824    )]
825    pub fn style(&self) -> sc::ShareableContentStyle;
826
827    #[objc::msg_send(pointPixelScale)]
828    #[api::available(
829        macos = 14.0,
830        maccatalyst = 18.2,
831        ios = 27.0,
832        visionos = 27.0,
833        tvos = 27.0
834    )]
835    pub fn point_pixel_scale(&self) -> f32;
836
837    #[objc::msg_send(contentRect)]
838    #[api::available(
839        macos = 14.0,
840        maccatalyst = 18.2,
841        ios = 27.0,
842        visionos = 27.0,
843        tvos = 27.0
844    )]
845    pub fn content_rect(&self) -> cg::Rect;
846
847    #[objc::msg_send(includeMenuBar)]
848    #[api::available(macos = 14.2, maccatalyst = 18.2)]
849    pub fn include_menu_bar(&self) -> bool;
850
851    /// To include menu bar as part of the capture. This property has no effect for the
852    /// desktop independent window filter. For content filters created with initWithDisplay:excluding,
853    /// the default value is 'true'. Display excluding content filters contains the desktop
854    /// and dock. For content filters created with initWithDisplay:including, the default
855    /// value is 'false'. Display including content filters do not contain the desktop and dock
856    #[objc::msg_send(setIncludeMenuBar:)]
857    #[api::available(macos = 14.2, maccatalyst = 18.2)]
858    pub fn set_include_menu_bar(&mut self, val: bool);
859
860    /// sc::Displays that are included in the content filter
861    #[objc::msg_send(includedDisplays)]
862    #[api::available(macos = 15.2, maccatalyst = 18.2)]
863    pub fn included_displays(&self) -> arc::R<ns::Array<sc::Display>>;
864
865    /// Applications that are included in the content filter
866    #[objc::msg_send(includedApplications)]
867    #[api::available(macos = 15.2, maccatalyst = 18.2)]
868    pub fn included_apps(&self) -> arc::R<ns::Array<sc::RunningApp>>;
869
870    /// Windows that are included in the content filter
871    #[objc::msg_send(includedWindows)]
872    #[api::available(macos = 15.2, maccatalyst = 18.2)]
873    pub fn included_windows(&self) -> arc::R<ns::Array<sc::Window>>;
874
875    #[objc::msg_send(isMicrophoneEnabled)]
876    #[api::available(macos = 27.0, maccatalyst = 27.0, ios = 27.0, visionos = 27.0)]
877    pub fn is_mic_enabled(&self) -> bool;
878
879    #[objc::msg_send(isCameraEnabled)]
880    #[api::available(macos = 27.0, maccatalyst = 27.0, ios = 27.0)]
881    pub fn is_camera_enabled(&self) -> bool;
882}
883
884define_obj_type!(
885    /// An object that represents a stream of shareable content.
886    #[doc(alias = "SCStream")]
887    pub Stream(ns::Id)
888);
889
890unsafe impl Send for Stream {}
891unsafe impl Sync for Stream {}
892
893/// Receives sample buffers produced by a stream.
894#[objc::protocol(SCStreamOutput)]
895pub trait Output: objc::Obj {
896    /// Called when the stream outputs a sample buffer of the requested type.
897    #[objc::optional]
898    #[objc::msg_send(stream:didOutputSampleBuffer:ofType:)]
899    fn stream_did_output_sample_buf(
900        &mut self,
901        stream: &Stream,
902        sample_buf: &mut cm::SampleBuf,
903        kind: OutputType,
904    );
905}
906
907/// Receives stream lifecycle callbacks.
908#[objc::protocol(SCStreamDelegate)]
909pub trait Delegate: objc::Obj {
910    /// Called when a stream stops because of an error.
911    #[objc::optional]
912    #[objc::msg_send(stream:didStopWithError:)]
913    fn stream_did_stop_with_err(&mut self, stream: &Stream, error: &ns::Error);
914
915    /// Called when the user stops stream sharing from system UI.
916    #[objc::optional]
917    #[objc::msg_send(userDidStopStream:)]
918    fn user_did_stop_stream(&mut self, stream: &Stream);
919
920    /// Called when a video effect output starts.
921    #[objc::optional]
922    #[objc::msg_send(outputVideoEffectDidStartForStream:)]
923    fn output_video_effect_did_start_for_stream(&mut self, stream: &Stream);
924
925    /// Called when a video effect output stops.
926    #[objc::optional]
927    #[objc::msg_send(outputVideoEffectDidStopForStream:)]
928    fn output_video_effect_did_stop_for_stream(&mut self, stream: &Stream);
929
930    /// Called when the stream becomes active.
931    #[objc::optional]
932    #[objc::msg_send(streamDidBecomeActive:)]
933    fn stream_did_become_active(&mut self, stream: &Stream);
934
935    /// Called when the stream becomes inactive.
936    #[objc::optional]
937    #[objc::msg_send(streamDidBecomeInactive:)]
938    fn stream_did_become_inactive(&mut self, stream: &Stream);
939}
940
941define_obj_type!(pub AnyDelegate(ns::Id));
942
943impl Delegate for AnyDelegate {}
944
945impl arc::A<Stream> {
946    /// Initializes a stream with a content filter, configuration, and optional delegate.
947    #[objc::msg_send(initWithFilter:configuration:delegate:)]
948    pub fn init_with_filter_configuration_delegate<D: Delegate>(
949        self,
950        filter: &ContentFilter,
951        configuration: &Cfg,
952        delegate: Option<&D>,
953    ) -> arc::Retained<Stream>;
954}
955
956impl Stream {
957    define_cls!(SC_STREAM);
958
959    /// Creates a stream with a delegate.
960    pub fn with_delegate<D: Delegate>(
961        filter: &ContentFilter,
962        configuration: &Cfg,
963        delegate: &D,
964    ) -> arc::R<Self> {
965        Self::alloc().init_with_filter_configuration_delegate(filter, configuration, Some(delegate))
966    }
967
968    /// Creates a stream without a delegate.
969    pub fn new(filter: &ContentFilter, configuration: &Cfg) -> arc::R<Self> {
970        Self::alloc().init_with_filter_configuration_delegate::<AnyDelegate>(
971            filter,
972            configuration,
973            None,
974        )
975    }
976
977    /// The synchronization clock used by stream sample buffers.
978    #[objc::msg_send(synchronizationClock)]
979    #[api::available(
980        macos = 13.0,
981        maccatalyst = 18.2,
982        ios = 27.0,
983        visionos = 27.0,
984        tvos = 27.0
985    )]
986    pub fn synchronization_clock(&self) -> Option<&cm::Clock>;
987
988    /// Whether the stream is currently capturing.
989    #[objc::msg_send(isCapturing)]
990    #[api::available(
991        macos = 27.0,
992        maccatalyst = 27.0,
993        ios = 27.0,
994        visionos = 27.0,
995        tvos = 27.0
996    )]
997    pub fn is_capturing(&self) -> bool;
998
999    #[objc::msg_send(addStreamOutput:type:sampleHandlerQueue:error:)]
1000    unsafe fn add_stream_output_type_sample_handler_queue_err<D: Output>(
1001        &self,
1002        output: &D,
1003        output_type: OutputType,
1004        queue: Option<&dispatch::Queue>,
1005        error: *mut Option<&ns::Error>,
1006    ) -> bool;
1007
1008    /// Adds a stream output for the requested output type.
1009    pub fn add_stream_output<'ear, D: Output>(
1010        &self,
1011        output: &D,
1012        output_type: OutputType,
1013        queue: Option<&dispatch::Queue>,
1014    ) -> ns::Result<'ear> {
1015        ns::if_false(|err| unsafe {
1016            self.add_stream_output_type_sample_handler_queue_err(output, output_type, queue, err)
1017        })
1018    }
1019
1020    #[objc::msg_send(removeStreamOutput:type:error:)]
1021    unsafe fn remove_stream_output_err<'ear, D: Output>(
1022        &self,
1023        output: &D,
1024        output_type: OutputType,
1025        error: *mut Option<&'ear ns::Error>,
1026    ) -> bool;
1027
1028    /// Removes a stream output for the requested output type.
1029    pub fn remove_stream_output<'ear, D: Output>(
1030        &self,
1031        output: &D,
1032        output_type: OutputType,
1033    ) -> ns::Result<'ear> {
1034        ns::if_false(|err| unsafe { self.remove_stream_output_err(output, output_type, err) })
1035    }
1036
1037    #[cfg(feature = "blocks")]
1038    #[objc::msg_send(updateContentFilter:completionHandler:)]
1039    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1040    pub fn update_content_filter_ch_block(
1041        &self,
1042        filter: &ContentFilter,
1043        ch: Option<&mut blocks::ErrCh>,
1044    );
1045
1046    #[cfg(feature = "blocks")]
1047    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1048    pub fn update_content_filter_ch(
1049        &self,
1050        filter: &ContentFilter,
1051        ch: impl FnMut(Option<&ns::Error>) + 'static,
1052    ) {
1053        let mut block = blocks::ErrCh::new1(ch);
1054        self.update_content_filter_ch_block(filter, Some(&mut block));
1055    }
1056
1057    #[cfg(all(feature = "blocks", feature = "async"))]
1058    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1059    pub async fn update_content_filter(
1060        &self,
1061        filter: &ContentFilter,
1062    ) -> Result<(), arc::R<ns::Error>> {
1063        let (future, mut block) = blocks::ok();
1064        self.update_content_filter_ch_block(filter, Some(&mut block));
1065        future.await
1066    }
1067
1068    #[cfg(feature = "blocks")]
1069    #[objc::msg_send(updateConfiguration:completionHandler:)]
1070    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1071    pub fn update_cfg_ch_block(&self, cfg: &Cfg, ch: Option<&mut blocks::ErrCh>);
1072
1073    #[cfg(feature = "blocks")]
1074    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1075    pub fn update_cfg_ch(&self, cfg: &Cfg, ch: impl FnMut(Option<&ns::Error>) + 'static) {
1076        let mut block = blocks::ErrCh::new1(ch);
1077        self.update_cfg_ch_block(cfg, Some(&mut block));
1078    }
1079
1080    #[cfg(all(feature = "blocks", feature = "async"))]
1081    #[api::available(macos = 12.3, maccatalyst = 18.2)]
1082    pub async fn update_cfg(&self, cfg: &Cfg) -> Result<(), arc::R<ns::Error>> {
1083        let (future, mut block) = blocks::ok();
1084        self.update_cfg_ch_block(cfg, Some(&mut block));
1085        future.await
1086    }
1087
1088    #[cfg(feature = "blocks")]
1089    /// Starts stream capture.
1090    #[objc::msg_send(startCaptureWithCompletionHandler:)]
1091    pub fn start_with_ch_block(&self, ch: Option<&mut blocks::ErrCh>);
1092
1093    #[cfg(feature = "blocks")]
1094    /// Starts stream capture.
1095    pub fn start_with_ch(&self, ch: impl FnMut(Option<&ns::Error>) + 'static) {
1096        let mut block = blocks::ErrCh::new1(ch);
1097        self.start_with_ch_block(Some(&mut block));
1098    }
1099
1100    #[cfg(feature = "blocks")]
1101    /// Stops stream capture.
1102    #[objc::msg_send(stopCaptureWithCompletionHandler:)]
1103    pub fn stop_with_ch_block(&self, ch: Option<&mut blocks::ErrCh>);
1104
1105    #[cfg(feature = "blocks")]
1106    /// Stops stream capture.
1107    pub fn stop_with_ch(&self, ch: impl FnMut(Option<&ns::Error>) + 'static) {
1108        let mut block = blocks::ErrCh::new1(ch);
1109        self.stop_with_ch_block(Some(&mut block));
1110    }
1111
1112    #[cfg(all(feature = "blocks", feature = "async"))]
1113    /// Starts stream capture.
1114    pub async fn start(&self) -> Result<(), arc::R<ns::Error>> {
1115        let (future, mut block) = blocks::ok();
1116        self.start_with_ch_block(Some(&mut block));
1117        future.await
1118    }
1119
1120    #[cfg(all(feature = "blocks", feature = "async"))]
1121    /// Stops stream capture.
1122    pub async fn stop(&self) -> Result<(), arc::R<ns::Error>> {
1123        let (future, mut block) = blocks::ok();
1124        self.stop_with_ch_block(Some(&mut block));
1125        future.await
1126    }
1127
1128    /// Adds a recording output to the stream.
1129    #[objc::msg_send(addRecordingOutput:error:)]
1130    #[api::available(
1131        macos = 15.0,
1132        maccatalyst = 18.2,
1133        ios = 27.0,
1134        visionos = 27.0,
1135        tvos = 27.0
1136    )]
1137    pub unsafe fn add_recording_output_err<'ar>(
1138        &mut self,
1139        val: &sc::RecordingOutput,
1140        error: *mut Option<&'ar ns::Error>,
1141    ) -> bool;
1142
1143    #[api::available(
1144        macos = 15.0,
1145        maccatalyst = 18.2,
1146        ios = 27.0,
1147        visionos = 27.0,
1148        tvos = 27.0
1149    )]
1150    pub fn add_recording_output<'ear>(&mut self, val: &sc::RecordingOutput) -> ns::Result<'ear> {
1151        ns::if_false(|err| unsafe { self.add_recording_output_err(val, err) })
1152    }
1153
1154    /// Removes a recording output from the stream.
1155    #[objc::msg_send(removeRecordingOutput:error:)]
1156    #[api::available(
1157        macos = 15.0,
1158        maccatalyst = 18.2,
1159        ios = 27.0,
1160        visionos = 27.0,
1161        tvos = 27.0
1162    )]
1163    pub unsafe fn remove_recording_output_err<'ar>(
1164        &mut self,
1165        val: &sc::RecordingOutput,
1166        error: *mut Option<&'ar ns::Error>,
1167    ) -> bool;
1168
1169    #[api::available(
1170        macos = 15.0,
1171        maccatalyst = 18.2,
1172        ios = 27.0,
1173        visionos = 27.0,
1174        tvos = 27.0
1175    )]
1176    pub fn remove_recording_output<'ear>(&mut self, val: &sc::RecordingOutput) -> ns::Result<'ear> {
1177        ns::if_false(|err| unsafe { self.remove_recording_output_err(val, err) })
1178    }
1179
1180    /// Adds a clip buffering output to the stream.
1181    #[objc::msg_send(addClipBufferingOutput:error:)]
1182    #[api::available(
1183        macos = 27.0,
1184        maccatalyst = 27.0,
1185        ios = 27.0,
1186        visionos = 27.0,
1187        tvos = 27.0
1188    )]
1189    pub unsafe fn add_clip_buffering_output_err<'ar>(
1190        &mut self,
1191        val: &sc::ClipBufferingOutput,
1192        error: *mut Option<&'ar ns::Error>,
1193    ) -> bool;
1194
1195    #[api::available(
1196        macos = 27.0,
1197        maccatalyst = 27.0,
1198        ios = 27.0,
1199        visionos = 27.0,
1200        tvos = 27.0
1201    )]
1202    pub fn add_clip_buffering_output<'ear>(
1203        &mut self,
1204        val: &sc::ClipBufferingOutput,
1205    ) -> ns::Result<'ear> {
1206        ns::if_false(|err| unsafe { self.add_clip_buffering_output_err(val, err) })
1207    }
1208
1209    /// Removes a clip buffering output from the stream.
1210    #[objc::msg_send(removeClipBufferingOutput:error:)]
1211    #[api::available(
1212        macos = 27.0,
1213        maccatalyst = 27.0,
1214        ios = 27.0,
1215        visionos = 27.0,
1216        tvos = 27.0
1217    )]
1218    pub unsafe fn remove_clip_buffering_output_err<'ar>(
1219        &mut self,
1220        val: &sc::ClipBufferingOutput,
1221        error: *mut Option<&'ar ns::Error>,
1222    ) -> bool;
1223
1224    #[api::available(
1225        macos = 27.0,
1226        maccatalyst = 27.0,
1227        ios = 27.0,
1228        visionos = 27.0,
1229        tvos = 27.0
1230    )]
1231    pub fn remove_clip_buffering_output<'ear>(
1232        &mut self,
1233        val: &sc::ClipBufferingOutput,
1234    ) -> ns::Result<'ear> {
1235        ns::if_false(|err| unsafe { self.remove_clip_buffering_output_err(val, err) })
1236    }
1237
1238    /// Adds a video effect output to the stream.
1239    #[objc::msg_send(addVideoEffectOutput:error:)]
1240    #[api::available(ios = 27.0)]
1241    pub unsafe fn add_video_effect_output_err<'ar>(
1242        &mut self,
1243        val: &sc::VideoEffectOutput,
1244        error: *mut Option<&'ar ns::Error>,
1245    ) -> bool;
1246
1247    #[api::available(ios = 27.0)]
1248    pub fn add_video_effect_output<'ear>(
1249        &mut self,
1250        val: &sc::VideoEffectOutput,
1251    ) -> ns::Result<'ear> {
1252        ns::if_false(|err| unsafe { self.add_video_effect_output_err(val, err) })
1253    }
1254
1255    /// Removes a video effect output from the stream.
1256    #[objc::msg_send(removeVideoEffectOutput:error:)]
1257    #[api::available(ios = 27.0)]
1258    pub unsafe fn remove_video_effect_output_err<'ar>(
1259        &mut self,
1260        val: &sc::VideoEffectOutput,
1261        error: *mut Option<&'ar ns::Error>,
1262    ) -> bool;
1263
1264    #[api::available(ios = 27.0)]
1265    pub fn remove_video_effect_output<'ear>(
1266        &mut self,
1267        val: &sc::VideoEffectOutput,
1268    ) -> ns::Result<'ear> {
1269        ns::if_false(|err| unsafe { self.remove_video_effect_output_err(val, err) })
1270    }
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275    use std::time::Duration;
1276
1277    use crate::{
1278        cm, cv, define_obj_type, dispatch, ns, objc, sc, sc::stream::Output, sc::stream::OutputImpl,
1279    };
1280
1281    define_obj_type!(
1282        FrameCounter + sc::stream::OutputImpl,
1283        usize,
1284        FRAME_COUNTER_CLS
1285    );
1286
1287    impl Output for FrameCounter {}
1288
1289    #[objc::add_methods]
1290    impl OutputImpl for FrameCounter {}
1291
1292    #[test]
1293    fn basics() {
1294        let mut cfg = sc::StreamCfg::new();
1295
1296        cfg.set_width(200);
1297        assert_eq!(200, cfg.width());
1298        cfg.set_height(300);
1299        assert_eq!(300, cfg.height());
1300
1301        cfg.set_minimum_frame_interval(cm::Time::new(1, 60));
1302        cfg.set_pixel_format(cv::PixelFormat::_32_BGRA);
1303        cfg.set_scales_to_fit(false);
1304        cfg.set_shows_cursor(true);
1305    }
1306
1307    #[tokio::test]
1308    async fn start_fails() {
1309        let q = dispatch::Queue::serial_with_ar_pool();
1310        let content = sc::ShareableContent::current().await.expect("content");
1311        let display = content.displays().get(0).unwrap();
1312        let mut cfg = sc::StreamCfg::new();
1313        cfg.set_width(display.width() as usize * 2);
1314        cfg.set_height(display.height() as usize * 2);
1315
1316        let windows = ns::Array::new();
1317        let filter = sc::ContentFilter::with_display_excluding_windows(&display, &windows);
1318        let stream = sc::Stream::new(&filter, &cfg);
1319        let delegate = FrameCounter::with(0);
1320        stream
1321            .add_stream_output(delegate.as_ref(), sc::OutputType::Screen, Some(&q))
1322            .unwrap();
1323        stream.start().await.expect("started");
1324        stream.start().await.expect_err("already started");
1325
1326        stream
1327            .update_content_filter(&filter)
1328            .await
1329            .expect("Failed to update filter");
1330        stream.update_cfg(&cfg).await.expect("Failed to update cfg");
1331
1332        tokio::time::sleep(Duration::from_secs(2)).await;
1333
1334        stream
1335            .remove_stream_output(delegate.as_ref(), sc::OutputType::Screen)
1336            .unwrap();
1337
1338        stream.stop().await.expect("stopped");
1339        stream.stop().await.expect_err("already stopped");
1340        // println!(
1341        //     "------- {:?} {:?}",
1342        //     d.obj.as_type_ref(),
1343        //     d.delegate.counter()
1344        // );
1345
1346        // assert!(d.delegate.counter() > 10, "{:?}", d.delegate.counter);
1347    }
1348
1349    #[tokio::test]
1350    async fn content_filter_with_display_including_windows() {
1351        let content = sc::ShareableContent::current().await.expect("content");
1352        let displays = content.displays();
1353        let windows = content.windows();
1354
1355        let display = displays.first().expect("at least one display");
1356
1357        // Test with empty window array
1358        let empty_windows = ns::Array::<sc::Window>::new();
1359        let filter = sc::ContentFilter::with_display_including_windows(&display, &empty_windows);
1360        // Filter should be created successfully
1361        let _ = filter.content_rect();
1362
1363        // Test with actual windows if available
1364        if let Some(window) = windows.first() {
1365            let window_array = ns::Array::from_slice_retained(&[window.retained()]);
1366            let filter = sc::ContentFilter::with_display_including_windows(&display, &window_array);
1367            // Filter should be created successfully and have valid content rect
1368            let rect = filter.content_rect();
1369            assert!(rect.size.width > 0.0 || rect.size.height > 0.0);
1370        }
1371    }
1372
1373    #[tokio::test]
1374    async fn content_filter_with_apps() {
1375        let content = sc::ShareableContent::current().await.expect("content");
1376        let displays = content.displays();
1377        let apps = content.apps();
1378
1379        let display = displays.first().expect("at least one display");
1380        let empty_windows = ns::Array::<sc::Window>::new();
1381
1382        // Test including apps
1383        if let Some(app) = apps.first() {
1384            let app_array = ns::Array::from_slice_retained(&[app.retained()]);
1385            let filter = sc::ContentFilter::with_display_including_apps_excepting_windows(
1386                &display,
1387                &app_array,
1388                &empty_windows,
1389            );
1390            // Filter should be created successfully
1391            let _ = filter.content_rect();
1392        }
1393
1394        // Test excluding apps
1395        if let Some(app) = apps.first() {
1396            let app_array = ns::Array::from_slice_retained(&[app.retained()]);
1397            let filter = sc::ContentFilter::with_display_excluding_apps_excepting_windows(
1398                &display,
1399                &app_array,
1400                &empty_windows,
1401            );
1402            // Filter should be created successfully
1403            let _ = filter.content_rect();
1404        }
1405    }
1406}