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 {
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
376unsafe impl Send for SCContentFilter {}
379unsafe impl Sync for SCContentFilter {}
380
381pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}