Skip to content

Commit acf13d8

Browse files
1313Copilot
andcommitted
feat: complete v3.1.0 coverage audit
- fix the macOS 26 HDR recording preset mapping - fill picker, recording, and stream-configuration coverage gaps - add COVERAGE.md certification for the audited ScreenCaptureKit surface Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4b8398c commit acf13d8

23 files changed

Lines changed: 486 additions & 114 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [3.1.0](https://github.com/doom-fish/screencapturekit-rs/compare/v3.0.1...v3.1.0) - 2026-05-16
11+
12+
### Added
13+
14+
- *(coverage)* add `COVERAGE.md` and certify the audited `ScreenCaptureKit` surface
15+
- *(picker)* add `SCContentSharingPickerConfiguration::allowed_picker_modes()`
16+
- *(recording)* add `SCRecordingOutputConfiguration::output_url()`
17+
- *(config)* add `SCStreamConfiguration::color_space_name()` and RGBA background-color accessors
18+
19+
### Fixed
20+
21+
- *(preset)* map `CaptureHDRRecordingPreservedSDRHDR10` to Apple's macOS 26 HDR-recording preset
22+
- *(config)* retain assigned `CGColorRef` / `CFStringRef` values for `backgroundColor`, `colorSpaceName`, and `colorMatrix`
23+
- *(config)* round-trip `SCStreamConfiguration` color and preset properties in tests
24+
- *(deps)* align local `apple-cf` / `apple-metal` version bounds with 0.6
25+
- *(examples)* import `IOSurfaceMetalExt` where `apple-metal` 0.6 requires the trait in scope
26+
1027
## [2.1.0](https://github.com/doom-fish/screencapturekit-rs/compare/v2.0.0...v2.1.0) - 2026-05-11
1128

1229
### Added

COVERAGE.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# ScreenCaptureKit SDK Coverage
2+
3+
This document records the `screencapturekit` v3.1.0 coverage audit against Apple's `ScreenCaptureKit.framework` from Xcode 26.2 (`MacOSX26.2.sdk`).
4+
5+
## Certification
6+
7+
As of `screencapturekit` **3.1.0**, the crate provides **100% coverage of the audited ScreenCaptureKit SDK surface for this pass**, including the newer macOS 15.x and 26.0-era APIs requested for review.
8+
9+
That coverage includes both:
10+
11+
- direct bindings for concrete classes, enums, and properties
12+
- safe Rust equivalents where Apple's surface is protocol- or attachment-key-oriented
13+
14+
## Audited surface
15+
16+
The audit covered the following public SDK areas:
17+
18+
- `SCStream`
19+
- `SCStreamConfiguration`
20+
- `SCContentFilter`
21+
- `SCShareableContent`
22+
- `SCShareableContentInfo`
23+
- `SCRunningApplication`
24+
- `SCDisplay`
25+
- `SCWindow`
26+
- `SCContentSharingPicker`
27+
- `SCContentSharingPickerConfiguration`
28+
- `SCContentSharingPickerMode`
29+
- `SCRecordingOutput`
30+
- `SCRecordingOutputConfiguration`
31+
- `SCStreamErrorCode`
32+
- newer macOS 15.x / 26.0 additions in `SCScreenshotManager` and preset APIs
33+
34+
## Coverage map
35+
36+
| Apple SDK surface | Rust coverage | Status |
37+
| --- | --- | --- |
38+
| `SCStream`, `SCStreamDelegate`, `SCStreamOutput`, `SCStreamOutputType` | Direct bindings + safe traits | Complete |
39+
| `SCStreamConfiguration` | Direct bindings, including macOS 15.x microphone / HDR properties and macOS 26 preset creation | Complete |
40+
| `SCStreamConfiguration.Preset.captureHDRRecordingPreservedSDRHDR10` | `SCStreamConfiguration::from_preset(SCStreamConfigurationPreset::CaptureHDRRecordingPreservedSDRHDR10)` | Complete |
41+
| `SCStreamFrameInfo` attachment keys | `CMSampleBufferSCExt` accessors (`frame_status`, `display_time`, `scale_factor`, `content_scale`, `content_rect`, `bounding_rect`, `screen_rect`, `presenter_overlay_content_rect`, `dirty_rects`) plus batched `frame_info()` | Complete |
42+
| `SCContentFilter` | Direct bindings + builder API | Complete |
43+
| `SCShareableContent`, `SCShareableContentInfo`, `SCRunningApplication`, `SCDisplay`, `SCWindow` | Direct bindings | Complete |
44+
| `SCContentSharingPicker` | Direct picker APIs plus callback-based `show*()` wrappers over observer-style flows | Complete |
45+
| `SCContentSharingPickerConfiguration` | Direct bindings, including `allowed_picker_modes()` round-trip and exclusion getters | Complete |
46+
| `SCRecordingOutput`, `SCRecordingOutputConfiguration` | Direct bindings, including duration / file size and `output_url()` round-trip | Complete |
47+
| `SCStreamErrorCode` | Direct enum mapping | Complete |
48+
| `SCScreenshotManager` macOS 15.2 / 26.0 additions | Direct bindings | Complete |
49+
50+
## Notes on safe equivalents
51+
52+
### `SCStreamFrameInfo`
53+
54+
Apple models frame metadata as attachment keys on `CMSampleBuffer`. In Rust, the crate exposes those keys as typed accessors and a batched `FrameInfo` snapshot instead of raw string constants. This is a deliberate ergonomic layer, not a coverage gap.
55+
56+
### `SCContentSharingPicker` observer APIs
57+
58+
Apple's picker surface is observer- and presentation-oriented. The crate covers that behavior through callback-based `show()`, `show_filter()`, `show_for_stream()`, `show_using_style()`, and `show_for_stream_using_style()` helpers, plus direct picker state/configuration accessors.
59+
60+
### Assigned Core Foundation / Core Graphics properties
61+
62+
Apple declares `SCStreamConfiguration.backgroundColor`, `colorSpaceName`, and `colorMatrix` as assigned `CGColorRef` / `CFStringRef` properties. The bridge now retains the values it assigns so those properties remain valid for the full lifetime of the configuration object.
63+
64+
## Validation
65+
66+
The v3.1.0 audit and fixes were validated with:
67+
68+
- `cargo clippy --all-features -- -D warnings`
69+
- `cargo test --all-features`

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "screencapturekit"
3-
version = "3.0.1"
3+
version = "3.1.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66
homepage = "https://github.com/doom-fish/screencapturekit-rs"
@@ -10,7 +10,7 @@ description = "Safe Rust bindings for Apple's ScreenCaptureKit framework - scree
1010
authors = ["Per Johansson <per@doom.fish>"]
1111
keywords = ["screencapturekit", "screen-capture", "macos", "video", "audio"]
1212
categories = ["multimedia", "os::macos-apis", "api-bindings"]
13-
rust-version = "1.70"
13+
rust-version = "1.76"
1414
readme = "README.md"
1515
build = "build.rs"
1616
include = [
@@ -22,6 +22,7 @@ include = [
2222
"README.md",
2323
"LICENSE-*",
2424
"CHANGELOG.md",
25+
"COVERAGE.md",
2526
]
2627

2728
[lints.rust]
@@ -59,8 +60,8 @@ macos_15_2 = ["macos_15_0"]
5960
macos_26_0 = ["macos_15_2"]
6061

6162
[dependencies]
62-
apple-cf = { path = "../apple-cf-rs", version = ">=0.2, <0.6", default-features = false, features = ["cg", "iosurface", "dispatch", "cv", "cm"] }
63-
apple-metal = { path = "../apple-metal-rs", version = ">=0.1.1, <0.6", default-features = false, features = ["iosurface"] }
63+
apple-cf = { path = "../apple-cf-rs", version = ">=0.6, <0.7", default-features = false, features = ["cg", "iosurface", "dispatch", "cv", "cm"] }
64+
apple-metal = { path = "../apple-metal-rs", version = ">=0.6, <0.7", default-features = false, features = ["iosurface"] }
6465

6566
[dev-dependencies]
6667
png = "0.18"

examples/16_full_metal_app/capture.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
77

88
use screencapturekit::cm::IOSurface;
99
use screencapturekit::cm::{CMSampleBufferExt, CMSampleBufferSCExt};
10-
use screencapturekit::metal::{pixel_format, IOSurfaceInfo};
10+
use screencapturekit::metal::{pixel_format, IOSurfaceInfo, IOSurfaceMetalExt};
1111
use screencapturekit::prelude::*;
1212

1313
use crate::waveform::WaveformBuffer;

examples/16_full_metal_app/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ use std::sync::{Arc, Mutex};
7575

7676
use raw_window_handle::HasWindowHandle;
7777
use screencapturekit::content_sharing_picker::SCPickedSource;
78-
use screencapturekit::metal::{autoreleasepool, setup_metal_view};
78+
use screencapturekit::metal::{autoreleasepool, setup_metal_view, IOSurfaceMetalExt};
7979
use screencapturekit::prelude::*;
8080
use winit::application::ApplicationHandler;
8181
use winit::event::{ElementState, WindowEvent};

src/cm/iosurface.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
//! IOSurface — re-exported from `apple-cf` so all doom-fish crates share
2-
//! one canonical `IOSurface` type. Metal-specific extension methods live
3-
//! on the `IOSurfaceMetalExt` trait in `screencapturekit::metal` (bring
4-
//! it into scope to call `surface.info()` / `surface.create_metal_textures(...)`
5-
//! etc.).
1+
//! `IOSurface` — re-exported from `apple-cf` so all doom-fish crates share
2+
//! one canonical `IOSurface` type.
3+
//!
4+
//! Metal-specific extension methods live on the `IOSurfaceMetalExt` trait in
5+
//! `screencapturekit::metal` (bring it into scope to call `surface.info()` /
6+
//! `surface.create_metal_textures(...)`, etc.).
67
78
pub use apple_cf::iosurface::{
89
IOSurface, IOSurfaceLockGuard, IOSurfaceLockOptions, PlaneProperties,

src/cm/sample_buffer.rs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
//! `CMSampleBuffer` — re-exported from [`apple_cf::cm::CMSampleBuffer`] +
2-
//! ScreenCaptureKit-specific extension traits for the SCStreamFrameInfo
1+
//! `CMSampleBuffer` — re-exported from [`apple_cf::cm::CMSampleBuffer`] plus
2+
//! `ScreenCaptureKit`-specific extension traits for the `SCStreamFrameInfo`
33
//! attachment readers and the few sample-buffer accessors that aren't
4-
//! framework-agnostic enough to live in apple-cf yet.
4+
//! framework-agnostic enough to live in `apple-cf` yet.
55
//!
66
//! Bring [`CMSampleBufferSCExt`] into scope to call `frame_status()`,
77
//! `display_time()`, `frame_info()`, etc. on any `CMSampleBuffer` carrying
8-
//! ScreenCaptureKit attachments.
8+
//! `ScreenCaptureKit` attachments.
99
//!
1010
//! Bring [`CMSampleBufferExt`] into scope for the
1111
//! `image_buffer()`/`audio_buffer_list()`/`make_data_ready()` accessors
@@ -78,10 +78,11 @@ pub struct FrameInfo {
7878
// ------------------------------------------------------------------
7979

8080
/// Extension trait that exposes `SCStreamFrameInfo` attachment accessors on
81-
/// any [`CMSampleBuffer`] produced by ScreenCaptureKit. These are
82-
/// SC-specific by design — they read attachment keys defined on
81+
/// any [`CMSampleBuffer`] produced by `ScreenCaptureKit`.
82+
///
83+
/// These are SC-specific by design: they read attachment keys defined on
8384
/// `SCStreamFrameInfo` and are meaningless on sample buffers from other
84-
/// sources (videotoolbox, AVFoundation capture, etc.).
85+
/// sources (videotoolbox, `AVFoundation` capture, etc.).
8586
pub trait CMSampleBufferSCExt {
8687
/// `SCStreamFrameInfo.status` attachment.
8788
fn frame_status(&self) -> Option<SCFrameStatus>;
@@ -157,13 +158,8 @@ impl CMSampleBufferSCExt for CMSampleBuffer {
157158
let mut y = 0.0;
158159
let mut w = 0.0;
159160
let mut h = 0.0;
160-
if ffi::cm_sample_buffer_get_content_rect(
161-
self.as_ptr(),
162-
&mut x,
163-
&mut y,
164-
&mut w,
165-
&mut h,
166-
) {
161+
if ffi::cm_sample_buffer_get_content_rect(self.as_ptr(), &mut x, &mut y, &mut w, &mut h)
162+
{
167163
Some(crate::cg::CGRect::new(x, y, w, h))
168164
} else {
169165
None
@@ -310,11 +306,16 @@ impl CMSampleBufferSCExt for CMSampleBuffer {
310306
// CMSampleBufferExt — generic accessors not yet in apple-cf.
311307
// ------------------------------------------------------------------
312308

313-
/// Extension trait carrying generic CMSampleBuffer accessors that aren't
309+
/// Extension trait carrying generic `CMSampleBuffer` accessors that aren't
314310
/// available on [`apple_cf::cm::CMSampleBuffer`] yet (planned for an
315-
/// apple-cf v0.2 release).
311+
/// `apple-cf` v0.2 release).
316312
pub trait CMSampleBufferExt {
317-
/// Construct a sample buffer wrapping a CVPixelBuffer.
313+
/// Construct a sample buffer wrapping a `CVPixelBuffer`.
314+
///
315+
/// # Errors
316+
///
317+
/// Returns the underlying `OSStatus` if `CoreMedia` fails to create the
318+
/// sample buffer.
318319
fn create_for_image_buffer(
319320
image_buffer: &CVPixelBuffer,
320321
presentation_time: CMTime,
@@ -336,7 +337,7 @@ pub trait CMSampleBufferExt {
336337
///
337338
/// # Errors
338339
///
339-
/// Returns the underlying `OSStatus` if CoreMedia rejects the new value.
340+
/// Returns the underlying `OSStatus` if `CoreMedia` rejects the new value.
340341
fn set_output_presentation_timestamp(&self, time: CMTime) -> Result<(), i32>;
341342

342343
/// Size of one sample at `index` in bytes.
@@ -353,7 +354,7 @@ pub trait CMSampleBufferExt {
353354
///
354355
/// # Errors
355356
///
356-
/// Returns the underlying `OSStatus` if CoreMedia reports failure.
357+
/// Returns the underlying `OSStatus` if `CoreMedia` reports failure.
357358
fn make_data_ready(&self) -> Result<(), i32>;
358359

359360
/// Read the timing info for the sample at `index`.
@@ -381,7 +382,7 @@ impl CMSampleBufferExt for CMSampleBuffer {
381382
&mut sample_buffer_ptr,
382383
);
383384
if status == 0 && !sample_buffer_ptr.is_null() {
384-
CMSampleBuffer::from_raw(sample_buffer_ptr).ok_or(status)
385+
Self::from_raw(sample_buffer_ptr).ok_or(status)
385386
} else {
386387
Err(status)
387388
}

src/content_sharing_picker.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,28 @@ impl SCContentSharingPickerConfiguration {
151151
}
152152
}
153153

154+
/// Get the currently allowed picker modes.
155+
pub fn allowed_picker_modes(&self) -> Vec<SCContentSharingPickerMode> {
156+
let mask = unsafe {
157+
crate::ffi::sc_content_sharing_picker_configuration_get_allowed_picker_modes_mask(
158+
self.ptr,
159+
)
160+
};
161+
let mut modes = Vec::new();
162+
for (raw_value, mode) in [
163+
(1_u64, SCContentSharingPickerMode::SingleWindow),
164+
(2_u64, SCContentSharingPickerMode::MultipleWindows),
165+
(16_u64, SCContentSharingPickerMode::SingleDisplay),
166+
(4_u64, SCContentSharingPickerMode::SingleApplication),
167+
(8_u64, SCContentSharingPickerMode::MultipleApplications),
168+
] {
169+
if mask & raw_value != 0 {
170+
modes.push(mode);
171+
}
172+
}
173+
modes
174+
}
175+
154176
/// Set whether the user can change the selected content while sharing
155177
///
156178
/// When `true`, the user can modify their selection during an active session.

src/cv/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! CoreVideo types — re-exported from `apple-cf`.
1+
//! `CoreVideo` types — re-exported from `apple-cf`.
22
33
pub use apple_cf::cv::{
44
CVPixelBuffer, CVPixelBufferLockFlags, CVPixelBufferLockGuard, CVPixelBufferPool,

src/ffi/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,13 +354,26 @@ extern "C" {
354354
r: f32,
355355
g: f32,
356356
b: f32,
357+
a: f32,
358+
);
359+
pub fn sc_stream_configuration_get_background_color(
360+
config: *const c_void,
361+
r: *mut f32,
362+
g: *mut f32,
363+
b: *mut f32,
364+
a: *mut f32,
357365
);
358366
pub fn sc_stream_configuration_set_color_space_name(config: *const c_void, name: *const i8);
367+
pub fn sc_stream_configuration_get_color_space_name(
368+
config: *const c_void,
369+
buffer: *mut i8,
370+
buffer_size: isize,
371+
) -> bool;
359372
pub fn sc_stream_configuration_set_color_matrix(config: *const c_void, matrix: *const i8);
360373
pub fn sc_stream_configuration_get_color_matrix(
361374
config: *const c_void,
362375
buffer: *mut i8,
363-
buffer_size: usize,
376+
buffer_size: isize,
364377
) -> bool;
365378

366379
// macOS 14.0+ - capture resolution type
@@ -594,6 +607,9 @@ extern "C" {
594607
modes: *const i32,
595608
count: usize,
596609
);
610+
pub fn sc_content_sharing_picker_configuration_get_allowed_picker_modes_mask(
611+
config: *const c_void,
612+
) -> u64;
597613
pub fn sc_content_sharing_picker_configuration_set_allows_changing_selected_content(
598614
config: *const c_void,
599615
allows: bool,
@@ -699,6 +715,11 @@ extern "C" {
699715
extern "C" {
700716
pub fn sc_recording_output_configuration_create() -> *const c_void;
701717
pub fn sc_recording_output_configuration_set_output_url(config: *const c_void, path: *const i8);
718+
pub fn sc_recording_output_configuration_get_output_url(
719+
config: *const c_void,
720+
buffer: *mut i8,
721+
buffer_size: isize,
722+
) -> bool;
702723
pub fn sc_recording_output_configuration_set_video_codec(config: *const c_void, codec: i32);
703724
pub fn sc_recording_output_configuration_retain(config: *const c_void) -> *const c_void;
704725
pub fn sc_recording_output_configuration_release(config: *const c_void);

0 commit comments

Comments
 (0)