Skip to main content

screencapturekit/shareable_content/
window.rs

1use crate::cg::CGRect;
2use crate::utils::ffi_string::ffi_string_owned;
3use core::fmt;
4use std::ffi::c_void;
5
6use super::SCRunningApplication;
7
8/// Wrapper around `SCWindow` from `ScreenCaptureKit`
9///
10/// Represents a window that can be captured.
11///
12/// # Examples
13///
14/// ```no_run
15/// use screencapturekit::shareable_content::SCShareableContent;
16///
17/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
18/// let content = SCShareableContent::get()?;
19/// for window in content.windows() {
20///     if let Some(title) = window.title() {
21///         println!("Window: {} (ID: {})", title, window.window_id());
22///     }
23/// }
24/// # Ok(())
25/// # }
26/// ```
27#[repr(transparent)]
28pub struct SCWindow(*const c_void);
29
30impl PartialEq for SCWindow {
31    fn eq(&self, other: &Self) -> bool {
32        self.0 == other.0
33    }
34}
35
36impl Eq for SCWindow {}
37
38impl std::hash::Hash for SCWindow {
39    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
40        self.0.hash(state);
41    }
42}
43
44impl SCWindow {
45    /// Create from raw pointer (used internally by shareable content)
46    pub(crate) unsafe fn from_ptr(ptr: *const c_void) -> Self {
47        Self(ptr)
48    }
49
50    /// Create from an FFI-owned (retained) pointer, returning `None` if null.
51    ///
52    /// # Safety
53    /// `ptr` must be null or a valid retained `SCWindow` pointer transferred
54    /// from the Swift FFI bridge (ownership moves into the returned wrapper).
55    pub(crate) unsafe fn from_retained_ptr(ptr: *const c_void) -> Option<Self> {
56        if ptr.is_null() {
57            None
58        } else {
59            Some(unsafe { Self::from_ptr(ptr) })
60        }
61    }
62
63    /// Get the raw pointer (used internally)
64    pub(crate) fn as_ptr(&self) -> *const c_void {
65        self.0
66    }
67
68    /// Get the owning application
69    pub fn owning_application(&self) -> Option<SCRunningApplication> {
70        unsafe {
71            let app_ptr = crate::ffi::sc_window_get_owning_application(self.0);
72            SCRunningApplication::from_retained_ptr(app_ptr)
73        }
74    }
75
76    /// Get the window ID
77    pub fn window_id(&self) -> u32 {
78        unsafe { crate::ffi::sc_window_get_window_id(self.0) }
79    }
80
81    /// Get the window frame (position and size)
82    pub fn frame(&self) -> CGRect {
83        let mut x = 0.0;
84        let mut y = 0.0;
85        let mut width = 0.0;
86        let mut height = 0.0;
87        unsafe {
88            crate::ffi::sc_window_get_frame_packed(
89                self.0,
90                &raw mut x,
91                &raw mut y,
92                &raw mut width,
93                &raw mut height,
94            );
95        }
96        CGRect::new(x, y, width, height)
97    }
98
99    /// Get the window title (if available)
100    pub fn title(&self) -> Option<String> {
101        unsafe { ffi_string_owned(|| crate::ffi::sc_window_get_title_owned(self.0)) }
102    }
103
104    /// Get window layer
105    pub fn window_layer(&self) -> i32 {
106        // FFI returns isize but window layer fits in i32
107        #[allow(clippy::cast_possible_truncation)]
108        unsafe {
109            crate::ffi::sc_window_get_window_layer(self.0) as i32
110        }
111    }
112
113    /// Check if window is on screen
114    pub fn is_on_screen(&self) -> bool {
115        unsafe { crate::ffi::sc_window_is_on_screen(self.0) }
116    }
117
118    /// Check if window is active (macOS 13.1+)
119    ///
120    /// With Stage Manager, a window can be offscreen but still active.
121    /// This property indicates whether the window is currently active,
122    /// regardless of its on-screen status.
123    #[cfg(feature = "macos_13_0")]
124    pub fn is_active(&self) -> bool {
125        unsafe { crate::ffi::sc_window_is_active(self.0) }
126    }
127}
128
129crate::utils::retained::sc_retained!(
130    SCWindow,
131    retain = crate::ffi::sc_window_retain,
132    release = crate::ffi::sc_window_release,
133);
134
135impl fmt::Debug for SCWindow {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        let mut debug = f.debug_struct("SCWindow");
138        debug
139            .field("window_id", &self.window_id())
140            .field("title", &self.title())
141            .field("frame", &self.frame())
142            .field("window_layer", &self.window_layer())
143            .field("is_on_screen", &self.is_on_screen());
144        #[cfg(feature = "macos_13_0")]
145        debug.field("is_active", &self.is_active());
146        debug.finish()
147    }
148}
149
150impl fmt::Display for SCWindow {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(
153            f,
154            "Window {} \"{}\" ({})",
155            self.window_id(),
156            self.title().unwrap_or_else(|| String::from("<untitled>")),
157            self.frame()
158        )
159    }
160}
161
162// SAFETY: `SCWindow` wraps an immutable Objective-C ScreenCaptureKit object.
163// ObjC reference counting is atomic and these accessor-only objects are safe to
164// send between and share across threads.
165unsafe impl Send for SCWindow {}
166unsafe impl Sync for SCWindow {}