๐ผ 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-metalbinding crates (plus trait-onlyfutures-corewhen theasyncfeature is on; no heavy third-party runtime deps)
Table of Contents
- Install ยท Quick Start ยท Recipes
- Feature Flags ยท Examples ยท Documentation
- Requirements & Permissions ยท Performance
- Troubleshooting ยท Migration ยท Contributing
Install
[]
= "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:
= { = "8", = ["async", "macos_15_0"] }
Upgrading a major version? See
docs/MIGRATION.mdfor a per-version guide. Releases 3.0โ6.0 consolidated the Core Graphics / Core Media foundation types onto the sharedapple-cfcrate and 7.0 hardens the FFI boundary; the only likely source change across that line is 5.0's nestedCGRectlayout (rect.origin.x/rect.size.width). 8.0 makes theasyncstream lifecycle methods real futures โ add.awaittoAsyncSCStream::{start,stop}_captureandupdate_*(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.
use *;
;
Output / delegate handlers must be
Send + Syncโ Apple's dispatch queues may invoke them concurrently from arbitrary threads.
Permission required โ see 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/ โ see the Examples table.
use *;
#
# use *;
#
Closures must be Fn + Send + Sync + 'static.
use ;
use *;
async
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, โฆ):
use StreamExt;
let first_30: = stream.frames.take.collect.await;
use ;
use *;
async
#
#
use *;
use *;
let config = new;
show;
For async contexts, use AsyncSCContentSharingPicker::show.
See examples/10_recording_output.rs โ it
covers SCRecordingOutput, SCRecordingOutputConfiguration, and the
delegate callbacks for start / finish / error.
use *;
use ;
#
QoS levels: Background, Utility, Default, UserInitiated, UserInteractive (Quality of Service).
use *;
;
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. A few
favourites to start with:
| Example | What it shows |
|---|---|
01_basic_capture |
Minimal screen capture โ start here |
08_async |
Async API, picker, runtime-agnostic patterns |
09_closure_handlers |
Closures + delegate callbacks |
10_recording_output |
Direct-to-file recording (macOS 15.0+) |
11_content_picker |
System picker UI (macOS 14.0+) |
16_full_metal_app/ |
Full Metal viewer app (macOS 14.0+) |
18_wgpu_integration |
Zero-copy wgpu integration |
19_ffmpeg_encoding |
Real-time H.264 via ffmpeg |
24_batched_apis_showcase |
Batched FFI vs per-element (perf) |
Feature Flags
See the full feature table under Install. One small example of gating version-specific options:
let mut config = new.with_width.with_height;
Documentation
| Where | What |
|---|---|
| docs.rs | Full API reference |
docs/MIGRATION.md |
Upgrading between major versions |
docs/BENCHMARKS.md |
Benchmark methodology + results |
examples/README.md |
All 23 examples + feature requirements |
CHANGELOG.md |
Release notes |
Requirements & Permissions
- macOS 12.3+ (Monterey) โ base
ScreenCaptureKit - macOS 13.0+ โ audio capture ยท 14.0+ โ picker / screenshots ยท 15.0+ โ recording / HDR / mic ยท 26.0+ โ advanced screenshots
- Xcode Command Line Tools at build time (
xcode-select --install)
Screen capture always requires user permission. To grant it:
- System Settings โ Privacy & Security โ Screen Recording
- Enable your binary (during development this is usually your terminal or IDE)
- Restart the app
For distribution, add a purpose string to Info.plist โ the user-facing
TCC prompt requires it and the app will be terminated without one:
NSScreenCaptureUsageDescription
Capture your screen so the app can โฆ
ScreenCaptureKit is purely TCC-gated: there is no code-signing
entitlement that grants screen capture access. Capture is allowed solely
when the user enables your binary under System Settings โ Privacy &
Security โ Screen & System Audio Recording.
| App type | What you need |
|---|---|
| Any signed macOS app (sandboxed or not) | NSScreenCaptureUsageDescription in Info.plist + user TCC grant |
| Sandboxed app | Additionally com.apple.security.app-sandbox = true in Entitlements.plist โ this only turns the sandbox on; it does not grant capture |
| Sandboxed app capturing system audio (macOS 13+) | Optionally com.apple.security.device.audio-input = true |
There is no
com.apple.security.screen-captureentitlement. That key isn't part of Apple's security-entitlements reference; the onlycom.apple.security.device.*keys arecamera,microphone,audio-input,usb, andbluetooth. The two real screen-capture entitlements (com.apple.developer.screen-capture.include-passthroughandcom.apple.developer.protected-content) are Enterprise / visionOS managed entitlements and don't apply toScreenCaptureKiton macOS.
Performance
Full capture (60 fps + 48 kHz stereo) costs ~1.9% of one core end-to-end
on Apple Silicon โ the binding itself is below the noise floor of a 4 kHz
sampling profiler; nearly all CPU lives in Apple's SkyLight /
libdispatch / libxpc pipeline.
| Resolution | Expected FPS | First-frame latency |
|---|---|---|
| 1080p | 30โ60 | 30โ100 ms |
| 4K | 15โ30 | 50โ150 ms |
Hot-path tips:
- Prefer
BGRAto skip the per-pixel RโB swap when uploading to Metal / wgpu / ffmpeg (CGImageExt::bgra_datais ~5% faster thanrgba_data). - Reuse a
Vec<u8>across screenshots with the*_data_intovariants (saves a ~33 MB allocation per 4K frame โ new in 2.1). - When iterating many windows / displays / apps, use the batched
SCShareableContent::snapshot()API โ collapses1 + N + 6NFFI calls into one round-trip per category (~2ร faster on a typical desktop). - Read every
SCStreamFrameInfoattachment in one cast viaCMSampleBuffer::frame_info().
use *;
use ContentSnapshot;
#
Run benchmarks on your hardware:
See docs/BENCHMARKS.md for methodology, throughput
numbers at various resolutions, and tuning guidance.
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
SCShareableContent::get() returns empty / errors |
Missing Screen Recording permission โ grant it in System Settings, then restart |
| Black / empty frames | Captured window minimized; pixel format mismatch; filter doesn't include the right display/window |
| No audio samples | Did you set .with_captures_audio(true) and add a handler for SCStreamOutputType::Audio? |
| Build fails with Swift bridge errors | xcode-select --install; then cargo clean && cargo build |
| App crashes after notarization | Missing NSScreenCaptureUsageDescription in Info.plist โ the system terminates apps that trigger the Screen Recording TCC prompt without one (see Requirements) |
match on PixelFormat / SCStreamErrorCode no longer compiles |
Both are #[non_exhaustive] in 2.0 โ add a wildcard _ => โฆ arm |
Migration
Upgrading? See docs/MIGRATION.md for the full guide,
including a section for every major version bump.
Highlights by major version:
- 2.0 โ output / delegate traits (and closure overloads) now require
Send + Sync;PixelFormatandSCStreamErrorCodebecame#[non_exhaustive];PixelFormatgainedUnknown(FourCharCode); everymacos_*Cargo feature now propagates to the Swift bridge build. - 3.0 โ foundation types (Core Graphics / Core Media /
IOSurface/ Core Video) moved onto the sharedapple-cf/apple-metalcrates as re-exports; ScreenCaptureKit-specificCMSampleBufferaccessors moved to theCMSampleBufferExt/CMSampleBufferSCExtextension traits (both in the prelude). - 4.0 โ
ScreenshotManager::capture_imagereturnsapple_cf::cg::CGImageandscreencapturekit::cm::CMTimeis anapple-cfre-export (drop any cross-crate conversions). - 5.0 โ adopts
apple-cf0.8's nestedCGRectlayout: userect.origin.x/rect.size.widthinstead of flatrect.x/rect.width. - 6.0 โ
CMSampleTimingInfoandCMClockare now re-exported fromapple-cfas well. - 7.0 โ no source changes for typical users. FFI-hardening release that
adds strided pixel-render helpers (
CGImageExt::rgba_data_into_strided/bgra_data_into_strided) plus a lockedIOSurfaceCPU view, and relaxes theAudioBufferRef::data()slice lifetime so the returned slice is tied to the wrapped buffer. - 8.0 โ
asynconly.AsyncSCStream::{start_capture, stop_capture, update_configuration, update_content_filter}now return a waker-based future instead of blocking โ add.await(e.g.stream.start_capture().await?). The stream engine now reports stops only throughSCStreamDelegateTrait::did_stop_with_error(the redundantstream_did_stopis deprecated). New:AsyncSCStream::{take_error, add_output_type, next_typed, try_next_typed}for error visibility and audio+video on one stream. The synchronousSCStreamAPI is unchanged.
If you only use the prelude / screencapturekit::{cg, cm} types, the 4.0โ7.0
upgrades are typically just the 5.0 CGRect field-access change, and 8.0 only
affects you if you use the async API.
Contributing
Contributions welcome! Please:
- Follow existing patterns โ builder pattern with
::new()and.with_*() - Add tests for new functionality
cargo fmt && cargo clippy --all-features -- -D warnings && cargo test- Update docs and
CHANGELOG.md
See CLAUDE.md / AGENTS.md for the project conventions agents follow.
Used By
Powering 50+ open-source projects across screen recording, AI agents, meeting transcription, and remote desktop. A few highlights:
- AFFiNE โ knowledge base, Notion / Miro alternative (68k+ โญ)
- voicebox โ open-source AI voice studio (25k+ โญ)
- Cap โ open-source Loom alternative (19k+ โญ)
- Observer โ local AI screen observer (1.4k+ โญ)
- my-translator โ real-time speech translation (1k+ โญ)
- hylarana โ cross-platform screen casting in Rust
- gst-screencapturekit โ
GStreamerplugin - open-agent, watson.ai, harana/search, agent-native by Builder.io
fl_caption, Lycoris, Hindsight, kivio, Drift, Phantom, ruhear, Tab5-Screen-Streamer, macloop, beer, phantom-ear, Logia, VibeTube, silly-ai, aresampler, xos, scriberr-desktop, echonote, zest-wallpaper, mira, overlay-ai, open-rec, omnirec, oxiremote, LocalWhisper, Hush, cocuyo, openhush, tucknotes, domino, bridge, screen-recorder, orbit, audio-capture, AFFiNE-teto, loom.
Using screencapturekit-rs? Open an issue and we'll add you.
Contributors
Thanks to everyone who has contributed!
Per Johansson (maintainer) ยท Iason Paraskevopoulos ยท Kris Krolak ยท Tokuhiro Matsuno ยท Pranav Joglekar ยท Alex Jiao ยท Charles ยท bigduu ยท Andrew N
License
Licensed under either of Apache-2.0 or MIT at your option.