screencapturekit/output/
iosurface.rs

1//! `IOSurface` wrapper for `ScreenCaptureKit`
2//!
3//! Provides access to IOSurface-backed pixel buffers for efficient frame processing
4
5use std::ffi::c_void;
6use std::io;
7
8/// Lock options for `IOSurface`
9#[repr(u32)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum IOSurfaceLockOptions {
12    /// Read-only lock
13    ReadOnly = 0x0000_0001,
14    /// Avoid synchronization
15    AvoidSync = 0x0000_0002,
16}
17
18impl IOSurfaceLockOptions {
19    pub const fn as_u32(self) -> u32 {
20        self as u32
21    }
22}
23
24/// A guard that provides access to locked `IOSurface` memory
25///
26/// The surface is automatically unlocked when this guard is dropped.
27pub struct IOSurfaceLockGuard<'a> {
28    surface_ptr: *const c_void,
29    base_address: std::ptr::NonNull<u8>,
30    width: usize,
31    height: usize,
32    bytes_per_row: usize,
33    options: IOSurfaceLockOptions,
34    _phantom: std::marker::PhantomData<&'a IOSurface>,
35}
36
37impl IOSurfaceLockGuard<'_> {
38    /// Create a new lock guard (used internally)
39    pub(crate) unsafe fn new(
40        surface_ptr: *const c_void,
41        options: IOSurfaceLockOptions,
42        width: usize,
43        height: usize,
44        bytes_per_row: usize,
45    ) -> Result<Self, i32> {
46        // Lock the surface
47        let result = crate::ffi::iosurface_lock(surface_ptr, options.as_u32());
48        if result != 0 {
49            return Err(result);
50        }
51
52        // Get base address
53        let base_ptr = crate::ffi::iosurface_get_base_address(surface_ptr);
54        let base_address = std::ptr::NonNull::new(base_ptr).ok_or(-1)?;
55
56        Ok(Self {
57            surface_ptr,
58            base_address,
59            width,
60            height,
61            bytes_per_row,
62            options,
63            _phantom: std::marker::PhantomData,
64        })
65    }
66
67    /// Get the width in pixels
68    pub const fn width(&self) -> usize {
69        self.width
70    }
71
72    /// Get the height in pixels
73    pub const fn height(&self) -> usize {
74        self.height
75    }
76
77    /// Get bytes per row
78    pub const fn bytes_per_row(&self) -> usize {
79        self.bytes_per_row
80    }
81
82    /// Get raw pointer to buffer data
83    pub const fn as_ptr(&self) -> *const u8 {
84        self.base_address.as_ptr()
85    }
86
87    /// Get mutable raw pointer to buffer data
88    pub fn as_mut_ptr(&mut self) -> *mut u8 {
89        self.base_address.as_ptr()
90    }
91
92    /// Get buffer data as a byte slice
93    pub fn as_slice(&self) -> &[u8] {
94        unsafe {
95            std::slice::from_raw_parts(self.base_address.as_ptr(), self.height * self.bytes_per_row)
96        }
97    }
98
99    /// Get a specific row as a slice
100    pub fn row(&self, row_index: usize) -> Option<&[u8]> {
101        if row_index >= self.height {
102            return None;
103        }
104        unsafe {
105            let row_ptr = self
106                .base_address
107                .as_ptr()
108                .add(row_index * self.bytes_per_row);
109            Some(std::slice::from_raw_parts(row_ptr, self.bytes_per_row))
110        }
111    }
112
113    /// Access buffer with a standard `std::io::Cursor`
114    ///
115    /// Returns a cursor over the buffer data that implements `Read` and `Seek` traits.
116    ///
117    /// # Examples
118    ///
119    /// ```no_run
120    /// use std::io::{Read, Seek, SeekFrom};
121    /// use screencapturekit::output::PixelBufferCursorExt;
122    /// # use screencapturekit::output::IOSurfaceLockOptions;
123    ///
124    /// # fn example(guard: screencapturekit::output::IOSurfaceLockGuard) {
125    /// let mut cursor = guard.cursor();
126    ///
127    /// // Read a pixel using the extension trait
128    /// let pixel = cursor.read_pixel().unwrap();
129    ///
130    /// // Or use standard Read trait
131    /// let mut buf = [0u8; 4];
132    /// cursor.read_exact(&mut buf).unwrap();
133    /// # }
134    /// ```
135    pub fn cursor(&self) -> io::Cursor<&[u8]> {
136        io::Cursor::new(self.as_slice())
137    }
138
139    /// Access buffer with a cursor using a closure (for backward compatibility)
140    ///
141    /// This method is provided for backward compatibility. Consider using
142    /// `cursor()` directly for more flexibility.
143    pub fn with_cursor<F, R>(&self, f: F) -> R
144    where
145        F: FnOnce(io::Cursor<&[u8]>) -> R,
146    {
147        f(self.cursor())
148    }
149}
150
151impl Drop for IOSurfaceLockGuard<'_> {
152    fn drop(&mut self) {
153        unsafe {
154            crate::ffi::iosurface_unlock(self.surface_ptr, self.options.as_u32());
155        }
156    }
157}
158
159impl std::ops::Deref for IOSurfaceLockGuard<'_> {
160    type Target = [u8];
161
162    fn deref(&self) -> &Self::Target {
163        self.as_slice()
164    }
165}
166
167/// Wrapper around `IOSurface`
168///
169/// `IOSurface` is a framebuffer object suitable for sharing across process boundaries.
170/// `ScreenCaptureKit` uses `IOSurface`-backed `CVPixelBuffer`s for efficient frame delivery.
171pub struct IOSurface(*const c_void);
172
173impl PartialEq for IOSurface {
174    fn eq(&self, other: &Self) -> bool {
175        self.0 == other.0
176    }
177}
178
179impl Eq for IOSurface {}
180
181impl std::hash::Hash for IOSurface {
182    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
183        self.0.hash(state);
184    }
185}
186
187impl IOSurface {
188    /// Create from raw pointer (used internally)
189    pub(crate) unsafe fn from_ptr(ptr: *const c_void) -> Option<Self> {
190        if ptr.is_null() {
191            None
192        } else {
193            Some(Self(ptr))
194        }
195    }
196
197    /// Get the width of the `IOSurface` in pixels
198    pub fn width(&self) -> usize {
199        // FFI returns isize but dimensions are always positive
200        #[allow(clippy::cast_sign_loss)]
201        unsafe {
202            crate::ffi::iosurface_get_width(self.0) as usize
203        }
204    }
205
206    /// Get the height of the `IOSurface` in pixels
207    pub fn height(&self) -> usize {
208        // FFI returns isize but dimensions are always positive
209        #[allow(clippy::cast_sign_loss)]
210        unsafe {
211            crate::ffi::iosurface_get_height(self.0) as usize
212        }
213    }
214
215    /// Get the number of bytes per row
216    pub fn bytes_per_row(&self) -> usize {
217        // FFI returns isize but byte count is always positive
218        #[allow(clippy::cast_sign_loss)]
219        unsafe {
220            crate::ffi::iosurface_get_bytes_per_row(self.0) as usize
221        }
222    }
223
224    /// Get the pixel format (OSType/FourCC)
225    pub fn pixel_format(&self) -> u32 {
226        unsafe { crate::ffi::iosurface_get_pixel_format(self.0) }
227    }
228
229    /// Get the base address of the `IOSurface` buffer
230    ///
231    /// **Important:** You must lock the `IOSurface` before accessing memory!
232    ///
233    /// # Safety
234    ///
235    /// The returned pointer is only valid while the `IOSurface` is locked.
236    /// Accessing unlocked memory or after unlock is undefined behavior.
237    pub unsafe fn base_address(&self) -> *mut u8 {
238        crate::ffi::iosurface_get_base_address(self.0)
239    }
240
241    /// Lock the `IOSurface` and get a guard for safe access
242    ///
243    /// The surface will be automatically unlocked when the guard is dropped.
244    ///
245    /// # Errors
246    ///
247    /// Returns an `IOSurface` error code if the lock operation fails.
248    ///
249    /// # Example
250    ///
251    /// ```no_run
252    /// # use screencapturekit::output::{IOSurface, IOSurfaceLockOptions};
253    /// # fn example(surface: IOSurface) -> Result<(), i32> {
254    /// let guard = surface.lock(IOSurfaceLockOptions::ReadOnly)?;
255    /// let data = guard.as_slice();
256    /// // Use data...
257    /// // Surface is automatically unlocked when guard goes out of scope
258    /// # Ok(())
259    /// # }
260    /// ```
261    pub fn lock(&self, options: IOSurfaceLockOptions) -> Result<IOSurfaceLockGuard<'_>, i32> {
262        unsafe {
263            IOSurfaceLockGuard::new(
264                self.0,
265                options,
266                self.width(),
267                self.height(),
268                self.bytes_per_row(),
269            )
270        }
271    }
272
273    /// Check if the `IOSurface` is currently in use
274    pub fn is_in_use(&self) -> bool {
275        unsafe { crate::ffi::iosurface_is_in_use(self.0) }
276    }
277}
278
279impl Drop for IOSurface {
280    fn drop(&mut self) {
281        if !self.0.is_null() {
282            unsafe {
283                crate::ffi::iosurface_release(self.0);
284            }
285        }
286    }
287}
288
289unsafe impl Send for IOSurface {}
290unsafe impl Sync for IOSurface {}
291
292impl std::fmt::Debug for IOSurface {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        f.debug_struct("IOSurface")
295            .field("width", &self.width())
296            .field("height", &self.height())
297            .field("bytes_per_row", &self.bytes_per_row())
298            .field("pixel_format", &format!("0x{:08X}", self.pixel_format()))
299            .field("is_in_use", &self.is_in_use())
300            .finish()
301    }
302}
303
304/// Extension trait for `CVPixelBuffer` to access `IOSurface`
305pub trait CVPixelBufferIOSurface {
306    /// Get the underlying `IOSurface` if the pixel buffer is backed by one
307    fn iosurface(&self) -> Option<IOSurface>;
308
309    /// Check if this pixel buffer is backed by an `IOSurface`
310    fn is_backed_by_iosurface(&self) -> bool;
311}
312
313impl CVPixelBufferIOSurface for crate::output::CVPixelBuffer {
314    fn iosurface(&self) -> Option<IOSurface> {
315        unsafe {
316            let ptr = crate::ffi::cv_pixel_buffer_get_iosurface(self.as_ptr());
317            IOSurface::from_ptr(ptr)
318        }
319    }
320
321    fn is_backed_by_iosurface(&self) -> bool {
322        unsafe { crate::ffi::cv_pixel_buffer_is_backed_by_iosurface(self.as_ptr()) }
323    }
324}