screencapturekit/shareable_content/
mod.rs

1//! Shareable content types - displays, windows, and applications
2//!
3//! This module provides access to the system's displays, windows, and running
4//! applications that can be captured by `ScreenCaptureKit`.
5//!
6//! # Examples
7//!
8//! ```no_run
9//! use screencapturekit::shareable_content::SCShareableContent;
10//!
11//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
12//! // Get all shareable content
13//! let content = SCShareableContent::get()?;
14//!
15//! // List displays
16//! for display in content.displays() {
17//!     println!("Display {}: {}x{}",
18//!         display.display_id(),
19//!         display.width(),
20//!         display.height()
21//!     );
22//! }
23//!
24//! // List windows
25//! for window in content.windows() {
26//!     if let Some(title) = window.title() {
27//!         println!("Window: {}", title);
28//!     }
29//! }
30//!
31//! // List applications
32//! for app in content.applications() {
33//!     println!("App: {}", app.application_name());
34//! }
35//! # Ok(())
36//! # }
37//! ```
38
39pub mod display;
40pub mod running_application;
41pub mod window;
42pub use display::SCDisplay;
43pub use running_application::SCRunningApplication;
44pub use window::SCWindow;
45
46use crate::error::SCError;
47use crate::utils::sync_completion::{error_from_cstr, SyncCompletion};
48use core::fmt;
49use std::ffi::c_void;
50
51#[repr(transparent)]
52pub struct SCShareableContent(*const c_void);
53
54unsafe impl Send for SCShareableContent {}
55unsafe impl Sync for SCShareableContent {}
56
57/// Callback for shareable content retrieval
58extern "C" fn shareable_content_callback(
59    content_ptr: *const c_void,
60    error_ptr: *const i8,
61    user_data: *mut c_void,
62) {
63    if !error_ptr.is_null() {
64        let error = unsafe { error_from_cstr(error_ptr) };
65        unsafe { SyncCompletion::<SCShareableContent>::complete_err(user_data, error) };
66    } else if !content_ptr.is_null() {
67        let content = unsafe { SCShareableContent::from_ptr(content_ptr) };
68        unsafe { SyncCompletion::complete_ok(user_data, content) };
69    } else {
70        unsafe {
71            SyncCompletion::<SCShareableContent>::complete_err(
72                user_data,
73                "Unknown error".to_string(),
74            );
75        };
76    }
77}
78
79impl PartialEq for SCShareableContent {
80    fn eq(&self, other: &Self) -> bool {
81        self.0 == other.0
82    }
83}
84
85impl Eq for SCShareableContent {}
86
87impl std::hash::Hash for SCShareableContent {
88    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
89        self.0.hash(state);
90    }
91}
92
93impl Clone for SCShareableContent {
94    fn clone(&self) -> Self {
95        unsafe { Self(crate::ffi::sc_shareable_content_retain(self.0)) }
96    }
97}
98
99impl SCShareableContent {
100    /// Create from raw pointer (used internally)
101    ///
102    /// # Safety
103    /// The pointer must be a valid retained `SCShareableContent` pointer from Swift FFI.
104    pub(crate) unsafe fn from_ptr(ptr: *const c_void) -> Self {
105        Self(ptr)
106    }
107
108    /// Get shareable content (displays, windows, and applications)
109    ///
110    /// # Examples
111    ///
112    /// ```no_run
113    /// use screencapturekit::shareable_content::SCShareableContent;
114    ///
115    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
116    /// let content = SCShareableContent::get()?;
117    /// println!("Found {} displays", content.displays().len());
118    /// println!("Found {} windows", content.windows().len());
119    /// println!("Found {} apps", content.applications().len());
120    /// # Ok(())
121    /// # }
122    /// ```
123    ///
124    /// # Errors
125    ///
126    /// Returns an error if screen recording permission is not granted.
127    pub fn get() -> Result<Self, SCError> {
128        Self::with_options().get()
129    }
130
131    /// Create options builder for customizing shareable content retrieval
132    ///
133    /// # Examples
134    ///
135    /// ```no_run
136    /// use screencapturekit::shareable_content::SCShareableContent;
137    ///
138    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
139    /// let content = SCShareableContent::with_options()
140    ///     .on_screen_windows_only(true)
141    ///     .exclude_desktop_windows(true)
142    ///     .get()?;
143    /// # Ok(())
144    /// # }
145    /// ```
146    pub fn with_options() -> SCShareableContentOptions {
147        SCShareableContentOptions::default()
148    }
149
150    /// Get all available displays
151    ///
152    /// # Examples
153    ///
154    /// ```no_run
155    /// use screencapturekit::shareable_content::SCShareableContent;
156    ///
157    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
158    /// let content = SCShareableContent::get()?;
159    /// for display in content.displays() {
160    ///     println!("Display: {}x{}", display.width(), display.height());
161    /// }
162    /// # Ok(())
163    /// # }
164    /// ```
165    pub fn displays(&self) -> Vec<SCDisplay> {
166        unsafe {
167            let count = crate::ffi::sc_shareable_content_get_displays_count(self.0);
168            // FFI returns isize but count is always positive
169            #[allow(clippy::cast_sign_loss)]
170            let mut displays = Vec::with_capacity(count as usize);
171
172            for i in 0..count {
173                let display_ptr = crate::ffi::sc_shareable_content_get_display_at(self.0, i);
174                if !display_ptr.is_null() {
175                    displays.push(SCDisplay::from_ptr(display_ptr));
176                }
177            }
178
179            displays
180        }
181    }
182
183    /// Get all available windows
184    ///
185    /// # Examples
186    ///
187    /// ```no_run
188    /// use screencapturekit::shareable_content::SCShareableContent;
189    ///
190    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
191    /// let content = SCShareableContent::get()?;
192    /// for window in content.windows() {
193    ///     if let Some(title) = window.title() {
194    ///         println!("Window: {}", title);
195    ///     }
196    /// }
197    /// # Ok(())
198    /// # }
199    /// ```
200    pub fn windows(&self) -> Vec<SCWindow> {
201        unsafe {
202            let count = crate::ffi::sc_shareable_content_get_windows_count(self.0);
203            // FFI returns isize but count is always positive
204            #[allow(clippy::cast_sign_loss)]
205            let mut windows = Vec::with_capacity(count as usize);
206
207            for i in 0..count {
208                let window_ptr = crate::ffi::sc_shareable_content_get_window_at(self.0, i);
209                if !window_ptr.is_null() {
210                    windows.push(SCWindow::from_ptr(window_ptr));
211                }
212            }
213
214            windows
215        }
216    }
217
218    pub fn applications(&self) -> Vec<SCRunningApplication> {
219        unsafe {
220            let count = crate::ffi::sc_shareable_content_get_applications_count(self.0);
221            // FFI returns isize but count is always positive
222            #[allow(clippy::cast_sign_loss)]
223            let mut apps = Vec::with_capacity(count as usize);
224
225            for i in 0..count {
226                let app_ptr = crate::ffi::sc_shareable_content_get_application_at(self.0, i);
227                if !app_ptr.is_null() {
228                    apps.push(SCRunningApplication::from_ptr(app_ptr));
229                }
230            }
231
232            apps
233        }
234    }
235
236    pub fn as_ptr(&self) -> *const c_void {
237        self.0
238    }
239}
240
241impl Drop for SCShareableContent {
242    fn drop(&mut self) {
243        if !self.0.is_null() {
244            unsafe {
245                crate::ffi::sc_shareable_content_release(self.0);
246            }
247        }
248    }
249}
250
251impl fmt::Debug for SCShareableContent {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        f.debug_struct("SCShareableContent")
254            .field("displays", &self.displays().len())
255            .field("windows", &self.windows().len())
256            .field("applications", &self.applications().len())
257            .finish()
258    }
259}
260
261impl fmt::Display for SCShareableContent {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        write!(
264            f,
265            "SCShareableContent ({} displays, {} windows, {} applications)",
266            self.displays().len(),
267            self.windows().len(),
268            self.applications().len()
269        )
270    }
271}
272
273#[derive(Default, Debug, Clone, PartialEq, Eq)]
274pub struct SCShareableContentOptions {
275    exclude_desktop_windows: bool,
276    on_screen_windows_only: bool,
277}
278
279impl SCShareableContentOptions {
280    /// Exclude desktop windows from the shareable content.
281    ///
282    /// When set to `true`, desktop-level windows (like the desktop background)
283    /// are excluded from the returned window list.
284    #[must_use]
285    pub fn exclude_desktop_windows(mut self, exclude: bool) -> Self {
286        self.exclude_desktop_windows = exclude;
287        self
288    }
289
290    /// Include only on-screen windows in the shareable content.
291    ///
292    /// When set to `true`, only windows that are currently visible on screen
293    /// are included. Minimized or off-screen windows are excluded.
294    #[must_use]
295    pub fn on_screen_windows_only(mut self, on_screen_only: bool) -> Self {
296        self.on_screen_windows_only = on_screen_only;
297        self
298    }
299
300    /// Get shareable content synchronously
301    ///
302    /// This blocks until the content is retrieved.
303    ///
304    /// # Errors
305    ///
306    /// Returns an error if screen recording permission is not granted or retrieval fails.
307    pub fn get(self) -> Result<SCShareableContent, SCError> {
308        let (completion, context) = SyncCompletion::<SCShareableContent>::new();
309
310        unsafe {
311            crate::ffi::sc_shareable_content_get_with_options(
312                self.exclude_desktop_windows,
313                self.on_screen_windows_only,
314                shareable_content_callback,
315                context,
316            );
317        }
318
319        completion.wait().map_err(SCError::NoShareableContent)
320    }
321}