#![cfg(feature = "macos_14_0")]
use screencapturekit::screenshot_manager::{CGImage, CGImageExt, SCScreenshotManager};
use screencapturekit::shareable_content::SCShareableContent;
use screencapturekit::stream::configuration::SCStreamConfiguration;
use screencapturekit::stream::content_filter::SCContentFilter;
fn cg_init_for_headless_ci() {
extern "C" {
fn sc_initialize_core_graphics();
}
unsafe { sc_initialize_core_graphics() }
}
macro_rules! require_display {
($content:expr, $display:ident) => {
let displays = $content.displays();
let Some($display) = displays.first() else {
eprintln!("skip: no displays available");
return;
};
};
}
fn has_capturable_display() -> bool {
SCShareableContent::get().is_ok_and(|content| !content.displays().is_empty())
}
#[test]
fn test_screenshot_manager_type() {
let _ = SCScreenshotManager;
}
#[test]
fn test_capture_image() {
cg_init_for_headless_ci();
let content = SCShareableContent::get().expect("Failed to get shareable content");
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCStreamConfiguration::new()
.with_width(640)
.with_height(480);
let result = SCScreenshotManager::capture_image(&filter, &config);
if let Ok(image) = result {
assert!(image.width() > 0);
assert!(image.height() > 0);
}
}
#[test]
fn test_capture_sample_buffer() {
cg_init_for_headless_ci();
let content = SCShareableContent::get().expect("Failed to get shareable content");
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCStreamConfiguration::new()
.with_width(640)
.with_height(480);
let result = SCScreenshotManager::capture_sample_buffer(&filter, &config);
if let Ok(buffer) = result {
let _pts = buffer.presentation_timestamp();
}
}
#[test]
fn test_cgimage_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<CGImage>();
assert_sync::<CGImage>();
}
#[test]
fn test_cgimage_rgba_data() {
cg_init_for_headless_ci();
let content = SCShareableContent::get().expect("Failed to get shareable content");
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCStreamConfiguration::new()
.with_width(100)
.with_height(100);
if let Ok(image) = SCScreenshotManager::capture_image(&filter, &config) {
if let Ok(data) = image.rgba_data() {
let expected_min_size = image.width() * image.height() * 4;
assert!(data.len() >= expected_min_size);
}
}
}
#[test]
fn test_cgimage_bgra_matches_rgba_byteswap() {
cg_init_for_headless_ci();
let Ok(content) = SCShareableContent::get() else {
return;
};
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCStreamConfiguration::new().with_width(64).with_height(64);
let Ok(image) = SCScreenshotManager::capture_image(&filter, &config) else {
return;
};
let Ok(rgba) = image.rgba_data() else { return };
let Ok(bgra) = image.bgra_data() else { return };
assert_eq!(
rgba.len(),
bgra.len(),
"RGBA and BGRA buffers must match in size"
);
assert_eq!(rgba.len(), image.width() * image.height() * 4);
let mismatches = rgba
.chunks_exact(4)
.zip(bgra.chunks_exact(4))
.filter(|(rgba_px, bgra_px)| {
rgba_px[0] != bgra_px[2]
|| rgba_px[1] != bgra_px[1]
|| rgba_px[2] != bgra_px[0]
|| rgba_px[3] != bgra_px[3]
})
.count();
let total = rgba.len() / 4;
let tolerance = total / 200 + 1;
assert!(
mismatches <= tolerance,
"BGRA layout doesn't match RGBA byte-swap: {mismatches}/{total} pixels differ (tolerance {tolerance})"
);
}
#[test]
fn test_cgimage_data_into_buffer_apis() {
cg_init_for_headless_ci();
let Ok(content) = SCShareableContent::get() else {
return;
};
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCStreamConfiguration::new().with_width(64).with_height(64);
let Ok(image) = SCScreenshotManager::capture_image(&filter, &config) else {
return;
};
let total_bytes = image.width() * image.height() * 4;
let rgba_owned = image.rgba_data().expect("rgba_data");
assert_eq!(rgba_owned.len(), total_bytes);
let mut rgba_buf = vec![0u8; total_bytes];
let written = image.rgba_data_into(&mut rgba_buf).expect("rgba_data_into");
assert_eq!(written, total_bytes);
let bgra_owned = image.bgra_data().expect("bgra_data");
assert_eq!(bgra_owned.len(), total_bytes);
let mut bgra_buf = vec![0u8; total_bytes];
let written = image.bgra_data_into(&mut bgra_buf).expect("bgra_data_into");
assert_eq!(written, total_bytes);
let mut a = vec![0u8; total_bytes];
let mut b = vec![0u8; total_bytes];
image.bgra_data_into(&mut a).expect("a");
image.bgra_data_into(&mut b).expect("b");
assert_eq!(a, b, "deterministic output for identical destination state");
let mut small = vec![0u8; total_bytes - 1];
assert!(
image.rgba_data_into(&mut small).is_err(),
"rgba_data_into must reject undersized destination"
);
assert!(
image.bgra_data_into(&mut small).is_err(),
"bgra_data_into must reject undersized destination"
);
let sentinel = 0xCDu8;
let mut large = vec![sentinel; total_bytes + 16];
let written = image.bgra_data_into(&mut large).expect("oversize ok");
assert_eq!(written, total_bytes);
assert!(
large[total_bytes..].iter().all(|&b| b == sentinel),
"bytes past the rendered region must not be touched"
);
}
#[test]
#[cfg(feature = "macos_15_2")]
fn test_capture_image_in_rect() {
use screencapturekit::cg::CGRect;
cg_init_for_headless_ci();
if !has_capturable_display() {
eprintln!("skip: no displays available");
return;
}
let rect = CGRect::new(0.0, 0.0, 640.0, 480.0);
let result = SCScreenshotManager::capture_image_in_rect(rect);
match result {
Ok(image) => {
assert!(image.width() > 0);
assert!(image.height() > 0);
println!(
"✓ Captured image in rect: {}x{}",
image.width(),
image.height()
);
}
Err(e) => {
println!("âš capture_image_in_rect not available: {e}");
}
}
}
#[test]
#[cfg(feature = "macos_15_2")]
fn test_capture_image_in_rect_small_region() {
use screencapturekit::cg::CGRect;
cg_init_for_headless_ci();
if !has_capturable_display() {
eprintln!("skip: no displays available");
return;
}
let rect = CGRect::new(100.0, 100.0, 100.0, 100.0);
let result = SCScreenshotManager::capture_image_in_rect(rect);
match result {
Ok(image) => {
println!(
"✓ Captured small region: {}x{}",
image.width(),
image.height()
);
}
Err(_) => {
println!("âš capture_image_in_rect not available");
}
}
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_creation() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let config = SCScreenshotConfiguration::new();
assert!(!config.as_ptr().is_null());
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_builder() {
use screencapturekit::cg::CGRect;
use screencapturekit::screenshot_manager::{
SCScreenshotConfiguration, SCScreenshotDisplayIntent, SCScreenshotDynamicRange,
};
let config = SCScreenshotConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_shows_cursor(true)
.with_source_rect(CGRect::new(0.0, 0.0, 1920.0, 1080.0))
.with_destination_rect(CGRect::new(0.0, 0.0, 1920.0, 1080.0))
.with_ignore_shadows(true)
.with_ignore_clipping(false)
.with_include_child_windows(true)
.with_display_intent(SCScreenshotDisplayIntent::Canonical)
.with_dynamic_range(SCScreenshotDynamicRange::SDR);
assert!(!config.as_ptr().is_null());
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_hdr() {
use screencapturekit::screenshot_manager::{
SCScreenshotConfiguration, SCScreenshotDynamicRange,
};
let sdr_config =
SCScreenshotConfiguration::new().with_dynamic_range(SCScreenshotDynamicRange::SDR);
assert!(!sdr_config.as_ptr().is_null());
let hdr_config =
SCScreenshotConfiguration::new().with_dynamic_range(SCScreenshotDynamicRange::HDR);
assert!(!hdr_config.as_ptr().is_null());
let both_config = SCScreenshotConfiguration::new()
.with_dynamic_range(SCScreenshotDynamicRange::BothSDRAndHDR);
assert!(!both_config.as_ptr().is_null());
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_file_path() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let config = SCScreenshotConfiguration::new().with_file_path("/tmp/test_screenshot.png");
assert!(!config.as_ptr().is_null());
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_send_sync() {
use screencapturekit::screenshot_manager::{SCScreenshotConfiguration, SCScreenshotOutput};
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<SCScreenshotConfiguration>();
assert_sync::<SCScreenshotConfiguration>();
assert_send::<SCScreenshotOutput>();
assert_sync::<SCScreenshotOutput>();
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_display_intent_enum() {
use screencapturekit::screenshot_manager::SCScreenshotDisplayIntent;
assert_eq!(SCScreenshotDisplayIntent::Canonical as i32, 0);
assert_eq!(SCScreenshotDisplayIntent::Local as i32, 1);
let default = SCScreenshotDisplayIntent::default();
assert_eq!(default, SCScreenshotDisplayIntent::Canonical);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_dynamic_range_enum() {
use screencapturekit::screenshot_manager::SCScreenshotDynamicRange;
assert_eq!(SCScreenshotDynamicRange::SDR as i32, 0);
assert_eq!(SCScreenshotDynamicRange::HDR as i32, 1);
assert_eq!(SCScreenshotDynamicRange::BothSDRAndHDR as i32, 2);
let default = SCScreenshotDynamicRange::default();
assert_eq!(default, SCScreenshotDynamicRange::SDR);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_capture_screenshot_with_configuration() {
use screencapturekit::screenshot_manager::{
SCScreenshotConfiguration, SCScreenshotDynamicRange,
};
cg_init_for_headless_ci();
let content = SCShareableContent::get().expect("Failed to get shareable content");
require_display!(content, display);
let filter = SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build();
let config = SCScreenshotConfiguration::new()
.with_width(640)
.with_height(480)
.with_shows_cursor(true)
.with_dynamic_range(SCScreenshotDynamicRange::SDR);
let result = SCScreenshotManager::capture_screenshot(&filter, &config);
match result {
Ok(output) => {
if let Some(sdr) = output.sdr_image() {
assert!(sdr.width() > 0);
assert!(sdr.height() > 0);
println!(
"✓ Advanced screenshot SDR: {}x{}",
sdr.width(),
sdr.height()
);
}
}
Err(e) => {
println!("âš capture_screenshot not available: {e}");
}
}
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_capture_screenshot_in_rect_with_configuration() {
use screencapturekit::cg::CGRect;
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
cg_init_for_headless_ci();
let rect = CGRect::new(0.0, 0.0, 640.0, 480.0);
let config = SCScreenshotConfiguration::new()
.with_width(640)
.with_height(480);
let result = SCScreenshotManager::capture_screenshot_in_rect(rect, &config);
match result {
Ok(output) => {
if let Some(image) = output.sdr_image() {
assert!(image.width() > 0);
println!(
"✓ Advanced screenshot in rect: {}x{}",
image.width(),
image.height()
);
}
}
Err(e) => {
println!("âš capture_screenshot_in_rect not available: {e}");
}
}
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_scalar_getters_round_trip() {
use screencapturekit::screenshot_manager::{
SCScreenshotConfiguration, SCScreenshotDisplayIntent, SCScreenshotDynamicRange,
};
let config = SCScreenshotConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_shows_cursor(true)
.with_ignore_shadows(true)
.with_ignore_clipping(true)
.with_include_child_windows(true)
.with_display_intent(SCScreenshotDisplayIntent::Local)
.with_dynamic_range(SCScreenshotDynamicRange::BothSDRAndHDR);
assert_eq!(config.width(), 1920);
assert_eq!(config.height(), 1080);
assert!(config.shows_cursor());
assert!(config.ignore_shadows());
assert!(config.ignore_clipping());
assert!(config.include_child_windows());
assert_eq!(
config.display_intent(),
Some(SCScreenshotDisplayIntent::Local)
);
assert_eq!(
config.dynamic_range(),
Some(SCScreenshotDynamicRange::BothSDRAndHDR)
);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_rect_getters_round_trip() {
use screencapturekit::cg::CGRect;
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let source = CGRect::new(10.0, 20.0, 640.0, 480.0);
let destination = CGRect::new(0.0, 0.0, 1280.0, 960.0);
let config = SCScreenshotConfiguration::new()
.with_source_rect(source)
.with_destination_rect(destination);
let read_source = config.source_rect();
assert!((read_source.origin.x - source.origin.x).abs() < f64::EPSILON);
assert!((read_source.origin.y - source.origin.y).abs() < f64::EPSILON);
assert!((read_source.size.width - source.size.width).abs() < f64::EPSILON);
assert!((read_source.size.height - source.size.height).abs() < f64::EPSILON);
let read_destination = config.destination_rect();
assert!((read_destination.size.width - destination.size.width).abs() < f64::EPSILON);
assert!((read_destination.size.height - destination.size.height).abs() < f64::EPSILON);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_file_path_round_trip() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
use std::path::PathBuf;
let dir = std::env::temp_dir();
let path: PathBuf = dir.join("screencapturekit round trip.png");
let config = SCScreenshotConfiguration::new().with_file_path(&path);
assert_eq!(config.file_path().as_deref(), Some(path.as_path()));
let cleared = config.without_file_path();
assert_eq!(cleared.file_path(), None);
let mut config = SCScreenshotConfiguration::new().with_file_path(&path);
config.clear_file_path();
assert_eq!(config.file_path(), None);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_long_file_path_survives() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let long_name = "x".repeat(200);
let path = std::env::temp_dir().join(format!("{long_name}.png"));
let config = SCScreenshotConfiguration::new().with_file_path(&path);
assert_eq!(config.file_path().as_deref(), Some(path.as_path()));
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_rejects_interior_nul_path() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let mut config = SCScreenshotConfiguration::new();
assert!(
config.try_set_file_path("/tmp/bad\0name.png").is_err(),
"interior NUL path must be rejected"
);
assert_eq!(config.file_path(), None);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_rejects_non_utf8_path() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
use std::os::unix::ffi::OsStringExt;
let path = std::path::PathBuf::from(std::ffi::OsString::from_vec(
b"/tmp/screenshot-\xff.png".to_vec(),
));
let mut config = SCScreenshotConfiguration::new();
assert!(config.try_set_file_path(path).is_err());
assert_eq!(config.file_path(), None);
}
#[test]
#[cfg(feature = "macos_26_0")]
fn test_screenshot_configuration_content_type_round_trip() {
use screencapturekit::screenshot_manager::SCScreenshotConfiguration;
let supported = SCScreenshotConfiguration::supported_content_types();
assert!(
!supported.is_empty(),
"SCScreenshotConfiguration reported no supported content types"
);
let config = SCScreenshotConfiguration::new().with_content_type("public.png");
assert_eq!(config.content_type().as_deref(), Some("public.png"));
}