screencapturekit/stream/
content_filter.rs

1//! Content filter for `ScreenCaptureKit` streams
2//!
3//! This module provides a wrapper around `SCContentFilter` that uses the Swift bridge.
4//!
5//! # Examples
6//!
7//! ```no_run
8//! use screencapturekit::shareable_content::SCShareableContent;
9//! use screencapturekit::stream::content_filter::SCContentFilter;
10//!
11//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
12//! let content = SCShareableContent::get()?;
13//! let display = &content.displays()[0];
14//!
15//! // Capture entire display
16//! let filter = SCContentFilter::create()
17//!     .with_display(display)
18//!     .with_excluding_windows(&[])
19//!     .build();
20//! # Ok(())
21//! # }
22//! ```
23
24use std::ffi::c_void;
25use std::fmt;
26
27#[cfg(feature = "macos_14_2")]
28use crate::cg::CGRect;
29use crate::{
30    ffi,
31    shareable_content::{SCDisplay, SCRunningApplication, SCWindow},
32};
33
34/// Content filter for `ScreenCaptureKit` streams
35///
36/// Defines what content to capture (displays, windows, or applications).
37///
38/// # Examples
39///
40/// ```no_run
41/// use screencapturekit::shareable_content::SCShareableContent;
42/// use screencapturekit::stream::content_filter::SCContentFilter;
43///
44/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
45/// let content = SCShareableContent::get()?;
46/// let display = &content.displays()[0];
47///
48/// // Capture entire display
49/// let filter = SCContentFilter::create()
50///     .with_display(display)
51///     .with_excluding_windows(&[])
52///     .build();
53///
54/// // Or capture a specific window
55/// let window = &content.windows()[0];
56/// let filter = SCContentFilter::create()
57///     .with_window(window)
58///     .build();
59/// # Ok(())
60/// # }
61/// ```
62pub struct SCContentFilter(*const c_void);
63
64impl PartialEq for SCContentFilter {
65    fn eq(&self, other: &Self) -> bool {
66        self.0 == other.0
67    }
68}
69
70impl Eq for SCContentFilter {}
71
72impl std::hash::Hash for SCContentFilter {
73    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
74        self.0.hash(state);
75    }
76}
77
78// Note: We intentionally do NOT implement Default for SCContentFilter.
79// A null filter would cause panics/crashes when used with SCStream.
80// Users should always use SCContentFilter::create() to create valid filters.
81
82impl SCContentFilter {
83    /// Creates a content filter builder
84    ///
85    /// # Examples
86    ///
87    /// ```no_run
88    /// use screencapturekit::prelude::*;
89    ///
90    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
91    /// let content = SCShareableContent::get()?;
92    /// let display = &content.displays()[0];
93    ///
94    /// let filter = SCContentFilter::create()
95    ///     .with_display(display)
96    ///     .with_excluding_windows(&[])
97    ///     .build();
98    /// # Ok(())
99    /// # }
100    /// ```
101    #[must_use]
102    pub fn create() -> SCContentFilterBuilder {
103        SCContentFilterBuilder::new()
104    }
105
106    /// Creates a content filter from a picker-returned pointer
107    ///
108    /// This is used internally when the content sharing picker returns a filter.
109    #[cfg(feature = "macos_14_0")]
110    pub(crate) fn from_picker_ptr(ptr: *const c_void) -> Self {
111        Self(ptr)
112    }
113
114    /// Returns the raw pointer to the content filter
115    pub(crate) fn as_ptr(&self) -> *const c_void {
116        self.0
117    }
118
119    /// Sets the content rectangle for this filter (macOS 14.2+)
120    ///
121    /// Specifies the rectangle within the content filter to capture.
122    #[cfg(feature = "macos_14_2")]
123    #[must_use]
124    pub fn set_content_rect(self, rect: CGRect) -> Self {
125        unsafe {
126            ffi::sc_content_filter_set_content_rect(
127                self.0,
128                rect.x,
129                rect.y,
130                rect.width,
131                rect.height,
132            );
133        }
134        self
135    }
136
137    /// Gets the content rectangle for this filter (macOS 14.2+)
138    #[cfg(feature = "macos_14_2")]
139    pub fn content_rect(&self) -> CGRect {
140        unsafe {
141            let mut x = 0.0;
142            let mut y = 0.0;
143            let mut width = 0.0;
144            let mut height = 0.0;
145            ffi::sc_content_filter_get_content_rect(
146                self.0,
147                &mut x,
148                &mut y,
149                &mut width,
150                &mut height,
151            );
152            CGRect::new(x, y, width, height)
153        }
154    }
155
156    /// Get the content style (macOS 14.0+)
157    ///
158    /// Returns the type of content being captured (window, display, application, or none).
159    #[cfg(feature = "macos_14_0")]
160    pub fn style(&self) -> SCShareableContentStyle {
161        let value = unsafe { ffi::sc_content_filter_get_style(self.0) };
162        SCShareableContentStyle::from(value)
163    }
164
165    /// Get the stream type (macOS 14.0+)
166    ///
167    /// Returns whether this filter captures a window or a display.
168    #[cfg(feature = "macos_14_0")]
169    pub fn stream_type(&self) -> SCStreamType {
170        let value = unsafe { ffi::sc_content_filter_get_stream_type(self.0) };
171        SCStreamType::from(value)
172    }
173
174    /// Get the point-to-pixel scale factor (macOS 14.0+)
175    ///
176    /// Returns the scaling factor used to convert points to pixels.
177    /// Typically 2.0 for Retina displays.
178    #[cfg(feature = "macos_14_0")]
179    pub fn point_pixel_scale(&self) -> f32 {
180        unsafe { ffi::sc_content_filter_get_point_pixel_scale(self.0) }
181    }
182
183    /// Include the menu bar in capture (macOS 14.2+)
184    ///
185    /// When set to `true`, the menu bar is included in display capture.
186    /// This property has no effect for window filters.
187    #[cfg(feature = "macos_14_2")]
188    pub fn set_include_menu_bar(&mut self, include: bool) {
189        unsafe {
190            ffi::sc_content_filter_set_include_menu_bar(self.0, include);
191        }
192    }
193
194    /// Check if menu bar is included in capture (macOS 14.2+)
195    #[cfg(feature = "macos_14_2")]
196    pub fn include_menu_bar(&self) -> bool {
197        unsafe { ffi::sc_content_filter_get_include_menu_bar(self.0) }
198    }
199
200    /// Get included displays (macOS 15.2+)
201    ///
202    /// Returns the displays currently included in this filter.
203    #[cfg(feature = "macos_15_2")]
204    pub fn included_displays(&self) -> Vec<SCDisplay> {
205        let count = unsafe { ffi::sc_content_filter_get_included_displays_count(self.0) };
206        if count <= 0 {
207            return Vec::new();
208        }
209        #[allow(clippy::cast_sign_loss)]
210        (0..count as usize)
211            .filter_map(|i| {
212                #[allow(clippy::cast_possible_wrap)]
213                let ptr =
214                    unsafe { ffi::sc_content_filter_get_included_display_at(self.0, i as isize) };
215                if ptr.is_null() {
216                    None
217                } else {
218                    Some(SCDisplay::from_ffi_owned(ptr))
219                }
220            })
221            .collect()
222    }
223
224    /// Get included windows (macOS 15.2+)
225    ///
226    /// Returns the windows currently included in this filter.
227    #[cfg(feature = "macos_15_2")]
228    pub fn included_windows(&self) -> Vec<SCWindow> {
229        let count = unsafe { ffi::sc_content_filter_get_included_windows_count(self.0) };
230        if count <= 0 {
231            return Vec::new();
232        }
233        #[allow(clippy::cast_sign_loss)]
234        (0..count as usize)
235            .filter_map(|i| {
236                #[allow(clippy::cast_possible_wrap)]
237                let ptr =
238                    unsafe { ffi::sc_content_filter_get_included_window_at(self.0, i as isize) };
239                if ptr.is_null() {
240                    None
241                } else {
242                    Some(SCWindow::from_ffi_owned(ptr))
243                }
244            })
245            .collect()
246    }
247
248    /// Get included applications (macOS 15.2+)
249    ///
250    /// Returns the applications currently included in this filter.
251    #[cfg(feature = "macos_15_2")]
252    pub fn included_applications(&self) -> Vec<SCRunningApplication> {
253        let count = unsafe { ffi::sc_content_filter_get_included_applications_count(self.0) };
254        if count <= 0 {
255            return Vec::new();
256        }
257        #[allow(clippy::cast_sign_loss)]
258        (0..count as usize)
259            .filter_map(|i| {
260                #[allow(clippy::cast_possible_wrap)]
261                let ptr = unsafe {
262                    ffi::sc_content_filter_get_included_application_at(self.0, i as isize)
263                };
264                if ptr.is_null() {
265                    None
266                } else {
267                    Some(SCRunningApplication::from_ffi_owned(ptr))
268                }
269            })
270            .collect()
271    }
272}
273
274/// Content style for filters (macOS 14.0+)
275#[repr(i32)]
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
277#[cfg(feature = "macos_14_0")]
278pub enum SCShareableContentStyle {
279    /// No specific content type
280    #[default]
281    None = 0,
282    /// Window-based content
283    Window = 1,
284    /// Display-based content
285    Display = 2,
286    /// Application-based content
287    Application = 3,
288}
289
290#[cfg(feature = "macos_14_0")]
291impl From<i32> for SCShareableContentStyle {
292    fn from(value: i32) -> Self {
293        match value {
294            1 => Self::Window,
295            2 => Self::Display,
296            3 => Self::Application,
297            _ => Self::None,
298        }
299    }
300}
301
302#[cfg(feature = "macos_14_0")]
303impl std::fmt::Display for SCShareableContentStyle {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        match self {
306            Self::None => write!(f, "None"),
307            Self::Window => write!(f, "Window"),
308            Self::Display => write!(f, "Display"),
309            Self::Application => write!(f, "Application"),
310        }
311    }
312}
313
314/// Stream type for filters (macOS 14.0+)
315#[repr(i32)]
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
317#[cfg(feature = "macos_14_0")]
318pub enum SCStreamType {
319    /// Window-based stream
320    #[default]
321    Window = 0,
322    /// Display-based stream
323    Display = 1,
324}
325
326#[cfg(feature = "macos_14_0")]
327impl From<i32> for SCStreamType {
328    fn from(value: i32) -> Self {
329        match value {
330            1 => Self::Display,
331            _ => Self::Window,
332        }
333    }
334}
335
336#[cfg(feature = "macos_14_0")]
337impl std::fmt::Display for SCStreamType {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        match self {
340            Self::Window => write!(f, "Window"),
341            Self::Display => write!(f, "Display"),
342        }
343    }
344}
345
346impl Drop for SCContentFilter {
347    fn drop(&mut self) {
348        if !self.0.is_null() {
349            unsafe {
350                ffi::sc_content_filter_release(self.0);
351            }
352        }
353    }
354}
355
356impl Clone for SCContentFilter {
357    fn clone(&self) -> Self {
358        unsafe { Self(crate::ffi::sc_content_filter_retain(self.0)) }
359    }
360}
361
362impl fmt::Debug for SCContentFilter {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        f.debug_struct("SCContentFilter")
365            .field("ptr", &self.0)
366            .finish()
367    }
368}
369
370impl fmt::Display for SCContentFilter {
371    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372        write!(f, "SCContentFilter")
373    }
374}
375
376// Safety: SCContentFilter wraps an Objective-C object that is thread-safe
377// The underlying SCContentFilter object can be safely sent between threads
378unsafe impl Send for SCContentFilter {}
379unsafe impl Sync for SCContentFilter {}
380
381/// Builder for creating `SCContentFilter` instances
382///
383/// # Examples
384///
385/// ```no_run
386/// use screencapturekit::prelude::*;
387///
388/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
389/// let content = SCShareableContent::get()?;
390/// let display = &content.displays()[0];
391///
392/// // Capture entire display
393/// let filter = SCContentFilter::create()
394///     .with_display(display)
395///     .with_excluding_windows(&[])
396///     .build();
397///
398/// // Capture with specific windows excluded
399/// let window = &content.windows()[0];
400/// let filter = SCContentFilter::create()
401///     .with_display(display)
402///     .with_excluding_windows(&[window])
403///     .build();
404///
405/// // Capture specific window
406/// let filter = SCContentFilter::create()
407///     .with_window(window)
408///     .build();
409/// # Ok(())
410/// # }
411/// ```
412pub struct SCContentFilterBuilder {
413    filter_type: FilterType,
414    #[cfg(feature = "macos_14_2")]
415    content_rect: Option<CGRect>,
416}
417
418enum FilterType {
419    None,
420    Window(SCWindow),
421    DisplayExcluding {
422        display: SCDisplay,
423        windows: Vec<SCWindow>,
424    },
425    DisplayIncluding {
426        display: SCDisplay,
427        windows: Vec<SCWindow>,
428    },
429    DisplayIncludingApplications {
430        display: SCDisplay,
431        applications: Vec<SCRunningApplication>,
432        excepting_windows: Vec<SCWindow>,
433    },
434    DisplayExcludingApplications {
435        display: SCDisplay,
436        applications: Vec<SCRunningApplication>,
437        excepting_windows: Vec<SCWindow>,
438    },
439}
440
441impl SCContentFilterBuilder {
442    fn new() -> Self {
443        Self {
444            filter_type: FilterType::None,
445            #[cfg(feature = "macos_14_2")]
446            content_rect: None,
447        }
448    }
449
450    /// Set the display to capture
451    #[must_use]
452    pub fn with_display(mut self, display: &SCDisplay) -> Self {
453        self.filter_type = FilterType::DisplayExcluding {
454            display: display.clone(),
455            windows: Vec::new(),
456        };
457        self
458    }
459
460    /// Set the window to capture
461    #[must_use]
462    pub fn with_window(mut self, window: &SCWindow) -> Self {
463        self.filter_type = FilterType::Window(window.clone());
464        self
465    }
466
467    /// Exclude specific windows from the display capture
468    #[must_use]
469    pub fn with_excluding_windows(mut self, windows: &[&SCWindow]) -> Self {
470        if let FilterType::DisplayExcluding {
471            windows: ref mut excluded,
472            ..
473        } = self.filter_type
474        {
475            *excluded = windows.iter().map(|w| (*w).clone()).collect();
476        }
477        self
478    }
479
480    /// Include only specific windows in the display capture
481    #[must_use]
482    pub fn with_including_windows(mut self, windows: &[&SCWindow]) -> Self {
483        if let FilterType::DisplayExcluding { display, .. } = self.filter_type {
484            self.filter_type = FilterType::DisplayIncluding {
485                display,
486                windows: windows.iter().map(|w| (*w).clone()).collect(),
487            };
488        }
489        self
490    }
491
492    /// Include specific applications and optionally except certain windows
493    #[must_use]
494    pub fn with_including_applications(
495        mut self,
496        applications: &[&SCRunningApplication],
497        excepting_windows: &[&SCWindow],
498    ) -> Self {
499        if let FilterType::DisplayExcluding { display, .. }
500        | FilterType::DisplayIncluding { display, .. } = self.filter_type
501        {
502            self.filter_type = FilterType::DisplayIncludingApplications {
503                display,
504                applications: applications.iter().map(|a| (*a).clone()).collect(),
505                excepting_windows: excepting_windows.iter().map(|w| (*w).clone()).collect(),
506            };
507        }
508        self
509    }
510
511    /// Exclude specific applications and optionally except certain windows
512    ///
513    /// Captures everything on the display except the specified applications.
514    /// Windows in `excepting_windows` will still be captured even if their
515    /// owning application is excluded.
516    #[must_use]
517    pub fn with_excluding_applications(
518        mut self,
519        applications: &[&SCRunningApplication],
520        excepting_windows: &[&SCWindow],
521    ) -> Self {
522        if let FilterType::DisplayExcluding { display, .. }
523        | FilterType::DisplayIncluding { display, .. } = self.filter_type
524        {
525            self.filter_type = FilterType::DisplayExcludingApplications {
526                display,
527                applications: applications.iter().map(|a| (*a).clone()).collect(),
528                excepting_windows: excepting_windows.iter().map(|w| (*w).clone()).collect(),
529            };
530        }
531        self
532    }
533
534    /// Set the content rectangle (macOS 14.2+)
535    #[cfg(feature = "macos_14_2")]
536    #[must_use]
537    pub fn with_content_rect(mut self, rect: CGRect) -> Self {
538        self.content_rect = Some(rect);
539        self
540    }
541
542    // =========================================================================
543    // Deprecated methods - use with_* versions instead
544    // =========================================================================
545
546    /// Set the display to capture
547    #[must_use]
548    #[deprecated(since = "1.5.0", note = "Use with_display() instead")]
549    pub fn display(self, display: &SCDisplay) -> Self {
550        self.with_display(display)
551    }
552
553    /// Set the window to capture
554    #[must_use]
555    #[deprecated(since = "1.5.0", note = "Use with_window() instead")]
556    pub fn window(self, window: &SCWindow) -> Self {
557        self.with_window(window)
558    }
559
560    /// Exclude specific windows from the display capture
561    #[must_use]
562    #[deprecated(since = "1.5.0", note = "Use with_excluding_windows() instead")]
563    pub fn exclude_windows(self, windows: &[&SCWindow]) -> Self {
564        self.with_excluding_windows(windows)
565    }
566
567    /// Include only specific windows in the display capture
568    #[must_use]
569    #[deprecated(since = "1.5.0", note = "Use with_including_windows() instead")]
570    pub fn include_windows(self, windows: &[&SCWindow]) -> Self {
571        self.with_including_windows(windows)
572    }
573
574    /// Include specific applications and optionally except certain windows
575    #[must_use]
576    #[deprecated(since = "1.5.0", note = "Use with_including_applications() instead")]
577    pub fn include_applications(
578        self,
579        applications: &[&SCRunningApplication],
580        excepting_windows: &[&SCWindow],
581    ) -> Self {
582        self.with_including_applications(applications, excepting_windows)
583    }
584
585    /// Exclude specific applications and optionally except certain windows
586    #[must_use]
587    #[deprecated(since = "1.5.0", note = "Use with_excluding_applications() instead")]
588    pub fn exclude_applications(
589        self,
590        applications: &[&SCRunningApplication],
591        excepting_windows: &[&SCWindow],
592    ) -> Self {
593        self.with_excluding_applications(applications, excepting_windows)
594    }
595
596    /// Set the content rectangle (macOS 14.2+)
597    #[cfg(feature = "macos_14_2")]
598    #[must_use]
599    #[deprecated(since = "1.5.0", note = "Use with_content_rect() instead")]
600    pub fn content_rect(self, rect: CGRect) -> Self {
601        self.with_content_rect(rect)
602    }
603
604    /// Build the content filter
605    ///
606    /// # Panics
607    ///
608    /// Panics if no filter type was set. Call `.display()` or `.window()` before `.build()`.
609    #[must_use]
610    #[allow(clippy::too_many_lines)]
611    pub fn build(self) -> SCContentFilter {
612        let filter = match self.filter_type {
613            FilterType::Window(window) => unsafe {
614                let ptr =
615                    ffi::sc_content_filter_create_with_desktop_independent_window(window.as_ptr());
616                SCContentFilter(ptr)
617            },
618            FilterType::DisplayExcluding { display, windows } => {
619                let window_refs: Vec<&SCWindow> = windows.iter().collect();
620                unsafe {
621                    let window_ptrs: Vec<*const c_void> =
622                        window_refs.iter().map(|w| w.as_ptr()).collect();
623
624                    let ptr = if window_ptrs.is_empty() {
625                        ffi::sc_content_filter_create_with_display_excluding_windows(
626                            display.as_ptr(),
627                            std::ptr::null(),
628                            0,
629                        )
630                    } else {
631                        #[allow(clippy::cast_possible_wrap)]
632                        ffi::sc_content_filter_create_with_display_excluding_windows(
633                            display.as_ptr(),
634                            window_ptrs.as_ptr(),
635                            window_ptrs.len() as isize,
636                        )
637                    };
638                    SCContentFilter(ptr)
639                }
640            }
641            FilterType::DisplayIncluding { display, windows } => {
642                let window_refs: Vec<&SCWindow> = windows.iter().collect();
643                unsafe {
644                    let window_ptrs: Vec<*const c_void> =
645                        window_refs.iter().map(|w| w.as_ptr()).collect();
646
647                    let ptr = if window_ptrs.is_empty() {
648                        ffi::sc_content_filter_create_with_display_including_windows(
649                            display.as_ptr(),
650                            std::ptr::null(),
651                            0,
652                        )
653                    } else {
654                        #[allow(clippy::cast_possible_wrap)]
655                        ffi::sc_content_filter_create_with_display_including_windows(
656                            display.as_ptr(),
657                            window_ptrs.as_ptr(),
658                            window_ptrs.len() as isize,
659                        )
660                    };
661                    SCContentFilter(ptr)
662                }
663            }
664            FilterType::DisplayIncludingApplications {
665                display,
666                applications,
667                excepting_windows,
668            } => {
669                let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
670                let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
671                unsafe {
672                    let app_ptrs: Vec<*const c_void> =
673                        app_refs.iter().map(|a| a.as_ptr()).collect();
674
675                    let window_ptrs: Vec<*const c_void> =
676                        window_refs.iter().map(|w| w.as_ptr()).collect();
677
678                    #[allow(clippy::cast_possible_wrap)]
679                    let ptr = ffi::sc_content_filter_create_with_display_including_applications_excepting_windows(
680                        display.as_ptr(),
681                        if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
682                        app_ptrs.len() as isize,
683                        if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
684                        window_ptrs.len() as isize,
685                    );
686                    SCContentFilter(ptr)
687                }
688            }
689            FilterType::DisplayExcludingApplications {
690                display,
691                applications,
692                excepting_windows,
693            } => {
694                let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
695                let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
696                unsafe {
697                    let app_ptrs: Vec<*const c_void> =
698                        app_refs.iter().map(|a| a.as_ptr()).collect();
699
700                    let window_ptrs: Vec<*const c_void> =
701                        window_refs.iter().map(|w| w.as_ptr()).collect();
702
703                    #[allow(clippy::cast_possible_wrap)]
704                    let ptr = ffi::sc_content_filter_create_with_display_excluding_applications_excepting_windows(
705                        display.as_ptr(),
706                        if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
707                        app_ptrs.len() as isize,
708                        if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
709                        window_ptrs.len() as isize,
710                    );
711                    SCContentFilter(ptr)
712                }
713            }
714            FilterType::None => {
715                panic!(
716                    "SCContentFilterBuilder: No filter type set. \
717                     Call .display() or .window() before .build()"
718                );
719            }
720        };
721
722        // Apply content rect if set (macOS 14.2+)
723        #[cfg(feature = "macos_14_2")]
724        let filter = if let Some(rect) = self.content_rect {
725            filter.set_content_rect(rect)
726        } else {
727            filter
728        };
729
730        filter
731    }
732}
733
734impl std::fmt::Debug for SCContentFilterBuilder {
735    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
736        let filter_type_name = match &self.filter_type {
737            FilterType::None => "None",
738            FilterType::Window(_) => "Window",
739            FilterType::DisplayExcluding { .. } => "DisplayExcluding",
740            FilterType::DisplayIncluding { .. } => "DisplayIncluding",
741            FilterType::DisplayIncludingApplications { .. } => "DisplayIncludingApplications",
742            FilterType::DisplayExcludingApplications { .. } => "DisplayExcludingApplications",
743        };
744
745        let mut debug = f.debug_struct("SCContentFilterBuilder");
746        debug.field("filter_type", &filter_type_name);
747
748        #[cfg(feature = "macos_14_2")]
749        debug.field("content_rect", &self.content_rect);
750
751        debug.finish()
752    }
753}