1use std::ffi::c_void;
25use std::fmt;
26
27#[cfg(feature = "macos_14_0")]
28use crate::cg::CGRect;
29use crate::{
30 error::{SCError, SCResult},
31 ffi,
32 shareable_content::{SCDisplay, SCRunningApplication, SCWindow},
33};
34
35pub struct SCContentFilter(*const c_void);
90
91impl PartialEq for SCContentFilter {
92 fn eq(&self, other: &Self) -> bool {
93 self.0 == other.0
94 }
95}
96
97impl Eq for SCContentFilter {}
98
99impl std::hash::Hash for SCContentFilter {
100 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101 self.0.hash(state);
102 }
103}
104
105impl SCContentFilter {
110 #[must_use]
129 pub fn create() -> SCContentFilterBuilder {
130 SCContentFilterBuilder::new()
131 }
132
133 #[cfg(feature = "macos_14_0")]
137 pub(crate) fn from_picker_ptr(ptr: *const c_void) -> Self {
138 Self(ptr)
139 }
140
141 pub(crate) fn as_ptr(&self) -> *const c_void {
143 self.0
144 }
145
146 #[cfg(feature = "macos_14_0")]
153 pub fn content_rect(&self) -> CGRect {
154 unsafe {
155 let mut x = 0.0;
156 let mut y = 0.0;
157 let mut width = 0.0;
158 let mut height = 0.0;
159 ffi::sc_content_filter_get_content_rect(
160 self.0,
161 &raw mut x,
162 &raw mut y,
163 &raw mut width,
164 &raw mut height,
165 );
166 CGRect::new(x, y, width, height)
167 }
168 }
169
170 #[cfg(feature = "macos_14_0")]
174 #[allow(clippy::missing_errors_doc)]
175 pub fn style(&self) -> SCResult<SCShareableContentStyle> {
176 let mut raw = 0_i32;
177 if !unsafe { ffi::sc_content_filter_get_style(self.0, &raw mut raw) } {
178 return Err(SCError::feature_not_available(
179 "SCContentFilter.style",
180 "14.0",
181 ));
182 }
183 SCShareableContentStyle::from_raw(raw).ok_or_else(|| SCError::UnknownValue {
184 type_name: "SCShareableContentStyle",
185 raw: i64::from(raw),
186 })
187 }
188
189 #[cfg(feature = "macos_14_0")]
193 #[deprecated(
194 since = "8.0.0",
195 note = "Apple deprecated SCContentFilter.streamType in macOS 14.2 (and SCStreamType \
196 itself in 15.0). Use `style()`, which also distinguishes application filters."
197 )]
198 #[allow(deprecated)]
199 #[allow(clippy::missing_errors_doc)]
200 pub fn stream_type(&self) -> SCResult<SCStreamType> {
201 let mut raw = 0_i32;
202 if !unsafe { ffi::sc_content_filter_get_stream_type(self.0, &raw mut raw) } {
203 return Err(SCError::feature_not_available(
204 "SCContentFilter.streamType",
205 "14.0",
206 ));
207 }
208 SCStreamType::from_raw(raw).ok_or_else(|| SCError::UnknownValue {
209 type_name: "SCStreamType",
210 raw: i64::from(raw),
211 })
212 }
213
214 #[cfg(feature = "macos_14_0")]
219 pub fn point_pixel_scale(&self) -> f32 {
220 unsafe { ffi::sc_content_filter_get_point_pixel_scale(self.0) }
221 }
222
223 #[cfg(feature = "macos_14_2")]
234 pub fn include_menu_bar(&self) -> bool {
235 unsafe { ffi::sc_content_filter_get_include_menu_bar(self.0) }
236 }
237
238 #[cfg(feature = "macos_15_2")]
242 pub fn included_displays(&self) -> Vec<SCDisplay> {
243 let count = unsafe { ffi::sc_content_filter_get_included_displays_count(self.0) };
244 if count <= 0 {
245 return Vec::new();
246 }
247 #[allow(clippy::cast_sign_loss)]
248 (0..count as usize)
249 .filter_map(|i| {
250 #[allow(clippy::cast_possible_wrap)]
251 let ptr =
252 unsafe { ffi::sc_content_filter_get_included_display_at(self.0, i as isize) };
253 unsafe { SCDisplay::from_retained_ptr(ptr) }
254 })
255 .collect()
256 }
257
258 #[cfg(feature = "macos_15_2")]
262 pub fn included_windows(&self) -> Vec<SCWindow> {
263 let count = unsafe { ffi::sc_content_filter_get_included_windows_count(self.0) };
264 if count <= 0 {
265 return Vec::new();
266 }
267 #[allow(clippy::cast_sign_loss)]
268 (0..count as usize)
269 .filter_map(|i| {
270 #[allow(clippy::cast_possible_wrap)]
271 let ptr =
272 unsafe { ffi::sc_content_filter_get_included_window_at(self.0, i as isize) };
273 unsafe { SCWindow::from_retained_ptr(ptr) }
274 })
275 .collect()
276 }
277
278 #[cfg(feature = "macos_15_2")]
282 pub fn included_applications(&self) -> Vec<SCRunningApplication> {
283 let count = unsafe { ffi::sc_content_filter_get_included_applications_count(self.0) };
284 if count <= 0 {
285 return Vec::new();
286 }
287 #[allow(clippy::cast_sign_loss)]
288 (0..count as usize)
289 .filter_map(|i| {
290 #[allow(clippy::cast_possible_wrap)]
291 let ptr = unsafe {
292 ffi::sc_content_filter_get_included_application_at(self.0, i as isize)
293 };
294 unsafe { SCRunningApplication::from_retained_ptr(ptr) }
295 })
296 .collect()
297 }
298}
299
300#[repr(i32)]
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
303#[cfg(feature = "macos_14_0")]
304pub enum SCShareableContentStyle {
305 #[default]
307 None = 0,
308 Window = 1,
310 Display = 2,
312 Application = 3,
314}
315
316#[cfg(feature = "macos_14_0")]
317impl SCShareableContentStyle {
318 pub const fn from_raw(raw: i32) -> Option<Self> {
319 match raw {
320 0 => Some(Self::None),
321 1 => Some(Self::Window),
322 2 => Some(Self::Display),
323 3 => Some(Self::Application),
324 _ => None,
325 }
326 }
327}
328
329#[cfg(feature = "macos_14_0")]
330impl std::fmt::Display for SCShareableContentStyle {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 match self {
333 Self::None => write!(f, "None"),
334 Self::Window => write!(f, "Window"),
335 Self::Display => write!(f, "Display"),
336 Self::Application => write!(f, "Application"),
337 }
338 }
339}
340
341#[repr(i32)]
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
344#[cfg(feature = "macos_14_0")]
345#[deprecated(
346 since = "8.0.0",
347 note = "Apple deprecated SCStreamType in macOS 15.0. Use SCShareableContentStyle instead."
348)]
349#[allow(deprecated)]
350pub enum SCStreamType {
351 #[default]
353 Window = 0,
354 Display = 1,
356}
357
358#[cfg(feature = "macos_14_0")]
359#[allow(deprecated)]
360impl SCStreamType {
361 pub const fn from_raw(raw: i32) -> Option<Self> {
362 match raw {
363 0 => Some(Self::Window),
364 1 => Some(Self::Display),
365 _ => None,
366 }
367 }
368}
369
370#[cfg(feature = "macos_14_0")]
371#[allow(deprecated)]
372impl std::fmt::Display for SCStreamType {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 match self {
375 Self::Window => write!(f, "Window"),
376 Self::Display => write!(f, "Display"),
377 }
378 }
379}
380
381crate::utils::retained::sc_retained!(
389 SCContentFilter,
390 retain = crate::ffi::sc_content_filter_retain,
391 release = crate::ffi::sc_content_filter_release,
392);
393
394impl fmt::Debug for SCContentFilter {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 f.debug_struct("SCContentFilter")
397 .field("ptr", &self.0)
398 .finish()
399 }
400}
401
402impl fmt::Display for SCContentFilter {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 write!(f, "SCContentFilter")
405 }
406}
407
408unsafe impl Send for SCContentFilter {}
416unsafe impl Sync for SCContentFilter {}
417
418pub struct SCContentFilterBuilder {
450 filter_type: FilterType,
451 #[cfg(feature = "macos_14_2")]
452 include_menu_bar: Option<bool>,
453}
454
455enum FilterType {
456 None,
457 Window(SCWindow),
458 DisplayExcluding {
459 display: SCDisplay,
460 windows: Vec<SCWindow>,
461 },
462 DisplayIncluding {
463 display: SCDisplay,
464 windows: Vec<SCWindow>,
465 },
466 DisplayIncludingApplications {
467 display: SCDisplay,
468 applications: Vec<SCRunningApplication>,
469 excepting_windows: Vec<SCWindow>,
470 },
471 DisplayExcludingApplications {
472 display: SCDisplay,
473 applications: Vec<SCRunningApplication>,
474 excepting_windows: Vec<SCWindow>,
475 },
476}
477
478impl SCContentFilterBuilder {
479 fn new() -> Self {
480 Self {
481 filter_type: FilterType::None,
482 #[cfg(feature = "macos_14_2")]
483 include_menu_bar: None,
484 }
485 }
486
487 #[must_use]
489 pub fn with_display(mut self, display: &SCDisplay) -> Self {
490 self.filter_type = FilterType::DisplayExcluding {
491 display: display.clone(),
492 windows: Vec::new(),
493 };
494 self
495 }
496
497 #[must_use]
499 pub fn with_window(mut self, window: &SCWindow) -> Self {
500 self.filter_type = FilterType::Window(window.clone());
501 self
502 }
503
504 #[must_use]
506 pub fn with_excluding_windows(mut self, windows: &[&SCWindow]) -> Self {
507 if let FilterType::DisplayExcluding {
508 windows: ref mut excluded,
509 ..
510 } = self.filter_type
511 {
512 let mut v = Vec::with_capacity(windows.len());
516 v.extend(windows.iter().map(|w| (*w).clone()));
517 *excluded = v;
518 }
519 self
520 }
521
522 #[must_use]
524 pub fn with_including_windows(mut self, windows: &[&SCWindow]) -> Self {
525 if let FilterType::DisplayExcluding { display, .. } = self.filter_type {
526 let mut v = Vec::with_capacity(windows.len());
527 v.extend(windows.iter().map(|w| (*w).clone()));
528 self.filter_type = FilterType::DisplayIncluding {
529 display,
530 windows: v,
531 };
532 }
533 self
534 }
535
536 #[must_use]
538 pub fn with_including_applications(
539 mut self,
540 applications: &[&SCRunningApplication],
541 excepting_windows: &[&SCWindow],
542 ) -> Self {
543 if let FilterType::DisplayExcluding { display, .. }
544 | FilterType::DisplayIncluding { display, .. } = self.filter_type
545 {
546 let mut apps = Vec::with_capacity(applications.len());
547 apps.extend(applications.iter().map(|a| (*a).clone()));
548 let mut wins = Vec::with_capacity(excepting_windows.len());
549 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
550 self.filter_type = FilterType::DisplayIncludingApplications {
551 display,
552 applications: apps,
553 excepting_windows: wins,
554 };
555 }
556 self
557 }
558
559 #[must_use]
565 pub fn with_excluding_applications(
566 mut self,
567 applications: &[&SCRunningApplication],
568 excepting_windows: &[&SCWindow],
569 ) -> Self {
570 if let FilterType::DisplayExcluding { display, .. }
571 | FilterType::DisplayIncluding { display, .. } = self.filter_type
572 {
573 let mut apps = Vec::with_capacity(applications.len());
574 apps.extend(applications.iter().map(|a| (*a).clone()));
575 let mut wins = Vec::with_capacity(excepting_windows.len());
576 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
577 self.filter_type = FilterType::DisplayExcludingApplications {
578 display,
579 applications: apps,
580 excepting_windows: wins,
581 };
582 }
583 self
584 }
585
586 #[cfg(feature = "macos_14_2")]
598 #[must_use]
599 pub fn with_include_menu_bar(mut self, include: bool) -> Self {
600 self.include_menu_bar = Some(include);
601 self
602 }
603
604 #[allow(clippy::too_many_lines)]
613 pub fn build(self) -> SCResult<SCContentFilter> {
614 let filter = match self.filter_type {
615 FilterType::Window(window) => unsafe {
616 let ptr =
617 ffi::sc_content_filter_create_with_desktop_independent_window(window.as_ptr());
618 SCContentFilter(ptr)
619 },
620 FilterType::DisplayExcluding { display, windows } => {
621 let window_refs: Vec<&SCWindow> = windows.iter().collect();
622 unsafe {
623 let window_ptrs: Vec<*const c_void> =
624 window_refs.iter().map(|w| w.as_ptr()).collect();
625
626 let ptr = if window_ptrs.is_empty() {
627 ffi::sc_content_filter_create_with_display_excluding_windows(
628 display.as_ptr(),
629 std::ptr::null(),
630 0,
631 )
632 } else {
633 #[allow(clippy::cast_possible_wrap)]
634 ffi::sc_content_filter_create_with_display_excluding_windows(
635 display.as_ptr(),
636 window_ptrs.as_ptr(),
637 window_ptrs.len() as isize,
638 )
639 };
640 SCContentFilter(ptr)
641 }
642 }
643 FilterType::DisplayIncluding { display, windows } => {
644 let window_refs: Vec<&SCWindow> = windows.iter().collect();
645 unsafe {
646 let window_ptrs: Vec<*const c_void> =
647 window_refs.iter().map(|w| w.as_ptr()).collect();
648
649 let ptr = if window_ptrs.is_empty() {
650 ffi::sc_content_filter_create_with_display_including_windows(
651 display.as_ptr(),
652 std::ptr::null(),
653 0,
654 )
655 } else {
656 #[allow(clippy::cast_possible_wrap)]
657 ffi::sc_content_filter_create_with_display_including_windows(
658 display.as_ptr(),
659 window_ptrs.as_ptr(),
660 window_ptrs.len() as isize,
661 )
662 };
663 SCContentFilter(ptr)
664 }
665 }
666 FilterType::DisplayIncludingApplications {
667 display,
668 applications,
669 excepting_windows,
670 } => {
671 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
672 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
673 unsafe {
674 let app_ptrs: Vec<*const c_void> =
675 app_refs.iter().map(|a| a.as_ptr()).collect();
676
677 let window_ptrs: Vec<*const c_void> =
678 window_refs.iter().map(|w| w.as_ptr()).collect();
679
680 #[allow(clippy::cast_possible_wrap)]
681 let ptr = ffi::sc_content_filter_create_with_display_including_applications_excepting_windows(
682 display.as_ptr(),
683 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
684 app_ptrs.len() as isize,
685 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
686 window_ptrs.len() as isize,
687 );
688 SCContentFilter(ptr)
689 }
690 }
691 FilterType::DisplayExcludingApplications {
692 display,
693 applications,
694 excepting_windows,
695 } => {
696 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
697 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
698 unsafe {
699 let app_ptrs: Vec<*const c_void> =
700 app_refs.iter().map(|a| a.as_ptr()).collect();
701
702 let window_ptrs: Vec<*const c_void> =
703 window_refs.iter().map(|w| w.as_ptr()).collect();
704
705 #[allow(clippy::cast_possible_wrap)]
706 let ptr = ffi::sc_content_filter_create_with_display_excluding_applications_excepting_windows(
707 display.as_ptr(),
708 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
709 app_ptrs.len() as isize,
710 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
711 window_ptrs.len() as isize,
712 );
713 SCContentFilter(ptr)
714 }
715 }
716 FilterType::None => {
717 return Err(SCError::invalid_config(
718 "SCContentFilterBuilder: No filter type set. \
719 Call .with_display() or .with_window() before building.",
720 ));
721 }
722 };
723
724 #[cfg(feature = "macos_14_2")]
731 if let Some(include) = self.include_menu_bar {
732 if !unsafe { ffi::sc_content_filter_set_include_menu_bar(filter.0, include) } {
733 return Err(SCError::feature_not_available(
734 "SCContentFilter.includeMenuBar",
735 "14.2",
736 ));
737 }
738 }
739
740 Ok(filter)
741 }
742}
743
744impl std::fmt::Debug for SCContentFilterBuilder {
745 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746 let filter_type_name = match &self.filter_type {
747 FilterType::None => "None",
748 FilterType::Window(_) => "Window",
749 FilterType::DisplayExcluding { .. } => "DisplayExcluding",
750 FilterType::DisplayIncluding { .. } => "DisplayIncluding",
751 FilterType::DisplayIncludingApplications { .. } => "DisplayIncludingApplications",
752 FilterType::DisplayExcludingApplications { .. } => "DisplayExcludingApplications",
753 };
754
755 let mut debug = f.debug_struct("SCContentFilterBuilder");
756 debug.field("filter_type", &filter_type_name);
757
758 #[cfg(feature = "macos_14_2")]
759 debug.field("include_menu_bar", &self.include_menu_bar);
760
761 debug.finish()
762 }
763}