MCPcopy Index your code
hub / github.com/doom-fish/screencapturekit-rs

github.com/doom-fish/screencapturekit-rs @v8.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v8.0.0 ↗ · + Follow
1,758 symbols 6,050 edges 125 files 366 documented · 21%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

ScreenCaptureKit-rs

Safe, idiomatic Rust bindings for Apple's ScreenCaptureKit framework.

Capture screens, windows, and applications on macOS 12.3+ with high performance and low overhead.

<a href="https://crates.io/crates/screencapturekit"><img alt="Crates.io" src="https://img.shields.io/crates/v/screencapturekit?style=for-the-badge&logo=rust&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41" /></a>
<a href="https://crates.io/crates/screencapturekit"><img alt="Crates.io Downloads" src="https://img.shields.io/crates/d/screencapturekit?style=for-the-badge&logo=rust&color=A6E3A1&logoColor=D9E0EE&labelColor=302D41" /></a>
<a href="https://docs.rs/screencapturekit"><img alt="docs.rs" src="https://img.shields.io/docsrs/screencapturekit?style=for-the-badge&logo=docs.rs&color=8bd5ca&logoColor=D9E0EE&labelColor=302D41" /></a>
<a href="https://github.com/doom-fish/screencapturekit-rs#license"><img alt="License" src="https://img.shields.io/crates/l/screencapturekit?style=for-the-badge&logo=apache&color=ee999f&logoColor=D9E0EE&labelColor=302D41" /></a>
<a href="https://github.com/doom-fish/screencapturekit-rs/actions"><img alt="Build Status" src="https://img.shields.io/github/actions/workflow/status/doom-fish/screencapturekit-rs/ci.yml?branch=main&style=for-the-badge&logo=github&color=c69ff5&logoColor=D9E0EE&labelColor=302D41" /></a>
<a href="https://github.com/doom-fish/screencapturekit-rs/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/doom-fish/screencapturekit-rs?style=for-the-badge&logo=starship&color=F5E0DC&logoColor=D9E0EE&labelColor=302D41" /></a>

💼 Looking for a hosted desktop recording API? Check out Recall.ai — an API for recording Zoom, Google Meet, Microsoft Teams, in-person meetings, and more.

https://github.com/user-attachments/assets/8a272c48-7ec3-4132-9111-4602b4fa991d


Highlights

  • 🎥 Screen, window, and app capture with a clean builder-pattern API
  • 🔊 System audio + microphone capture (macOS 13.0+ / 15.0+)
  • Real-time, zero-copy frame delivery via IOSurface / Metal
  • 🔄 Async support that works with any executor (Tokio, async-std, smol, …) — fully waker-based, never blocks your runtime
  • 📸 Screenshots + direct-to-file recording (macOS 14.0+ / 15.0+)
  • 🖱️ System content picker UI (macOS 14.0+)
  • 🛡️ Memory safe — proper retain/release, leak-tested
  • 📦 Minimal dependencies — only the thin apple-cf / apple-metal binding crates (plus trait-only futures-core when the async feature is on; no heavy third-party runtime deps)

Table of Contents


Install

[dependencies]
screencapturekit = "8"

Opt-in features (additive):

Feature Enables
async Runtime-agnostic async API (Tokio / async-std / smol / …)
macos_13_0 Audio capture, sync clock
macos_14_0 Screenshots, content picker, content info
macos_14_2 Menu bar capture, child windows, presenter overlay
macos_14_4 Current-process shareable content
macos_15_0 Recording output, HDR capture, microphone
macos_15_2 Screenshot in rect, stream active/inactive delegates
macos_26_0 Advanced screenshot config, HDR screenshot output

macos_* features are cumulative — enabling macos_15_0 automatically enables every earlier version. Pick the highest version your minimum-supported macOS will satisfy:

screencapturekit = { version = "8", features = ["async", "macos_15_0"] }

Upgrading a major version? See docs/MIGRATION.md for a per-version guide. Releases 3.0–6.0 consolidated the Core Graphics / Core Media foundation types onto the shared apple-cf crate and 7.0 hardens the FFI boundary; the only likely source change across that line is 5.0's nested CGRect layout (rect.origin.x / rect.size.width). 8.0 makes the async stream lifecycle methods real futures — add .await to AsyncSCStream::{start,stop}_capture and update_* (see below).

Quick Start

A minimal screen capture in ~25 lines. Everything else builds on these four steps: (1) list shareable content, (2) build a content filter, (3) configure the stream, (4) add an output handler and start.

