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}