This guide helps you migrate between major versions of screencapturekit-rs.
Note: The current release line is 7.x. The sections below document historical major-version migrations (the FFI-hardening work that started in 2.0). For changes in recent releases, see
CHANGELOG.md.
Version 2.0 hardens the FFI boundary. Most projects can upgrade by bumping the dependency and addressing a handful of compile errors — no design-level rework is required.
[dependencies]
-screencapturekit = "1"
+screencapturekit = "2"SCStreamOutputTrait and SCStreamDelegateTrait (and the Fn(...) closure
overloads) now require Send + Sync. Apple's dispatch queues may invoke the
handler concurrently from arbitrary threads, so any state owned by the
handler must be thread-safe.
Before (1.x):
struct Handler { count: std::cell::Cell<usize> } // !Sync — compiles in 1.x
impl SCStreamOutputTrait for Handler { /* ... */ }After (2.0):
use std::sync::atomic::{AtomicUsize, Ordering};
struct Handler { count: AtomicUsize } // Send + Sync
impl SCStreamOutputTrait for Handler {
fn did_output_sample_buffer(&self, _: CMSampleBuffer, _: SCStreamOutputType) {
self.count.fetch_add(1, Ordering::Relaxed);
}
}For closures: replace Cell / Rc with Arc<Atomic*> /
Arc<Mutex<...>> / Arc<RwLock<...>>.
PixelFormat is now #[non_exhaustive] and surfaces unrecognised codes
via a new Unknown(FourCharCode) variant instead of mapping them to
BGRA. This means every match over PixelFormat must include a
wildcard arm:
Before (1.x):
match config.pixel_format() {
PixelFormat::BGRA => { /* ... */ }
PixelFormat::YCbCr420v => { /* ... */ }
PixelFormat::YCbCr420f => { /* ... */ }
PixelFormat::L10R => { /* ... */ }
}After (2.0):
match config.pixel_format() {
PixelFormat::BGRA => { /* ... */ }
PixelFormat::YCbCr420v => { /* ... */ }
PixelFormat::YCbCr420f => { /* ... */ }
PixelFormat::L10R => { /* ... */ }
PixelFormat::Unknown(code) => eprintln!("unrecognised pixel format: {code}"),
_ => { /* future variants */ }
}PartialEq / Hash are now normalised through FourCharCode, so two
representations of the same OSType (e.g. PixelFormat::BGRA vs
PixelFormat::Unknown(FourCharCode::from_bytes(*b"BGRA"))) compare equal.
match arms over SCStreamErrorCode (typically inside an
SCError::StreamError { code, .. } arm) now require a wildcard:
match err_code {
SCStreamErrorCode::UserStopped => { /* graceful */ }
SCStreamErrorCode::UserDeclined => { /* permission */ }
_ => { /* anything Apple adds in a future macOS */ }
}The build script no longer silently degrades when xcrun / SDK detection
fails — it bails with a clear error pointing at xcode-select. Make sure
Xcode Command Line Tools are installed and selected:
xcode-select --install
sudo xcode-select --switch /Applications/Xcode.app/Contents/DeveloperEvery macos_* Cargo feature is also forwarded to the Swift compile, so
a feature only enabled on the Rust side (which used to silently miss the
matching Swift symbols) will now produce a coherent linker error.
SCContentSharingPicker::is_active()/set_is_active()— query and toggle the picker's idle state without recreating it.SCContentSharingPicker::default_configuration()— read the system defaultSCContentSharingPickerConfigurationso user-facing pickers match macOS defaults.CMSampleBuffer::presenter_overlay_content_rect()(and the matching field onframe_info()) — the new Presenter Overlay layout rect.
2.1 is fully backwards-compatible with 2.0 — no source changes required. New optional APIs:
CGImage::rgba_data_into(&mut [u8])andbgra_data_into(&mut [u8])— render into a caller-supplied buffer to amortise the per-callwidth*height*4byte allocation across many screenshots.- Native-BGRA fast path in
SCScreenshotManagerskips the channel swap for downstreams that accept BGRA directly (Metal / wgpu / ffmpeg).
3.0 migrates the Core Graphics / Core Media / IOSurface / Core Video
foundation types onto the shared
apple-cf and
apple-metal crates, eliminating
screencapturekit's private nominal duplicates. Most affected types are now
re-exports, so use screencapturekit::cg::CGRect; (or the prelude) keeps
working unchanged.
The one source-level change: the ScreenCaptureKit-specific accessors on
CMSampleBuffer moved to extension traits. Bring them into scope to call
them:
use screencapturekit::cm::{CMSampleBufferExt, CMSampleBufferSCExt};
// now `sample.image_buffer()`, `sample.frame_status()`, … resolveThe prelude already re-exports both traits, so use screencapturekit::prelude::*;
is enough.
4.0 removes duplicated Core Media / Core Graphics value types from the public
API in favour of the canonical apple-cf ones:
ScreenshotManager::capture_imagenow returnsapple_cf::cg::CGImage.screencapturekit::cm::CMTimeis now a re-export ofapple_cf::cm::CMTime.
If you previously converted between screencapturekit's types and apple-cf's
when chaining into ImageIO / VideoToolbox, delete those conversions — the
types are now identical.
5.0 adopts apple-cf 0.8's nested CGRect layout. Flat field access
becomes nested through origin / size:
-let x = rect.x;
-let w = rect.width;
+let x = rect.origin.x;
+let w = rect.size.width;The convenience constructor is unchanged — CGRect::new(x, y, w, h) still
takes four flat coordinates.
6.0 re-exports the final Core Media timing types from apple-cf:
screencapturekit::cm::{CMSampleTimingInfo, CMClock} are now re-exports of
apple_cf::cm::{CMSampleTimingInfo, CMClock}. As with 4.0, drop any manual
conversions between the previously-distinct types. No other source changes are
required.
The 4.0 → 6.0 bumps are all driven by consolidating onto
apple-cf; if your code only usedscreencapturekit's own types (via the prelude orscreencapturekit::{cg, cm}) the upgrade is typically just theCGRectfield-access change from 5.0.
7.0 is an FFI-hardening release with no required source changes for typical users. It is a major version only because of conservative semver around two low-level changes:
AudioBufferRef::data()lifetime. The returned slice is now tied to the lifetime'aof the wrapped audio buffer rather than the&selfborrow. This relaxes the borrow (the slice may now outlive the&selfreference), so existing call sites keep compiling unchanged.- Strided pixel render + locked
IOSurfaceCPU view. New additive helpersCGImageExt::rgba_data_into_strided/bgra_data_into_stridedrender into a caller-supplied buffer using an explicit row stride, so consumers with padded/row-aligned buffers (GPU upload,wgpu) aren't forced into tight packing. The existingrgba_data_into/bgra_data_intopaths are unchanged.
Everything else is internal: MaybeUninit scratch buffers for batched FFI
calls, null-checked constructors, and consolidated retain/release wrappers.
The async stream lifecycle methods are now genuinely asynchronous. Previously
AsyncSCStream::start_capture / stop_capture / update_configuration /
update_content_filter returned Result<(), SCError> and blocked the calling
thread on a condition variable until ScreenCaptureKit acknowledged the
operation — which stalls single-threaded / current-thread executors. They now
return a StreamControlFuture you .await:
- stream.start_capture()?;
- stream.stop_capture()?;
+ stream.start_capture().await?;
+ stream.stop_capture().await?;- stream.update_configuration(&config)?;
- stream.update_content_filter(&filter)?;
+ stream.update_configuration(&config).await?;
+ stream.update_content_filter(&filter).await?;Awaiting now parks the task via its Waker and resumes from the Swift
completion callback, so it never blocks the executor — matching the rest of the
async_api (content queries, screenshots, picker, frame iteration) and the
underlying Swift Task { try await … } entry points. The returned
StreamControlFuture is Send, so it can be moved across tokio::spawn.
If you specifically want a blocking call (e.g. from synchronous code), reach
through to the synchronous stream with stream.inner().start_capture() — the
SCStream methods are unchanged.
Version 1.0 introduced a complete API redesign with builder patterns, async support, and new macOS features.
Before (0.x):
use screencapturekit::sc_stream_configuration::UnsafeSCStreamConfiguration;
let mut config = UnsafeSCStreamConfiguration::default();
config.set_width(1920);
config.set_height(1080);
config.set_shows_cursor(true);After (1.0):
use screencapturekit::prelude::*;
let config = SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_shows_cursor(true);Before (0.x):
use screencapturekit::sc_content_filter::UnsafeSCContentFilter;
let filter = UnsafeSCContentFilter::new(display);After (1.0):
use screencapturekit::prelude::*;
let filter = SCContentFilter::create()
.with_display(&display)
.with_excluding_windows(&[])
.build();Before (0.x):
use screencapturekit::sc_stream::UnsafeSCStream;
let stream = UnsafeSCStream::new(filter, config, handler);
stream.start_capture();After (1.0):
use screencapturekit::prelude::*;
let mut stream = SCStream::new(&filter, &config);
stream.add_output_handler(handler, SCStreamOutputType::Screen);
stream.start_capture()?;Before (0.x):
impl StreamOutput for MyHandler {
fn stream_output(&self, sample: CMSampleBuffer, _of_type: SCStreamOutputType) {
// process sample
}
}After (1.0):
impl SCStreamOutputTrait for MyHandler {
fn did_output_sample_buffer(&self, sample: CMSampleBuffer, _of_type: SCStreamOutputType) {
// process sample
}
}You can now use closures instead of implementing traits:
stream.add_output_handler(
|sample: CMSampleBuffer, output_type: SCStreamOutputType| {
println!("Got frame!");
},
SCStreamOutputType::Screen
);Before (0.x):
// Errors were often panics or Option<T>
let content = SCShareableContent::get().unwrap();After (1.0):
// Proper Result<T, SCError> types
let content = SCShareableContent::get()?;| 0.x Path | 1.0 Path |
|---|---|
screencapturekit::sc_stream::* |
screencapturekit::stream::* |
screencapturekit::sc_stream_configuration::* |
screencapturekit::stream::configuration::* |
screencapturekit::sc_content_filter::* |
screencapturekit::stream::content_filter::* |
screencapturekit::sc_shareable_content::* |
screencapturekit::shareable_content::* |
Recommended: Use the prelude for common types:
use screencapturekit::prelude::*;| 0.x | 1.0 |
|---|---|
| N/A | async - Async API support |
| N/A | macos_13_0 - Audio capture |
| N/A | macos_14_0 - Screenshots, content picker |
| N/A | macos_14_2 - Menu bar, child windows |
| N/A | macos_15_0 - Recording, HDR, microphone |
| N/A | macos_15_2 - Screenshot in rect |
| N/A | macos_26_0 - Advanced screenshot config |
Before (1.0.0):
let filter = SCContentFilter::build()
.with_display(&display)
.with_excluding_windows(&[])
.build();After (1.1+):
let filter = SCContentFilter::create() // build() → create()
.with_display(&display)
.with_excluding_windows(&[])
.build();Before (1.0.0):
let mut config = SCStreamConfiguration::new();
config.set_width(1920); // Returns &mut Self
config.set_height(1080);After (1.1+):
let config = SCStreamConfiguration::new()
.with_width(1920) // Chainable
.with_height(1080);Before (1.2):
// Blocking API
let result = SCContentSharingPicker::pick(&config)?;After (1.3+):
// Callback-based API
SCContentSharingPicker::show(&config, |outcome| {
match outcome {
SCPickerOutcome::Picked(result) => { /* use result */ }
SCPickerOutcome::Cancelled => { /* handle cancel */ }
SCPickerOutcome::Error(e) => { /* handle error */ }
}
});
// Or async (with async feature)
let outcome = AsyncSCContentSharingPicker::show(&config).await;The get_ prefix was removed from getters:
Before:
let width = config.get_width();
let rect = filter.get_content_rect();
let time = sample.get_presentation_timestamp();After:
let width = config.width();
let rect = filter.content_rect();
let time = sample.presentation_timestamp();Before:
sample.get_image_buffer()
sample.get_format_description()After:
sample.image_buffer()
sample.format_description()The following APIs are deprecated and will be removed in future versions:
| Deprecated | Replacement |
|---|---|
SCStreamConfiguration::builder() |
SCStreamConfiguration::new() |
config.get_*() methods |
config.*() (without get_ prefix) |
- Update
Cargo.tomlto new version - Replace
Unsafe*types with safe equivalents - Use
prelude::*for common imports - Replace trait implementations with closures (optional)
- Update handler trait name to
SCStreamOutputTrait - Add
?for error handling (functions now returnResult) - Add feature flags for version-specific APIs
- Remove
get_prefix from getter calls - Update
SCContentFilter::build()to::create()