```rust,no_run use screencapturekit::prelude::*;

struct Handler; impl SCStreamOutputTrait for Handler { fn did_output_sample_buffer(&self, sample: CMSampleBuffer, _: SCStreamOutputType) { println!("📹 frame @ {:?}", sample.presentation_timestamp()); } }

fn main() -> Result<(), Box> { let content = SCShareableContent::get()?; let display = &content.displays()[0];

let filter = SCContentFilter::create()
    .with_display(display)
    .with_excluding_windows(&[])
    .build();

let config = SCStreamConfiguration::new()
    .with_width(1920)
    .with_height(1080)
    .with_pixel_format(PixelFormat::BGRA);

let mut stream = SCStream::new(&filter, &config);
stream.add_output_handler(Handler, SCStreamOutputType::Screen);
stream.start_capture()?;

std::thread::sleep(std::time::Duration::from_secs(5));
stream.stop_capture()?;
Ok(())

}


> Output / delegate handlers must be `Send + Sync` — Apple's dispatch
> queues may invoke them concurrently from arbitrary threads.

Permission required — see [Requirements & Permissions](#requirements--permissions).
Run it: `cargo run --example 01_basic_capture`.

## Recipes

Short snippets for the most common follow-on tasks. Every recipe is a runnable
example in [`examples/`](examples/) — see the [Examples](#examples) table.






<strong>Window capture with audio</strong>



```rust,no_run
use screencapturekit::prelude::*;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = SCShareableContent::get()?;
let window = content.windows().into_iter()
    .find(|w| w.title().as_deref() == Some("Safari"))
    .ok_or("Safari window not found")?;

let filter = SCContentFilter::create().with_window(&window).build();
let config = SCStreamConfiguration::new()
    .with_captures_audio(true)
    .with_sample_rate(48_000)
    .with_channel_count(2);

let mut stream = SCStream::new(&filter, &config);
// stream.add_output_handler(...) for Screen and/or Audio
stream.start_capture()?;
# Ok(()) }

Closure-based handler (no trait impl needed)

```rust,no_run

use screencapturekit::prelude::*;

fn example(stream: &mut SCStream) {

stream.add_output_handler( |sample: CMSampleBuffer, _of_type: SCStreamOutputType| { println!("📹 frame @ {:?}", sample.presentation_timestamp()); }, SCStreamOutputType::Screen, );

}


Closures must be `Fn + Send + Sync + 'static`.









<strong>Async capture (any executor)</strong>



```rust,ignore
use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
use screencapturekit::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = AsyncSCShareableContent::get().await?;
    let display = &content.displays()[0];

    let filter = SCContentFilter::create()
        .with_display(display).with_excluding_windows(&[]).build();
    let config = SCStreamConfiguration::new().with_width(1920).with_height(1080);

    // 30-frame ring buffer; oldest frames are dropped if the consumer can't keep up.
    let stream = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen);
    // start/stop/update are real futures — awaiting parks the task via its
    // Waker and never blocks the executor thread.
    stream.start_capture().await?;

    while let Some(_frame) = stream.next().await {
        // process frame
        # break;
    }

    stream.stop_capture().await?;
    // Distinguish a normal end from an error stop (display gone, perms revoked, …):
    if let Some(err) = stream.take_error() {
        eprintln!("stream stopped with error: {err}");
    }
    Ok(())
}

Requires the async feature. Works with Tokio, async-std, smol, or any custom executor — the binding does not spawn its own runtime, and the lifecycle methods are waker-based so they never block the executor. AsyncSCStream also exposes frames() / frames_typed() as futures::Streams, so you can use the StreamExt combinators (take, map, filter, collect, …):

```rust,ignore use futures_util::StreamExt; let first_30: Vec<_> = stream.frames().take(30).collect().await;










<strong>Async audio + video from one stream</strong>



```rust,ignore
use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
use screencapturekit::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = AsyncSCShareableContent::get().await?;
    let display = &content.displays()[0];
    let filter = SCContentFilter::create()
        .with_display(display).with_excluding_windows(&[]).build();
    // Enable audio in the configuration …
    let config = SCStreamConfiguration::new()
        .with_width(1920).with_height(1080)
        .with_captures_audio(true);

    let mut stream = AsyncSCStream::new(&filter, &config, 32, SCStreamOutputType::Screen);
    // … then register audio as a second output type on the SAME stream.
    stream.add_output_type(SCStreamOutputType::Audio);
    stream.start_capture().await?;

    // `next_typed()` yields each sample tagged with its output type.
    while let Some((_sample, kind)) = stream.next_typed().await {
        match kind {
            SCStreamOutputType::Screen => { /* video frame */ }
            SCStreamOutputType::Audio  => { /* system audio */ }
            _ => {}
        }
        # break;
    }

    stream.stop_capture().await?;
    Ok(())
}

Screenshot (macOS 14.0+)

```rust,no_run

#[cfg(feature = "macos_14_0")]

fn example(

filter: &screencapturekit::stream::content_filter::SCContentFilter,

config: &screencapturekit::stream::configuration::SCStreamConfiguration,

) -> Result<(), Box> {

use screencapturekit::screenshot_manager::{CGImageExt, SCScreenshotManager};

let img = SCScreenshotManager::capture_image(filter, config)?; let pixels = img.bgra_data()?; // native BGRA — skips R↔B swap // For sustained loops, reuse a buffer: // img.bgra_data_into(&mut buffer)?;

Ok(()) }










