screencapturekit-rs

repository·main·Indexed 42 days ago

https://github.com/doom-fish/screencapturekit-rs

Safe, idiomatic Rust bindings for Apple's ScreenCaptureKit framework, enabling high-performance screen, window, and application capture on macOS 13.0+. Version 10.0.3 provides support for audio capture, screenshots, and Metal integration, with cumulative feature flags for macOS versions up to 26.0.

Tokens
34.2K
Snippets
79
Records
106
Agent score
54%

What's inside screencapturekit-rs

  1. Understand the Swift Bridge structure

    main

    The screencapturekit-rs project uses a Swift bridge to expose macOS's ScreenCaptureKit to Rust via C-compatible symbols (@_cdecl). The bridge is organized into several functional domains:

    • Capture Control: Stream.swift handles stream lifecycle.
    • Content Discovery: ShareableContent.swift manages finding available content.
    • Configuration: StreamConfiguration.swift defines stream settings.
    • Advanced Features: ScreenshotManager.swift (macOS 14+), RecordingOutput.swift (macOS 15+), and ContentSharingPicker.swift (macOS 14+).
    • Data Types: Provides bindings for CoreGraphics (CGImage, CGRect), CoreMedia (CMSampleBuffer, CMTime), CoreVideo (CVPixelBuffer), IOSurface, and Dispatch (DispatchQueue).
  2. Understand ScreenCaptureKit SDK coverage

    main

    The screencapturekit crate provides Rust bindings for Apple's ScreenCaptureKit.framework.

    Important Caveat on Coverage: When a type is marked as "Bound", it means the crate exposes the top-level declaration (class, protocol, enum, or constant). It does not guarantee that every single property or method of that type is implemented in Rust. If you encounter a missing property on a bound type, it may be a gap in the bindings.

    Audited Surface Areas include:

    • SCStream, SCStreamConfiguration, SCContentFilter
    • SCShareableContent, SCShareableContentInfo, SCRunningApplication, SCDisplay, SCWindow
    • SCContentSharingPicker, SCContentSharingPickerConfiguration, SCContentSharingPickerMode
    • SCRecordingOutput, SCRecordingOutputConfiguration
    • SCScreenshotManager (macOS 15.2+ additions)
    • Error types: SCStreamErrorCode, SCStreamErrorDomain
  3. Understand the screencapturekit-rs coverage and limitations

    main

    The screencapturekit crate provides Rust bindings for the macOS ScreenCaptureKit SDK.

    Important Coverage Note: Coverage metrics refer to declaration-level coverage, not member-level coverage. This means that while a top-level SDK declaration (like a class, protocol, or enum) may have a corresponding Rust binding, not every property or method on that type is necessarily reachable or implemented in Rust.

    If you encounter a property or method that exists in the macOS SDK but is missing in the Rust API, it is likely due to these implementation gaps.

  4. Align Rust data structures with Metal shaders

    main

    When passing data from Rust to Metal shaders (as seen in the full_metal_app example), you must ensure memory alignment matches the Metal expectations. Use #[repr(C)] and include explicit padding for types to maintain 16-byte alignment for uniforms.

    // Rust struct matching Metal shader vertex input
    #[repr(C)]
    struct Vertex {
        position: [f32; 2],  // 8 bytes - matches packed_float2
        color: [f32; 4],     // 16 bytes - matches packed_float4
    }
    
    // Uniforms with explicit padding for 16-byte alignment
    #[repr(C)]
    struct Uniforms {
        viewport_size: [f32; 2],  // 8 bytes
        time: f32,                // 4 bytes
        _padding: f32,            // 4 bytes (16-byte alignment)
    }
  5. Use SCShareableContentStyle instead of the deprecated SCStreamType

    main
    The SCStreamType enum is deprecated by Apple in favor of SCShareableContentStyle (starting in macOS 15.0). While the crate still exposes stream::content_filter::SCStreamType for backward compatibility, you should use stream::content_filter::SCShareableContentStyle for modern macOS targets.
  6. How async stream lifecycle methods work in unreleased versions

    main

    In upcoming unreleased versions, the async stream lifecycle methods are transitioning from blocking calls to true asynchronous operations.

    Previously, methods like start_capture, stop_capture, update_configuration, and update_content_filter blocked the calling thread on a condition variable. They now return a StreamControlFuture that must be .awaited.

    This change ensures that awaiting these methods parks the task via its Waker and resumes from the Swift completion callback, preventing the executor from stalling.

    Async Usage

    stream.start_capture().await?;
    stream.stop_capture().await?;
    stream.update_configuration(&config).await?;
    stream.update_content_filter(&filter).await?;

    Synchronous/Blocking Usage

    If you need to call these methods from synchronous code without awaiting, you can access the underlying synchronous stream:

    stream.inner().start_capture();
    // Async lifecycle methods
    stream.start_capture().await?;
    stream.stop_capture().await?;
    stream.update_configuration(&config).await?;
    stream.update_content_filter(&filter).await?;
  7. Access frame metadata via SCStreamFrameInfo

    main

    Apple models frame metadata as attachment keys on CMSampleBuffer. Instead of requiring you to use raw string constants, screencapturekit provides a more ergonomic Rust layer.

    It exposes these keys as typed accessors and a batched FrameInfo snapshot. You can access properties such as:

    • frame_status
    • display_time
    • scale_factor
    • content_scale
    • content_rect
    • bounding_rect
    • screen_rect
    • presenter_overlay_content_rect
    • dirty_rects

    Use the frame_info() method to retrieve a batched snapshot of this data.

  8. Use Zero-Copy GPU access with IOSurface

    main

    To avoid CPU copies when working with Metal or GPU pipelines, use IOSurface to create textures directly from the pixel buffer.

    if let Some(surface) = pixel_buffer.io_surface() {
        // Create Metal texture directly from IOSurface
        let texture = device.create_texture_from_iosurface(&surface);
    }
  9. Memory Management in the Swift Bridge

    main

    Because the bridge crosses the FFI boundary between Swift and Rust, manual memory management is required using the following primitives:

    • retain<T>(_:): Use this to pass a retained object to Rust.
    • unretained<T>(_:): Use this to get a reference from a Rust pointer.
    • release(_:): Use this to release an object currently held by Rust.
    retain<T>(_:)     // Pass retained object to Rust
    unretained<T>(_:) // Get reference from Rust pointer  
    release(_:)       // Release Rust-held object
  10. Understand the binding coverage and limitations of screencapturekit-rs

    main

    The screencapturekit crate provides Rust bindings for the macOS ScreenCaptureKit SDK. While the crate aims for high declaration-level coverage (matching top-level classes, protocols, and enums), it is important to note that not every property and method on a bound type is necessarily reachable.

    Coverage is measured at the declaration level. For example, a class might be bound, but specific methods within that class might not yet have corresponding Rust implementations. Always check the specific module documentation for the availability of individual members.

  11. Achieve zero-copy GPU access via IOSurface

    main

    To avoid CPU copies when using Metal or wgpu, access the underlying IOSurface from the CMSampleBuffer.

    1. Get the pixel_buffer from the CMSampleBuffer.
    2. Get the io_surface from the pixel_buffer.
    3. Wrap the surface as a MTLTexture (Metal) or use it in wgpu.

    Built-in Metal helpers are available in screencapturekit::metal.

    use screencapturekit::prelude::*;
    
    impl SCStreamOutputTrait for H {
        fn did_output_sample_buffer(&self, sample: CMSampleBuffer, _: SCStreamOutputType) {
            if let Some(pb) = sample.pixel_buffer() {
                if let Some(surface) = pb.io_surface() {
                    let _ = (surface.width(), surface.height());
                    // Wrap as `MTLTexture` — no copy.
                }
            }
        }
    }