screencapturekit/
screenshot_manager.rs

1//! `SCScreenshotManager` - Single-shot screenshot capture
2//!
3//! Available on macOS 14.0+
4//! Provides high-quality screenshot capture without the overhead of setting up a stream.
5
6use crate::error::SCError;
7use crate::stream::configuration::SCStreamConfiguration;
8use crate::stream::content_filter::SCContentFilter;
9use crate::utils::sync_completion::{error_from_cstr, SyncCompletion};
10use std::ffi::c_void;
11
12extern "C" fn image_callback(
13    image_ptr: *const c_void,
14    error_ptr: *const i8,
15    user_data: *mut c_void,
16) {
17    if !error_ptr.is_null() {
18        let error = unsafe { error_from_cstr(error_ptr) };
19        unsafe { SyncCompletion::<CGImage>::complete_err(user_data, error) };
20    } else if !image_ptr.is_null() {
21        unsafe { SyncCompletion::complete_ok(user_data, CGImage::from_ptr(image_ptr)) };
22    } else {
23        unsafe { SyncCompletion::<CGImage>::complete_err(user_data, "Unknown error".to_string()) };
24    }
25}
26
27extern "C" fn buffer_callback(
28    buffer_ptr: *const c_void,
29    error_ptr: *const i8,
30    user_data: *mut c_void,
31) {
32    if !error_ptr.is_null() {
33        let error = unsafe { error_from_cstr(error_ptr) };
34        unsafe { SyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(user_data, error) };
35    } else if !buffer_ptr.is_null() {
36        let buffer = unsafe { crate::cm::CMSampleBuffer::from_ptr(buffer_ptr.cast_mut()) };
37        unsafe { SyncCompletion::complete_ok(user_data, buffer) };
38    } else {
39        unsafe {
40            SyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(
41                user_data,
42                "Unknown error".to_string(),
43            );
44        };
45    }
46}
47
48/// `CGImage` wrapper for screenshots
49///
50/// Represents a Core Graphics image returned from screenshot capture.
51///
52/// # Examples
53///
54/// ```no_run
55/// # use screencapturekit::screenshot_manager::SCScreenshotManager;
56/// # use screencapturekit::stream::{content_filter::SCContentFilter, configuration::SCStreamConfiguration};
57/// # use screencapturekit::shareable_content::SCShareableContent;
58/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
59/// let content = SCShareableContent::get()?;
60/// let display = &content.displays()[0];
61/// let filter = SCContentFilter::builder().display(display).exclude_windows(&[]).build();
62/// let config = SCStreamConfiguration::default();
63///
64/// let image = SCScreenshotManager::capture_image(&filter, &config)?;
65/// println!("Screenshot size: {}x{}", image.width(), image.height());
66/// # Ok(())
67/// # }
68/// ```
69pub struct CGImage {
70    ptr: *const c_void,
71}
72
73impl CGImage {
74    pub(crate) fn from_ptr(ptr: *const c_void) -> Self {
75        Self { ptr }
76    }
77
78    /// Get image width in pixels
79    ///
80    /// # Examples
81    ///
82    /// ```no_run
83    /// # use screencapturekit::screenshot_manager::SCScreenshotManager;
84    /// # use screencapturekit::stream::{content_filter::SCContentFilter, configuration::SCStreamConfiguration};
85    /// # use screencapturekit::shareable_content::SCShareableContent;
86    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
87    /// # let content = SCShareableContent::get()?;
88    /// # let display = &content.displays()[0];
89    /// # let filter = SCContentFilter::build().display(display).exclude_windows(&[]).build();
90    /// # let config = SCStreamConfiguration::default();
91    /// let image = SCScreenshotManager::capture_image(&filter, &config)?;
92    /// let width = image.width();
93    /// println!("Width: {}", width);
94    /// # Ok(())
95    /// # }
96    /// ```
97    #[must_use]
98    pub fn width(&self) -> usize {
99        unsafe { crate::ffi::cgimage_get_width(self.ptr) }
100    }
101
102    /// Get image height in pixels
103    #[must_use]
104    pub fn height(&self) -> usize {
105        unsafe { crate::ffi::cgimage_get_height(self.ptr) }
106    }
107
108    #[must_use]
109    pub fn as_ptr(&self) -> *const c_void {
110        self.ptr
111    }
112
113    /// Get raw RGBA pixel data
114    ///
115    /// Returns a vector containing RGBA bytes (4 bytes per pixel).
116    /// The data is in row-major order.
117    ///
118    /// # Errors
119    /// Returns an error if the pixel data cannot be extracted
120    pub fn get_rgba_data(&self) -> Result<Vec<u8>, SCError> {
121        let mut data_ptr: *const u8 = std::ptr::null();
122        let mut data_length: usize = 0;
123
124        let success = unsafe {
125            crate::ffi::cgimage_get_data(
126                self.ptr,
127                std::ptr::addr_of_mut!(data_ptr),
128                std::ptr::addr_of_mut!(data_length),
129            )
130        };
131
132        if !success || data_ptr.is_null() {
133            return Err(SCError::internal_error(
134                "Failed to extract pixel data from CGImage",
135            ));
136        }
137
138        // Copy the data into a Vec
139        let data = unsafe { std::slice::from_raw_parts(data_ptr, data_length).to_vec() };
140
141        // Free the allocated data
142        unsafe {
143            crate::ffi::cgimage_free_data(data_ptr.cast_mut());
144        }
145
146        Ok(data)
147    }
148}
149
150impl Drop for CGImage {
151    fn drop(&mut self) {
152        if !self.ptr.is_null() {
153            unsafe {
154                crate::ffi::cgimage_release(self.ptr);
155            }
156        }
157    }
158}
159
160unsafe impl Send for CGImage {}
161unsafe impl Sync for CGImage {}
162
163/// Manager for capturing single screenshots
164///
165/// Available on macOS 14.0+. Provides a simpler API than `SCStream` for one-time captures.
166///
167/// # Examples
168///
169/// ```no_run
170/// use screencapturekit::screenshot_manager::SCScreenshotManager;
171/// use screencapturekit::stream::{content_filter::SCContentFilter, configuration::SCStreamConfiguration};
172/// use screencapturekit::shareable_content::SCShareableContent;
173///
174/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
175/// let content = SCShareableContent::get()?;
176/// let display = &content.displays()[0];
177/// let filter = SCContentFilter::builder().display(display).exclude_windows(&[]).build();
178/// let mut config = SCStreamConfiguration::default();
179/// config.set_width(1920);
180/// config.set_height(1080);
181///
182/// let image = SCScreenshotManager::capture_image(&filter, &config)?;
183/// println!("Captured screenshot: {}x{}", image.width(), image.height());
184/// # Ok(())
185/// # }
186/// ```
187pub struct SCScreenshotManager;
188
189impl SCScreenshotManager {
190    /// Capture a single screenshot as a `CGImage`
191    ///
192    /// # Errors
193    /// Returns an error if:
194    /// - The system is not macOS 14.0+
195    /// - Screen recording permission is not granted
196    /// - The capture fails for any reason
197    ///
198    /// # Panics
199    /// Panics if the internal mutex is poisoned.
200    pub fn capture_image(
201        content_filter: &SCContentFilter,
202        configuration: &SCStreamConfiguration,
203    ) -> Result<CGImage, SCError> {
204        let (completion, context) = SyncCompletion::<CGImage>::new();
205
206        unsafe {
207            crate::ffi::sc_screenshot_manager_capture_image(
208                content_filter.as_ptr(),
209                configuration.as_ptr(),
210                image_callback,
211                context,
212            );
213        }
214
215        completion.wait().map_err(SCError::ScreenshotError)
216    }
217
218    /// Capture a single screenshot as a `CMSampleBuffer`
219    ///
220    /// Returns the sample buffer for advanced processing.
221    ///
222    /// # Errors
223    /// Returns an error if:
224    /// - The system is not macOS 14.0+
225    /// - Screen recording permission is not granted
226    /// - The capture fails for any reason
227    ///
228    /// # Panics
229    /// Panics if the internal mutex is poisoned.
230    pub fn capture_sample_buffer(
231        content_filter: &SCContentFilter,
232        configuration: &SCStreamConfiguration,
233    ) -> Result<crate::cm::CMSampleBuffer, SCError> {
234        let (completion, context) = SyncCompletion::<crate::cm::CMSampleBuffer>::new();
235
236        unsafe {
237            crate::ffi::sc_screenshot_manager_capture_sample_buffer(
238                content_filter.as_ptr(),
239                configuration.as_ptr(),
240                buffer_callback,
241                context,
242            );
243        }
244
245        completion.wait().map_err(SCError::ScreenshotError)
246    }
247}