<strong>System content picker (macOS 14.0+)</strong>



```rust,ignore
use screencapturekit::content_sharing_picker::*;
use screencapturekit::prelude::*;

let config = SCContentSharingPickerConfiguration::new();
SCContentSharingPicker::show(&config, |outcome| match outcome {
    SCPickerOutcome::Picked(result) => {
        let (w, h) = result.pixel_size();
        let filter = result.filter();
        // Use `filter` with SCStream as in the Quick Start.
        let _ = (w, h, filter);
    }
    SCPickerOutcome::Cancelled => println!("user cancelled"),
    SCPickerOutcome::Error(e)  => eprintln!("picker error: {e}"),
});

For async contexts, use [AsyncSCContentSharingPicker::show].

Direct-to-file recording (macOS 15.0+)

See examples/10_recording_output.rs — it covers SCRecordingOutput, SCRecordingOutputConfiguration, and the delegate callbacks for start / finish / error.

Custom dispatch queue / QoS

```rust,no_run use screencapturekit::prelude::*; use screencapturekit::dispatch_queue::{DispatchQueue, DispatchQoS};

fn example(stream: &mut SCStream) {

let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive); stream.add_output_handler_with_queue( |_sample, _of_type| { / runs on queue / }, SCStreamOutputType::Screen, Some(&queue), );

}


`QoS` levels: `Background`, `Utility`, `Default`, `UserInitiated`, `UserInteractive` (Quality of Service).









<strong>Zero-copy GPU access (IOSurface → Metal / wgpu)</strong>



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

Built-in Metal helpers live in screencapturekit::metal and ship a small shader library (SHADER_SOURCE) covering BGRA, YCbCr, and UI overlay rendering. See examples/16_full_metal_app/ for a complete app and examples/18_wgpu_integration.rs for the wgpu equivalent.

Examples

23 runnable examples cover every API surface. The full table with feature requirements lives in [examples/README.md](exa

Extension points exported contracts — how you extend this code

SCStreamOutputTrait (Interface)
Trait for handling stream output Implement this trait to receive callbacks when the stream captures frames or audio. # [24 …
src/stream/output_trait.rs
SCRecordingOutputDelegate (Interface)
Delegate for recording output events Implement this trait to receive notifications about recording lifecycle events. # [4 …
src/recording_output.rs
PickerDecode (Interface)
Decodes the `(code, ptr)` pair from the Swift bridge into a typed outcome. Implemented by zero-sized marker types so a [2 …
src/content_sharing_picker.rs
IOSurfaceMetalExt (Interface)
Extension trait that adds Metal-related convenience methods to `apple_cf::iosurface::IOSurface`. It's a trait (rather t [1 …
src/metal.rs
CGImageExt (Interface)
Screenshot-specific helpers implemented for the canonical [`CGImage`] type. Import this trait to access pixel extractio [1 …
src/screenshot_manager.rs
CMSampleBufferSCExt (Interface)
Extension trait that exposes `SCStreamFrameInfo` attachment accessors on any [`CMSampleBuffer`] produced by `ScreenCaptu [1 …
src/cm/sample_buffer.rs
SCStreamDelegateTrait (Interface)
Trait for handling stream lifecycle events Implement this trait to receive notifications about stream state changes, er [8 …
src/stream/delegate_trait.rs
CMSampleBufferExt (Interface)
Extension trait carrying generic `CMSampleBuffer` accessors that aren't available on [`apple_cf::cm::CMSampleBuffer`] ye [1 …
src/cm/sample_buffer.rs

Core symbols most depended-on inside this repo

displays
called by 132
src/shareable_content/mod.rs
with_display
called by 123
src/stream/content_filter.rs
as_ptr
called by 119
src/metal.rs
iter
called by 116
src/cm/audio.rs
get
called by 111
src/cm/audio.rs
with_excluding_windows
called by 104
src/stream/content_filter.rs
with_width
called by 103
src/screenshot_manager.rs
with_height
called by 103
src/screenshot_manager.rs

Shape

Function 920
Method 642
Class 148
Enum 39
Interface 9

Languages

Rust98%
Python2%
TypeScript1%

Modules by API surface

tests/metal_tests.rs108 symbols
src/metal.rs98 symbols
tests/async_api_tests.rs80 symbols
src/async_api.rs75 symbols
src/screenshot_manager.rs56 symbols
tests/configuration_tests.rs46 symbols
src/content_sharing_picker.rs46 symbols
tests/recording_output_tests.rs45 symbols
tests/iosurface_tests.rs39 symbols
tests/configuration_builder_tests.rs39 symbols
src/stream/content_filter.rs38 symbols
src/recording_output.rs38 symbols

For agents

$ claude mcp add screencapturekit-rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact