πΌ 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.
Safe, idiomatic Rust bindings for macOS ScreenCaptureKit framework.
Capture screen content, windows, and applications with high performance and low overhead on macOS 12.3+.
- π₯ Screen & Window Capture - Capture displays, windows, or specific applications
- π Audio Capture - Capture system audio and microphone input
- β‘ Real-time Processing - High-performance frame callbacks with custom dispatch queues
- ποΈ Builder Pattern API - Clean, type-safe configuration with
::builder() - π Async Support - Runtime-agnostic async API (works with Tokio, async-std, smol, etc.)
- π¨ IOSurface Access - Zero-copy GPU texture access for Metal/OpenGL
- π‘οΈ Memory Safe - Proper reference counting and leak-free by design
- π¦ Zero Dependencies - No runtime dependencies (only dev dependencies for examples)
Add to your Cargo.toml:
[dependencies]
screencapturekit = "1"For async support:
[dependencies]
screencapturekit = { version = "1", features = ["async"] }For latest macOS features:
[dependencies]
screencapturekit = { version = "1", features = ["macos_15_0"] }use screencapturekit::prelude::*;
struct Handler;
impl SCStreamOutputTrait for Handler {
fn did_output_sample_buffer(&self, sample: CMSampleBuffer, _type: SCStreamOutputType) {
println!("πΉ Received frame at {} pts", sample.get_presentation_timestamp());
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get available displays
let content = SCShareableContent::get()?;
let display = &content.displays()[0];
// Configure capture
let filter = SCContentFilter::builder()
.display(display)
.exclude_windows(&[])
.build();
let config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_pixel_format(PixelFormat::BGRA);
// Start streaming
let mut stream = SCStream::new(&filter, &config);
stream.add_output_handler(Handler, SCStreamOutputType::Screen);
stream.start_capture()?;
// Capture runs in background...
std::thread::sleep(std::time::Duration::from_secs(5));
stream.stop_capture()?;
Ok(())
}use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
use screencapturekit::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get content asynchronously
let content = AsyncSCShareableContent::get().await?;
let display = &content.displays()[0];
// Create filter and config
let filter = SCContentFilter::builder()
.display(display)
.exclude_windows(&[])
.build();
let config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080);
// Create async stream with frame buffer
let stream = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen);
stream.start_capture()?;
// Capture frames asynchronously
for _ in 0..10 {
if let Some(frame) = stream.next().await {
println!("πΉ Got frame!");
}
}
stream.stop_capture()?;
Ok(())
}use screencapturekit::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = SCShareableContent::get()?;
// Find a specific window
let window = content.windows()
.iter()
.find(|w| w.title().as_deref() == Some("Safari"))
.ok_or("Safari window not found")?;
// Capture window with audio
let filter = SCContentFilter::builder()
.window(window)
.build();
let config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_captures_audio(true)
.with_sample_rate(48000)
.with_channel_count(2);
let mut stream = SCStream::new(&filter, &config);
// Add handlers...
stream.start_capture()?;
Ok(())
}Use the system picker UI to let users choose what to capture:
use screencapturekit::content_sharing_picker::*;
use screencapturekit::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SCContentSharingPickerConfiguration::new();
// Advanced API: Get filter with metadata (dimensions, scale)
match SCContentSharingPicker::pick(&config) {
SCPickerOutcome::Picked(result) => {
// Get dimensions from the picked content
let (width, height) = result.pixel_size();
println!("Selected: {}x{} (scale: {})", width, height, result.scale());
let stream_config = SCStreamConfiguration::new()
.with_width(width)
.with_height(height);
// Get filter for streaming
let filter = result.filter();
let mut stream = SCStream::new(&filter, &stream_config);
// ...
}
SCPickerOutcome::Cancelled => println!("User cancelled"),
SCPickerOutcome::Error(e) => eprintln!("Error: {}", e),
}
Ok(())
}Use the async version in async contexts to avoid blocking:
use screencapturekit::async_api::AsyncSCContentSharingPicker;
use screencapturekit::content_sharing_picker::*;
use screencapturekit::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SCContentSharingPickerConfiguration::new();
// Async picker - doesn't block the executor
match AsyncSCContentSharingPicker::pick(&config).await {
SCPickerOutcome::Picked(result) => {
let (width, height) = result.pixel_size();
println!("Selected: {}x{}", width, height);
let filter = result.filter();
// Use filter with stream...
}
SCPickerOutcome::Cancelled => println!("User cancelled"),
SCPickerOutcome::Error(e) => eprintln!("Error: {}", e),
}
Ok(())
}Different types use slightly different patterns:
// Content filters use .builder() with .build()
let filter = SCContentFilter::builder()
.display(&display)
.exclude_windows(&windows)
.build();
// Stream configuration uses ::new() with .with_*() chainable methods
let config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_pixel_format(PixelFormat::BGRA)
.with_captures_audio(true);
// Options for content retrieval
let content = SCShareableContent::with_options()
.on_screen_windows_only(true)
.exclude_desktop_windows(true)
.get()?;Control callback threading with custom dispatch queues:
use screencapturekit::dispatch_queue::{DispatchQueue, DispatchQoS};
let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
stream.add_output_handler_with_queue(
my_handler,
SCStreamOutputType::Screen,
Some(&queue)
);QoS Levels:
Background- Maintenance tasksUtility- Long-running tasksDefault- Standard priorityUserInitiated- User-initiated tasksUserInteractive- UI updates (highest priority)
Zero-copy GPU texture access:
impl SCStreamOutputTrait for Handler {
fn did_output_sample_buffer(&self, sample: CMSampleBuffer, _type: SCStreamOutputType) {
if let Some(pixel_buffer) = sample.get_image_buffer() {
if let Some(surface) = pixel_buffer.get_iosurface() {
let width = surface.get_width();
let height = surface.get_height();
let pixel_format = surface.get_pixel_format();
// Use with Metal/OpenGL...
println!("IOSurface: {}x{} format: {}", width, height, pixel_format);
}
}
}
}| Feature | Description |
|---|---|
async |
Runtime-agnostic async API (works with any executor) |
Feature flags enable APIs for specific macOS versions. They are cumulative (enabling macos_15_0 enables all earlier versions).
| Feature | macOS | APIs Enabled |
|---|---|---|
macos_13_0 |
13.0 Ventura | Audio capture, synchronization clock |
macos_14_0 |
14.0 Sonoma | Content picker, screenshots, content info |
macos_14_2 |
14.2 | Menu bar capture, child windows, presenter overlay |
macos_14_4 |
14.4 | Current process shareable content |
macos_15_0 |
15.0 Sequoia | Recording output, HDR capture, microphone |
macos_15_2 |
15.2 | Screenshot in rect, stream active/inactive delegates |
macos_26_0 |
26.0 | Advanced screenshot config, HDR screenshot output |
let mut config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080);
#[cfg(feature = "macos_13_0")]
config.set_should_be_opaque(true);
#[cfg(feature = "macos_14_2")]
{
config.set_ignores_shadows_single_window(true);
config.set_includes_child_windows(false);
}SCShareableContent- Query available displays, windows, and applicationsSCContentFilter- Define what to capture (display/window/app)SCStreamConfiguration- Configure resolution, format, audio, etc.SCStream- Main capture stream with output handlersCMSampleBuffer- Frame data with timing and metadata
AsyncSCShareableContent- Async content queriesAsyncSCStream- Async stream with frame iterationAsyncSCScreenshotManager- Async screenshot capture (macOS 14.0+)AsyncSCContentSharingPicker- Async content picker UI (macOS 14.0+)
SCDisplay- Display information (resolution, ID, etc.)SCWindow- Window information (title, bounds, owner, etc.)SCRunningApplication- Application information (name, PID, etc.)
CMSampleBuffer- Sample buffer with timing and attachmentsCMTime- High-precision timestampsIOSurface- GPU-backed pixel buffersCGImage- CoreGraphics images
PixelFormat- BGRA, YCbCr420v, YCbCr420f, l10r (10-bit)SCPresenterOverlayAlertSetting- Privacy alert behaviorSCCaptureDynamicRange- HDR/SDR modes (macOS 15.0+)SCScreenshotConfiguration- Advanced screenshot config (macOS 26.0+)SCScreenshotDynamicRange- SDR/HDR screenshot output (macOS 26.0+)
The examples/ directory contains focused API demonstrations:
01_basic_capture.rs- Simplest screen capture02_window_capture.rs- Capture specific windows03_audio_capture.rs- Audio + video capture04_pixel_access.rs- Read pixel data withstd::io::Cursor05_screenshot.rs- Single screenshot, HDR capture (macOS 14.0+, 26.0+)06_iosurface.rs- Zero-copy GPU buffers07_list_content.rs- List available content08_async.rs- Async/await API with multiple examples09_closure_handlers.rs- Closure-based handlers and delegates10_recording_output.rs- Direct video file recording (macOS 15.0+)11_content_picker.rs- System UI for content selection (macOS 14.0+)
See examples/README.md for detailed descriptions.
Run an example:
# Basic examples
cargo run --example 01_basic_capture
cargo run --example 09_closure_handlers
# Feature-gated examples
cargo run --example 05_screenshot --features macos_14_0
cargo run --example 08_async --features async
cargo run --example 10_recording_output --features macos_15_0
cargo run --example 11_content_picker --features macos_14_0# All tests
cargo test
# With features
cargo test --features async
cargo test --all-features
# Specific test
cargo test test_stream_configurationcargo clippy --all-features -- -D warnings
cargo fmt --checkscreencapturekit/
βββ cm/ # Core Media (CMSampleBuffer, CMTime, CVPixelBuffer)
βββ cg/ # Core Graphics (CGRect, CGImage)
βββ stream/ # Stream management
β βββ configuration/ # SCStreamConfiguration
β βββ content_filter/ # SCContentFilter
β βββ sc_stream/ # SCStream
βββ shareable_content/ # SCShareableContent, SCDisplay, SCWindow
βββ output/ # Frame buffers and pixel data
βββ dispatch_queue/ # Custom dispatch queues
βββ error/ # Error types
βββ screenshot_manager/ # SCScreenshotManager (macOS 14.0+)
βββ content_sharing_picker/ # SCContentSharingPicker (macOS 14.0+)
βββ recording_output/ # SCRecordingOutput (macOS 15.0+)
βββ async_api/ # Async wrappers (feature = "async")
βββ utils/ # FFI strings, FourCharCode utilities
βββ prelude/ # Convenience re-exports
- Reference Counting - Proper CFRetain/CFRelease for all CoreFoundation types
- RAII - Automatic cleanup in Drop implementations
- Thread Safety - Safe to share across threads (where supported)
- Leak Free - Comprehensive leak tests ensure no memory leaks
- macOS 12.3+ (Monterey) - Base ScreenCaptureKit support
- macOS 13.0+ (Ventura) - Additional features with
macos_13_0 - macOS 14.0+ (Sonoma) - Content picker, advanced config
- macOS 15.0+ (Sequoia) - Recording output, HDR capture
Contributions welcome! Please:
- Follow existing code patterns (builder pattern with
::new()and.with_*()methods) - Add tests for new functionality
- Run
cargo testandcargo clippy - Update documentation
Thanks to everyone who has contributed to this project!
- Per Johansson - Maintainer
- Iason Paraskevopoulos
- Kris Krolak
- Tokuhiro Matsuno
- Pranav Joglekar
- Alex Jiao
- Charles
- bigduu
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.