1use std::ffi::c_void;
25use std::fmt;
26
27#[cfg(feature = "macos_14_2")]
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);
64
65impl PartialEq for SCContentFilter {
66 fn eq(&self, other: &Self) -> bool {
67 self.0 == other.0
68 }
69}
70
71impl Eq for SCContentFilter {}
72
73impl std::hash::Hash for SCContentFilter {
74 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
75 self.0.hash(state);
76 }
77}
78
79impl SCContentFilter {
84 #[must_use]
103 pub fn create() -> SCContentFilterBuilder {
104 SCContentFilterBuilder::new()
105 }
106
107 #[cfg(feature = "macos_14_0")]
111 pub(crate) fn from_picker_ptr(ptr: *const c_void) -> Self {
112 Self(ptr)
113 }
114
115 pub(crate) fn as_ptr(&self) -> *const c_void {
117 self.0
118 }
119
120 #[cfg(feature = "macos_14_2")]
124 #[must_use]
125 pub fn set_content_rect(self, rect: CGRect) -> Self {
126 unsafe {
127 ffi::sc_content_filter_set_content_rect(
128 self.0,
129 rect.origin.x,
130 rect.origin.y,
131 rect.size.width,
132 rect.size.height,
133 );
134 }
135 self
136 }
137
138 #[cfg(feature = "macos_14_2")]
140 pub fn content_rect(&self) -> CGRect {
141 unsafe {
142 let mut x = 0.0;
143 let mut y = 0.0;
144 let mut width = 0.0;
145 let mut height = 0.0;
146 ffi::sc_content_filter_get_content_rect(
147 self.0,
148 &mut x,
149 &mut y,
150 &mut width,
151 &mut height,
152 );
153 CGRect::new(x, y, width, height)
154 }
155 }
156
157 #[cfg(feature = "macos_14_0")]
161 pub fn style(&self) -> SCShareableContentStyle {
162 let value = unsafe { ffi::sc_content_filter_get_style(self.0) };
163 SCShareableContentStyle::from(value)
164 }
165
166 #[cfg(feature = "macos_14_0")]
170 pub fn stream_type(&self) -> SCStreamType {
171 let value = unsafe { ffi::sc_content_filter_get_stream_type(self.0) };
172 SCStreamType::from(value)
173 }
174
175 #[cfg(feature = "macos_14_0")]
180 pub fn point_pixel_scale(&self) -> f32 {
181 unsafe { ffi::sc_content_filter_get_point_pixel_scale(self.0) }
182 }
183
184 #[cfg(feature = "macos_14_2")]
189 pub fn set_include_menu_bar(&mut self, include: bool) {
190 unsafe {
191 ffi::sc_content_filter_set_include_menu_bar(self.0, include);
192 }
193 }
194
195 #[cfg(feature = "macos_14_2")]
197 pub fn include_menu_bar(&self) -> bool {
198 unsafe { ffi::sc_content_filter_get_include_menu_bar(self.0) }
199 }
200
201 #[cfg(feature = "macos_15_2")]
205 pub fn included_displays(&self) -> Vec<SCDisplay> {
206 let count = unsafe { ffi::sc_content_filter_get_included_displays_count(self.0) };
207 if count <= 0 {
208 return Vec::new();
209 }
210 #[allow(clippy::cast_sign_loss)]
211 (0..count as usize)
212 .filter_map(|i| {
213 #[allow(clippy::cast_possible_wrap)]
214 let ptr =
215 unsafe { ffi::sc_content_filter_get_included_display_at(self.0, i as isize) };
216 unsafe { SCDisplay::from_retained_ptr(ptr) }
217 })
218 .collect()
219 }
220
221 #[cfg(feature = "macos_15_2")]
225 pub fn included_windows(&self) -> Vec<SCWindow> {
226 let count = unsafe { ffi::sc_content_filter_get_included_windows_count(self.0) };
227 if count <= 0 {
228 return Vec::new();
229 }
230 #[allow(clippy::cast_sign_loss)]
231 (0..count as usize)
232 .filter_map(|i| {
233 #[allow(clippy::cast_possible_wrap)]
234 let ptr =
235 unsafe { ffi::sc_content_filter_get_included_window_at(self.0, i as isize) };
236 unsafe { SCWindow::from_retained_ptr(ptr) }
237 })
238 .collect()
239 }
240
241 #[cfg(feature = "macos_15_2")]
245 pub fn included_applications(&self) -> Vec<SCRunningApplication> {
246 let count = unsafe { ffi::sc_content_filter_get_included_applications_count(self.0) };
247 if count <= 0 {
248 return Vec::new();
249 }
250 #[allow(clippy::cast_sign_loss)]
251 (0..count as usize)
252 .filter_map(|i| {
253 #[allow(clippy::cast_possible_wrap)]
254 let ptr = unsafe {
255 ffi::sc_content_filter_get_included_application_at(self.0, i as isize)
256 };
257 unsafe { SCRunningApplication::from_retained_ptr(ptr) }
258 })
259 .collect()
260 }
261}
262
263#[repr(i32)]
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
266#[cfg(feature = "macos_14_0")]
267pub enum SCShareableContentStyle {
268 #[default]
270 None = 0,
271 Window = 1,
273 Display = 2,
275 Application = 3,
277}
278
279#[cfg(feature = "macos_14_0")]
280impl From<i32> for SCShareableContentStyle {
281 fn from(value: i32) -> Self {
282 match value {
283 1 => Self::Window,
284 2 => Self::Display,
285 3 => Self::Application,
286 _ => Self::None,
287 }
288 }
289}
290
291#[cfg(feature = "macos_14_0")]
292impl std::fmt::Display for SCShareableContentStyle {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 match self {
295 Self::None => write!(f, "None"),
296 Self::Window => write!(f, "Window"),
297 Self::Display => write!(f, "Display"),
298 Self::Application => write!(f, "Application"),
299 }
300 }
301}
302
303#[repr(i32)]
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
306#[cfg(feature = "macos_14_0")]
307pub enum SCStreamType {
308 #[default]
310 Window = 0,
311 Display = 1,
313}
314
315#[cfg(feature = "macos_14_0")]
316impl From<i32> for SCStreamType {
317 fn from(value: i32) -> Self {
318 match value {
319 1 => Self::Display,
320 _ => Self::Window,
321 }
322 }
323}
324
325#[cfg(feature = "macos_14_0")]
326impl std::fmt::Display for SCStreamType {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 match self {
329 Self::Window => write!(f, "Window"),
330 Self::Display => write!(f, "Display"),
331 }
332 }
333}
334
335crate::utils::retained::sc_retained!(
340 SCContentFilter,
341 retain = crate::ffi::sc_content_filter_retain,
342 release = crate::ffi::sc_content_filter_release,
343);
344
345impl fmt::Debug for SCContentFilter {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 f.debug_struct("SCContentFilter")
348 .field("ptr", &self.0)
349 .finish()
350 }
351}
352
353impl fmt::Display for SCContentFilter {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 write!(f, "SCContentFilter")
356 }
357}
358
359unsafe impl Send for SCContentFilter {}
362unsafe impl Sync for SCContentFilter {}
363
364pub struct SCContentFilterBuilder {
396 filter_type: FilterType,
397 #[cfg(feature = "macos_14_2")]
398 content_rect: Option<CGRect>,
399}
400
401enum FilterType {
402 None,
403 Window(SCWindow),
404 DisplayExcluding {
405 display: SCDisplay,
406 windows: Vec<SCWindow>,
407 },
408 DisplayIncluding {
409 display: SCDisplay,
410 windows: Vec<SCWindow>,
411 },
412 DisplayIncludingApplications {
413 display: SCDisplay,
414 applications: Vec<SCRunningApplication>,
415 excepting_windows: Vec<SCWindow>,
416 },
417 DisplayExcludingApplications {
418 display: SCDisplay,
419 applications: Vec<SCRunningApplication>,
420 excepting_windows: Vec<SCWindow>,
421 },
422}
423
424impl SCContentFilterBuilder {
425 fn new() -> Self {
426 Self {
427 filter_type: FilterType::None,
428 #[cfg(feature = "macos_14_2")]
429 content_rect: None,
430 }
431 }
432
433 #[must_use]
435 pub fn with_display(mut self, display: &SCDisplay) -> Self {
436 self.filter_type = FilterType::DisplayExcluding {
437 display: display.clone(),
438 windows: Vec::new(),
439 };
440 self
441 }
442
443 #[must_use]
445 pub fn with_window(mut self, window: &SCWindow) -> Self {
446 self.filter_type = FilterType::Window(window.clone());
447 self
448 }
449
450 #[must_use]
452 pub fn with_excluding_windows(mut self, windows: &[&SCWindow]) -> Self {
453 if let FilterType::DisplayExcluding {
454 windows: ref mut excluded,
455 ..
456 } = self.filter_type
457 {
458 let mut v = Vec::with_capacity(windows.len());
462 v.extend(windows.iter().map(|w| (*w).clone()));
463 *excluded = v;
464 }
465 self
466 }
467
468 #[must_use]
470 pub fn with_including_windows(mut self, windows: &[&SCWindow]) -> Self {
471 if let FilterType::DisplayExcluding { display, .. } = self.filter_type {
472 let mut v = Vec::with_capacity(windows.len());
473 v.extend(windows.iter().map(|w| (*w).clone()));
474 self.filter_type = FilterType::DisplayIncluding {
475 display,
476 windows: v,
477 };
478 }
479 self
480 }
481
482 #[must_use]
484 pub fn with_including_applications(
485 mut self,
486 applications: &[&SCRunningApplication],
487 excepting_windows: &[&SCWindow],
488 ) -> Self {
489 if let FilterType::DisplayExcluding { display, .. }
490 | FilterType::DisplayIncluding { display, .. } = self.filter_type
491 {
492 let mut apps = Vec::with_capacity(applications.len());
493 apps.extend(applications.iter().map(|a| (*a).clone()));
494 let mut wins = Vec::with_capacity(excepting_windows.len());
495 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
496 self.filter_type = FilterType::DisplayIncludingApplications {
497 display,
498 applications: apps,
499 excepting_windows: wins,
500 };
501 }
502 self
503 }
504
505 #[must_use]
511 pub fn with_excluding_applications(
512 mut self,
513 applications: &[&SCRunningApplication],
514 excepting_windows: &[&SCWindow],
515 ) -> Self {
516 if let FilterType::DisplayExcluding { display, .. }
517 | FilterType::DisplayIncluding { display, .. } = self.filter_type
518 {
519 let mut apps = Vec::with_capacity(applications.len());
520 apps.extend(applications.iter().map(|a| (*a).clone()));
521 let mut wins = Vec::with_capacity(excepting_windows.len());
522 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
523 self.filter_type = FilterType::DisplayExcludingApplications {
524 display,
525 applications: apps,
526 excepting_windows: wins,
527 };
528 }
529 self
530 }
531
532 #[cfg(feature = "macos_14_2")]
534 #[must_use]
535 pub fn with_content_rect(mut self, rect: CGRect) -> Self {
536 self.content_rect = Some(rect);
537 self
538 }
539
540 #[must_use]
546 #[deprecated(since = "1.5.0", note = "Use with_display() instead")]
547 pub fn display(self, display: &SCDisplay) -> Self {
548 self.with_display(display)
549 }
550
551 #[must_use]
553 #[deprecated(since = "1.5.0", note = "Use with_window() instead")]
554 pub fn window(self, window: &SCWindow) -> Self {
555 self.with_window(window)
556 }
557
558 #[must_use]
560 #[deprecated(since = "1.5.0", note = "Use with_excluding_windows() instead")]
561 pub fn exclude_windows(self, windows: &[&SCWindow]) -> Self {
562 self.with_excluding_windows(windows)
563 }
564
565 #[must_use]
567 #[deprecated(since = "1.5.0", note = "Use with_including_windows() instead")]
568 pub fn include_windows(self, windows: &[&SCWindow]) -> Self {
569 self.with_including_windows(windows)
570 }
571
572 #[must_use]
574 #[deprecated(since = "1.5.0", note = "Use with_including_applications() instead")]
575 pub fn include_applications(
576 self,
577 applications: &[&SCRunningApplication],
578 excepting_windows: &[&SCWindow],
579 ) -> Self {
580 self.with_including_applications(applications, excepting_windows)
581 }
582
583 #[must_use]
585 #[deprecated(since = "1.5.0", note = "Use with_excluding_applications() instead")]
586 pub fn exclude_applications(
587 self,
588 applications: &[&SCRunningApplication],
589 excepting_windows: &[&SCWindow],
590 ) -> Self {
591 self.with_excluding_applications(applications, excepting_windows)
592 }
593
594 #[cfg(feature = "macos_14_2")]
596 #[must_use]
597 #[deprecated(since = "1.5.0", note = "Use with_content_rect() instead")]
598 pub fn content_rect(self, rect: CGRect) -> Self {
599 self.with_content_rect(rect)
600 }
601
602 #[must_use]
610 pub fn build(self) -> SCContentFilter {
611 self.try_build()
612 .expect("SCContentFilterBuilder: No filter type set. Call .display() or .window() before .build()")
613 }
614
615 #[allow(clippy::too_many_lines)]
623 pub fn try_build(self) -> SCResult<SCContentFilter> {
624 let filter = match self.filter_type {
625 FilterType::Window(window) => unsafe {
626 let ptr =
627 ffi::sc_content_filter_create_with_desktop_independent_window(window.as_ptr());
628 SCContentFilter(ptr)
629 },
630 FilterType::DisplayExcluding { display, windows } => {
631 let window_refs: Vec<&SCWindow> = windows.iter().collect();
632 unsafe {
633 let window_ptrs: Vec<*const c_void> =
634 window_refs.iter().map(|w| w.as_ptr()).collect();
635
636 let ptr = if window_ptrs.is_empty() {
637 ffi::sc_content_filter_create_with_display_excluding_windows(
638 display.as_ptr(),
639 std::ptr::null(),
640 0,
641 )
642 } else {
643 #[allow(clippy::cast_possible_wrap)]
644 ffi::sc_content_filter_create_with_display_excluding_windows(
645 display.as_ptr(),
646 window_ptrs.as_ptr(),
647 window_ptrs.len() as isize,
648 )
649 };
650 SCContentFilter(ptr)
651 }
652 }
653 FilterType::DisplayIncluding { display, windows } => {
654 let window_refs: Vec<&SCWindow> = windows.iter().collect();
655 unsafe {
656 let window_ptrs: Vec<*const c_void> =
657 window_refs.iter().map(|w| w.as_ptr()).collect();
658
659 let ptr = if window_ptrs.is_empty() {
660 ffi::sc_content_filter_create_with_display_including_windows(
661 display.as_ptr(),
662 std::ptr::null(),
663 0,
664 )
665 } else {
666 #[allow(clippy::cast_possible_wrap)]
667 ffi::sc_content_filter_create_with_display_including_windows(
668 display.as_ptr(),
669 window_ptrs.as_ptr(),
670 window_ptrs.len() as isize,
671 )
672 };
673 SCContentFilter(ptr)
674 }
675 }
676 FilterType::DisplayIncludingApplications {
677 display,
678 applications,
679 excepting_windows,
680 } => {
681 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
682 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
683 unsafe {
684 let app_ptrs: Vec<*const c_void> =
685 app_refs.iter().map(|a| a.as_ptr()).collect();
686
687 let window_ptrs: Vec<*const c_void> =
688 window_refs.iter().map(|w| w.as_ptr()).collect();
689
690 #[allow(clippy::cast_possible_wrap)]
691 let ptr = ffi::sc_content_filter_create_with_display_including_applications_excepting_windows(
692 display.as_ptr(),
693 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
694 app_ptrs.len() as isize,
695 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
696 window_ptrs.len() as isize,
697 );
698 SCContentFilter(ptr)
699 }
700 }
701 FilterType::DisplayExcludingApplications {
702 display,
703 applications,
704 excepting_windows,
705 } => {
706 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
707 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
708 unsafe {
709 let app_ptrs: Vec<*const c_void> =
710 app_refs.iter().map(|a| a.as_ptr()).collect();
711
712 let window_ptrs: Vec<*const c_void> =
713 window_refs.iter().map(|w| w.as_ptr()).collect();
714
715 #[allow(clippy::cast_possible_wrap)]
716 let ptr = ffi::sc_content_filter_create_with_display_excluding_applications_excepting_windows(
717 display.as_ptr(),
718 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
719 app_ptrs.len() as isize,
720 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
721 window_ptrs.len() as isize,
722 );
723 SCContentFilter(ptr)
724 }
725 }
726 FilterType::None => {
727 return Err(SCError::invalid_config(
728 "SCContentFilterBuilder: No filter type set. \
729 Call .display() or .window() before building.",
730 ));
731 }
732 };
733
734 #[cfg(feature = "macos_14_2")]
736 let filter = if let Some(rect) = self.content_rect {
737 filter.set_content_rect(rect)
738 } else {
739 filter
740 };
741
742 Ok(filter)
743 }
744}
745
746impl std::fmt::Debug for SCContentFilterBuilder {
747 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
748 let filter_type_name = match &self.filter_type {
749 FilterType::None => "None",
750 FilterType::Window(_) => "Window",
751 FilterType::DisplayExcluding { .. } => "DisplayExcluding",
752 FilterType::DisplayIncluding { .. } => "DisplayIncluding",
753 FilterType::DisplayIncludingApplications { .. } => "DisplayIncludingApplications",
754 FilterType::DisplayExcludingApplications { .. } => "DisplayExcludingApplications",
755 };
756
757 let mut debug = f.debug_struct("SCContentFilterBuilder");
758 debug.field("filter_type", &filter_type_name);
759
760 #[cfg(feature = "macos_14_2")]
761 debug.field("content_rect", &self.content_rect);
762
763 debug.finish()
764 }
765}