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}