1use 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
34pub 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
78impl SCContentFilter {
83 #[must_use]
102 pub fn create() -> SCContentFilterBuilder {
103 SCContentFilterBuilder::new()
104 }
105
106 #[cfg(feature = "macos_14_0")]
110 pub(crate) fn from_picker_ptr(ptr: *const c_void) -> Self {
111 Self(ptr)
112 }
113
114 pub(crate) fn as_ptr(&self) -> *const c_void {
116 self.0
117 }
118
119 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[repr(i32)]
276#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
277#[cfg(feature = "macos_14_0")]
278pub enum SCShareableContentStyle {
279 #[default]
281 None = 0,
282 Window = 1,
284 Display = 2,
286 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#[repr(i32)]
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
317#[cfg(feature = "macos_14_0")]
318pub enum SCStreamType {
319 #[default]
321 Window = 0,
322 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 {
366 unsafe { Self(crate::ffi::sc_content_filter_retain(self.0)) }
367 }
368}
369
370impl fmt::Debug for SCContentFilter {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 f.debug_struct("SCContentFilter")
373 .field("ptr", &self.0)
374 .finish()
375 }
376}
377
378impl fmt::Display for SCContentFilter {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 write!(f, "SCContentFilter")
381 }
382}
383
384unsafe impl Send for SCContentFilter {}
387unsafe impl Sync for SCContentFilter {}
388
389pub struct SCContentFilterBuilder {
421 filter_type: FilterType,
422 #[cfg(feature = "macos_14_2")]
423 content_rect: Option<CGRect>,
424}
425
426enum FilterType {
427 None,
428 Window(SCWindow),
429 DisplayExcluding {
430 display: SCDisplay,
431 windows: Vec<SCWindow>,
432 },
433 DisplayIncluding {
434 display: SCDisplay,
435 windows: Vec<SCWindow>,
436 },
437 DisplayIncludingApplications {
438 display: SCDisplay,
439 applications: Vec<SCRunningApplication>,
440 excepting_windows: Vec<SCWindow>,
441 },
442 DisplayExcludingApplications {
443 display: SCDisplay,
444 applications: Vec<SCRunningApplication>,
445 excepting_windows: Vec<SCWindow>,
446 },
447}
448
449impl SCContentFilterBuilder {
450 fn new() -> Self {
451 Self {
452 filter_type: FilterType::None,
453 #[cfg(feature = "macos_14_2")]
454 content_rect: None,
455 }
456 }
457
458 #[must_use]
460 pub fn with_display(mut self, display: &SCDisplay) -> Self {
461 self.filter_type = FilterType::DisplayExcluding {
462 display: display.clone(),
463 windows: Vec::new(),
464 };
465 self
466 }
467
468 #[must_use]
470 pub fn with_window(mut self, window: &SCWindow) -> Self {
471 self.filter_type = FilterType::Window(window.clone());
472 self
473 }
474
475 #[must_use]
477 pub fn with_excluding_windows(mut self, windows: &[&SCWindow]) -> Self {
478 if let FilterType::DisplayExcluding {
479 windows: ref mut excluded,
480 ..
481 } = self.filter_type
482 {
483 let mut v = Vec::with_capacity(windows.len());
487 v.extend(windows.iter().map(|w| (*w).clone()));
488 *excluded = v;
489 }
490 self
491 }
492
493 #[must_use]
495 pub fn with_including_windows(mut self, windows: &[&SCWindow]) -> Self {
496 if let FilterType::DisplayExcluding { display, .. } = self.filter_type {
497 let mut v = Vec::with_capacity(windows.len());
498 v.extend(windows.iter().map(|w| (*w).clone()));
499 self.filter_type = FilterType::DisplayIncluding {
500 display,
501 windows: v,
502 };
503 }
504 self
505 }
506
507 #[must_use]
509 pub fn with_including_applications(
510 mut self,
511 applications: &[&SCRunningApplication],
512 excepting_windows: &[&SCWindow],
513 ) -> Self {
514 if let FilterType::DisplayExcluding { display, .. }
515 | FilterType::DisplayIncluding { display, .. } = self.filter_type
516 {
517 let mut apps = Vec::with_capacity(applications.len());
518 apps.extend(applications.iter().map(|a| (*a).clone()));
519 let mut wins = Vec::with_capacity(excepting_windows.len());
520 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
521 self.filter_type = FilterType::DisplayIncludingApplications {
522 display,
523 applications: apps,
524 excepting_windows: wins,
525 };
526 }
527 self
528 }
529
530 #[must_use]
536 pub fn with_excluding_applications(
537 mut self,
538 applications: &[&SCRunningApplication],
539 excepting_windows: &[&SCWindow],
540 ) -> Self {
541 if let FilterType::DisplayExcluding { display, .. }
542 | FilterType::DisplayIncluding { display, .. } = self.filter_type
543 {
544 let mut apps = Vec::with_capacity(applications.len());
545 apps.extend(applications.iter().map(|a| (*a).clone()));
546 let mut wins = Vec::with_capacity(excepting_windows.len());
547 wins.extend(excepting_windows.iter().map(|w| (*w).clone()));
548 self.filter_type = FilterType::DisplayExcludingApplications {
549 display,
550 applications: apps,
551 excepting_windows: wins,
552 };
553 }
554 self
555 }
556
557 #[cfg(feature = "macos_14_2")]
559 #[must_use]
560 pub fn with_content_rect(mut self, rect: CGRect) -> Self {
561 self.content_rect = Some(rect);
562 self
563 }
564
565 #[must_use]
571 #[deprecated(since = "1.5.0", note = "Use with_display() instead")]
572 pub fn display(self, display: &SCDisplay) -> Self {
573 self.with_display(display)
574 }
575
576 #[must_use]
578 #[deprecated(since = "1.5.0", note = "Use with_window() instead")]
579 pub fn window(self, window: &SCWindow) -> Self {
580 self.with_window(window)
581 }
582
583 #[must_use]
585 #[deprecated(since = "1.5.0", note = "Use with_excluding_windows() instead")]
586 pub fn exclude_windows(self, windows: &[&SCWindow]) -> Self {
587 self.with_excluding_windows(windows)
588 }
589
590 #[must_use]
592 #[deprecated(since = "1.5.0", note = "Use with_including_windows() instead")]
593 pub fn include_windows(self, windows: &[&SCWindow]) -> Self {
594 self.with_including_windows(windows)
595 }
596
597 #[must_use]
599 #[deprecated(since = "1.5.0", note = "Use with_including_applications() instead")]
600 pub fn include_applications(
601 self,
602 applications: &[&SCRunningApplication],
603 excepting_windows: &[&SCWindow],
604 ) -> Self {
605 self.with_including_applications(applications, excepting_windows)
606 }
607
608 #[must_use]
610 #[deprecated(since = "1.5.0", note = "Use with_excluding_applications() instead")]
611 pub fn exclude_applications(
612 self,
613 applications: &[&SCRunningApplication],
614 excepting_windows: &[&SCWindow],
615 ) -> Self {
616 self.with_excluding_applications(applications, excepting_windows)
617 }
618
619 #[cfg(feature = "macos_14_2")]
621 #[must_use]
622 #[deprecated(since = "1.5.0", note = "Use with_content_rect() instead")]
623 pub fn content_rect(self, rect: CGRect) -> Self {
624 self.with_content_rect(rect)
625 }
626
627 #[must_use]
633 #[allow(clippy::too_many_lines)]
634 pub fn build(self) -> SCContentFilter {
635 let filter = match self.filter_type {
636 FilterType::Window(window) => unsafe {
637 let ptr =
638 ffi::sc_content_filter_create_with_desktop_independent_window(window.as_ptr());
639 SCContentFilter(ptr)
640 },
641 FilterType::DisplayExcluding { 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_excluding_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_excluding_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::DisplayIncluding { display, windows } => {
665 let window_refs: Vec<&SCWindow> = windows.iter().collect();
666 unsafe {
667 let window_ptrs: Vec<*const c_void> =
668 window_refs.iter().map(|w| w.as_ptr()).collect();
669
670 let ptr = if window_ptrs.is_empty() {
671 ffi::sc_content_filter_create_with_display_including_windows(
672 display.as_ptr(),
673 std::ptr::null(),
674 0,
675 )
676 } else {
677 #[allow(clippy::cast_possible_wrap)]
678 ffi::sc_content_filter_create_with_display_including_windows(
679 display.as_ptr(),
680 window_ptrs.as_ptr(),
681 window_ptrs.len() as isize,
682 )
683 };
684 SCContentFilter(ptr)
685 }
686 }
687 FilterType::DisplayIncludingApplications {
688 display,
689 applications,
690 excepting_windows,
691 } => {
692 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
693 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
694 unsafe {
695 let app_ptrs: Vec<*const c_void> =
696 app_refs.iter().map(|a| a.as_ptr()).collect();
697
698 let window_ptrs: Vec<*const c_void> =
699 window_refs.iter().map(|w| w.as_ptr()).collect();
700
701 #[allow(clippy::cast_possible_wrap)]
702 let ptr = ffi::sc_content_filter_create_with_display_including_applications_excepting_windows(
703 display.as_ptr(),
704 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
705 app_ptrs.len() as isize,
706 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
707 window_ptrs.len() as isize,
708 );
709 SCContentFilter(ptr)
710 }
711 }
712 FilterType::DisplayExcludingApplications {
713 display,
714 applications,
715 excepting_windows,
716 } => {
717 let app_refs: Vec<&SCRunningApplication> = applications.iter().collect();
718 let window_refs: Vec<&SCWindow> = excepting_windows.iter().collect();
719 unsafe {
720 let app_ptrs: Vec<*const c_void> =
721 app_refs.iter().map(|a| a.as_ptr()).collect();
722
723 let window_ptrs: Vec<*const c_void> =
724 window_refs.iter().map(|w| w.as_ptr()).collect();
725
726 #[allow(clippy::cast_possible_wrap)]
727 let ptr = ffi::sc_content_filter_create_with_display_excluding_applications_excepting_windows(
728 display.as_ptr(),
729 if app_ptrs.is_empty() { std::ptr::null() } else { app_ptrs.as_ptr() },
730 app_ptrs.len() as isize,
731 if window_ptrs.is_empty() { std::ptr::null() } else { window_ptrs.as_ptr() },
732 window_ptrs.len() as isize,
733 );
734 SCContentFilter(ptr)
735 }
736 }
737 FilterType::None => {
738 panic!(
739 "SCContentFilterBuilder: No filter type set. \
740 Call .display() or .window() before .build()"
741 );
742 }
743 };
744
745 #[cfg(feature = "macos_14_2")]
747 let filter = if let Some(rect) = self.content_rect {
748 filter.set_content_rect(rect)
749 } else {
750 filter
751 };
752
753 filter
754 }
755}
756
757impl std::fmt::Debug for SCContentFilterBuilder {
758 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
759 let filter_type_name = match &self.filter_type {
760 FilterType::None => "None",
761 FilterType::Window(_) => "Window",
762 FilterType::DisplayExcluding { .. } => "DisplayExcluding",
763 FilterType::DisplayIncluding { .. } => "DisplayIncluding",
764 FilterType::DisplayIncludingApplications { .. } => "DisplayIncludingApplications",
765 FilterType::DisplayExcludingApplications { .. } => "DisplayExcludingApplications",
766 };
767
768 let mut debug = f.debug_struct("SCContentFilterBuilder");
769 debug.field("filter_type", &filter_type_name);
770
771 #[cfg(feature = "macos_14_2")]
772 debug.field("content_rect", &self.content_rect);
773
774 debug.finish()
775 }
776}