use screencapturekit::{
cg::CGRect,
cm::CMSampleBuffer,
shareable_content::{SCRunningApplication, SCShareableContent, SCWindow},
stream::{
configuration::{pixel_format::PixelFormat, SCStreamConfiguration},
content_filter::SCContentFilter,
output_trait::SCStreamOutputTrait,
output_type::SCStreamOutputType,
SCStream,
},
};
use std::{
process::Command,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
thread,
time::Duration,
};
fn init_cg() {
extern "C" {
fn sc_initialize_core_graphics();
}
unsafe { sc_initialize_core_graphics() }
}
#[allow(clippy::struct_field_names)]
struct LeakTestHandler {
screen_samples: AtomicUsize,
audio_samples: AtomicUsize,
#[cfg(feature = "macos_15_0")]
mic_samples: AtomicUsize,
}
impl LeakTestHandler {
const fn new() -> Self {
Self {
screen_samples: AtomicUsize::new(0),
audio_samples: AtomicUsize::new(0),
#[cfg(feature = "macos_15_0")]
mic_samples: AtomicUsize::new(0),
}
}
fn report(&self) {
let screen = self.screen_samples.load(Ordering::Relaxed);
let audio = self.audio_samples.load(Ordering::Relaxed);
#[cfg(feature = "macos_15_0")]
let mic = self.mic_samples.load(Ordering::Relaxed);
#[cfg(feature = "macos_15_0")]
println!(" Samples: screen={screen}, audio={audio}, mic={mic}");
#[cfg(not(feature = "macos_15_0"))]
println!(" Samples: screen={screen}, audio={audio}");
}
}
impl SCStreamOutputTrait for LeakTestHandler {
fn did_output_sample_buffer(&self, sample: CMSampleBuffer, of_type: SCStreamOutputType) {
let _timestamp = sample.presentation_timestamp();
let _duration = sample.duration();
match of_type {
SCStreamOutputType::Screen => {
self.screen_samples.fetch_add(1, Ordering::Relaxed);
}
SCStreamOutputType::Audio => {
self.audio_samples.fetch_add(1, Ordering::Relaxed);
}
#[cfg(feature = "macos_15_0")]
SCStreamOutputType::Microphone => {
self.mic_samples.fetch_add(1, Ordering::Relaxed);
}
#[cfg(not(feature = "macos_15_0"))]
_ => {}
}
}
}
struct SharedHandler(Arc<LeakTestHandler>);
impl SCStreamOutputTrait for SharedHandler {
fn did_output_sample_buffer(&self, sample: CMSampleBuffer, of_type: SCStreamOutputType) {
self.0.did_output_sample_buffer(sample, of_type);
}
}
fn main() {
init_cg();
println!("🔍 Memory Leak Detection Test");
println!("==============================\n");
let iterations = 3;
let capture_duration = Duration::from_millis(500);
println!("Configuration:");
println!(" • Iterations: {iterations}");
println!(" • Capture duration per iteration: {capture_duration:?}");
println!();
println!("📋 Testing SCShareableContent queries...");
test_shareable_content_queries();
println!("\n📹 Testing different filter configurations...\n");
for i in 1..=iterations {
println!("--- Iteration {i}/{iterations} ---\n");
println!(" 1️⃣ Display filter (exclude windows, audio enabled)");
test_capture_with_filter(FilterType::DisplayExcludeWindows, &capture_duration);
println!(" 2️⃣ Display filter (include windows)");
test_capture_with_filter(FilterType::DisplayIncludeWindows, &capture_duration);
println!(" 3️⃣ Display filter (exclude apps)");
test_capture_with_filter(FilterType::DisplayExcludeApps, &capture_duration);
println!(" 4️⃣ Display filter (include apps)");
test_capture_with_filter(FilterType::DisplayIncludeApps, &capture_duration);
println!(" 5️⃣ Single window filter");
test_capture_with_filter(FilterType::SingleWindow, &capture_duration);
#[cfg(feature = "macos_15_0")]
{
println!(" 6️⃣ Full config (audio + microphone)");
test_capture_with_filter(FilterType::FullConfigWithMic, &capture_duration);
}
println!();
}
println!("⚙️ Testing configuration variations...");
test_configuration_variations();
println!("\n🧪 Running leak analysis...\n");
let result = check_for_leaks();
match result {
LeakResult::NoLeaks => {
println!("✅ No memory leaks detected!");
}
LeakResult::AppleFrameworkLeaksOnly(count) => {
println!("⚠️ Apple framework leaks detected: {count} leaks (ignored)");
println!(" These are bugs in Apple's ScreenCaptureKit, not our code.");
}
LeakResult::LeaksDetected(details) => {
println!("❌ Memory leaks detected in our code!");
println!("\nDetails:\n{details}");
std::process::exit(1);
}
LeakResult::Error(msg) => {
println!("⚠️ Could not run leak check: {msg}");
std::process::exit(2);
}
LeakResult::NotDebuggable => {
println!("⚠️ Process is not debuggable (security restriction)");
println!(" To run leak check locally, try one of:");
println!(" • Run with sudo");
println!(" • Disable SIP (not recommended for production machines)");
println!(" • Code sign binary with get-task-allow entitlement");
println!("\n✅ Memory tests passed (leak check skipped due to security)");
}
}
}
#[derive(Clone, Copy)]
enum FilterType {
DisplayExcludeWindows,
DisplayIncludeWindows,
DisplayExcludeApps,
DisplayIncludeApps,
SingleWindow,
#[cfg(feature = "macos_15_0")]
FullConfigWithMic,
}
fn test_shareable_content_queries() {
let content = match SCShareableContent::get() {
Ok(c) => c,
Err(e) => {
eprintln!("⚠️ Skipping content queries — screen recording permission required.");
eprintln!(
" Grant permission via System Settings → Privacy & Security → Screen Recording."
);
eprintln!(" Underlying error: {e:?}");
return;
}
};
let displays = content.displays();
println!(" Found {} display(s)", displays.len());
for display in &displays {
let _id = display.display_id();
let _width = display.width();
let _height = display.height();
let _frame = display.frame();
}
let windows = content.windows();
println!(" Found {} window(s)", windows.len());
for window in windows.iter().take(10) {
let _id = window.window_id();
let _title = window.title();
let _frame = window.frame();
let _on_screen = window.is_on_screen();
let _layer = window.window_layer();
let _app = window.owning_application();
}
let apps = content.applications();
println!(" Found {} application(s)", apps.len());
for app in apps.iter().take(10) {
let _name = app.application_name();
let _bundle_id = app.bundle_identifier();
let _pid = app.process_id();
}
}
fn collect_window_refs(windows: &[SCWindow], count: usize) -> Vec<&SCWindow> {
windows.iter().take(count).collect()
}
fn collect_app_refs(apps: &[SCRunningApplication], count: usize) -> Vec<&SCRunningApplication> {
apps.iter().take(count).collect()
}
#[allow(clippy::too_many_lines)]
fn test_capture_with_filter(filter_type: FilterType, duration: &Duration) {
let content = match SCShareableContent::get() {
Ok(c) => c,
Err(e) => {
eprintln!(
"⚠️ Skipping capture-with-filter test — screen recording permission required."
);
eprintln!(" Underlying error: {e:?}");
return;
}
};
let displays = content.displays();
let Some(display) = displays.first() else {
eprintln!("⚠️ No displays available — skipping capture-with-filter test.");
return;
};
let windows = content.windows();
let apps = content.applications();
let handler = Arc::new(LeakTestHandler::new());
let filter = match filter_type {
FilterType::DisplayExcludeWindows => {
let exclude = collect_window_refs(&windows, 5);
SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&exclude)
.build()
}
FilterType::DisplayIncludeWindows => {
let include = collect_window_refs(&windows, 3);
SCContentFilter::create()
.with_display(display)
.with_including_windows(&include)
.build()
}
FilterType::DisplayExcludeApps => {
let exclude_apps = collect_app_refs(&apps, 2);
let except_windows = collect_window_refs(&windows, 1);
SCContentFilter::create()
.with_display(display)
.with_excluding_applications(&exclude_apps, &except_windows)
.build()
}
FilterType::DisplayIncludeApps => {
let include_apps = collect_app_refs(&apps, 3);
let except_windows = collect_window_refs(&windows, 1);
SCContentFilter::create()
.with_display(display)
.with_including_applications(&include_apps, &except_windows)
.build()
}
FilterType::SingleWindow => {
let window = windows
.iter()
.find(|w| w.is_on_screen())
.unwrap_or(&windows[0]);
SCContentFilter::create().with_window(window).build()
}
#[cfg(feature = "macos_15_0")]
FilterType::FullConfigWithMic => SCContentFilter::create()
.with_display(display)
.with_excluding_windows(&[])
.build(),
};
#[cfg(feature = "macos_14_2")]
let _rect = filter.content_rect();
#[cfg(feature = "macos_14_0")]
let _scale = filter.point_pixel_scale();
let config = match filter_type {
#[cfg(feature = "macos_15_0")]
FilterType::FullConfigWithMic => SCStreamConfiguration::new()
.with_width(320)
.with_height(240)
.with_pixel_format(PixelFormat::BGRA)
.with_captures_audio(true)
.with_captures_microphone(true)
.with_sample_rate(48000)
.with_channel_count(2),
_ => SCStreamConfiguration::new()
.with_width(320)
.with_height(240)
.with_pixel_format(PixelFormat::BGRA)
.with_captures_audio(true)
.with_sample_rate(24000)
.with_channel_count(2),
};
let mut stream = SCStream::new(&filter, &config);
stream.add_output_handler(SharedHandler(handler.clone()), SCStreamOutputType::Screen);
stream.add_output_handler(SharedHandler(handler.clone()), SCStreamOutputType::Audio);
#[cfg(feature = "macos_15_0")]
if matches!(filter_type, FilterType::FullConfigWithMic) {
stream.add_output_handler(
SharedHandler(handler.clone()),
SCStreamOutputType::Microphone,
);
}
if let Err(e) = stream.start_capture() {
eprintln!(" ⚠️ Failed to start: {e}");
return;
}
thread::sleep(*duration);
if let Err(e) = stream.stop_capture() {
eprintln!(" ⚠️ Failed to stop: {e}");
}
handler.report();
drop(stream);
println!(" ✓ Done");
}
fn test_configuration_variations() {
let configs = [
SCStreamConfiguration::new().with_width(64).with_height(64),
SCStreamConfiguration::new()
.with_width(128)
.with_height(128)
.with_pixel_format(PixelFormat::YCbCr_420v),
SCStreamConfiguration::new()
.with_width(256)
.with_height(256)
.with_source_rect(CGRect::new(0.0, 0.0, 100.0, 100.0))
.with_destination_rect(CGRect::new(0.0, 0.0, 256.0, 256.0))
.with_scales_to_fit(true),
SCStreamConfiguration::new()
.with_width(1920)
.with_height(1080)
.with_shows_cursor(true)
.with_queue_depth(8),
SCStreamConfiguration::new()
.with_width(100)
.with_height(100)
.with_captures_audio(true)
.with_sample_rate(48000)
.with_channel_count(2),
];
println!(" Testing {} configuration variations...", configs.len());
for (i, _config) in configs.iter().enumerate() {
println!(" Config {}: ✓", i + 1);
}
}
enum LeakResult {
NoLeaks,
AppleFrameworkLeaksOnly(usize),
LeaksDetected(String),
NotDebuggable,
Error(String),
}
fn check_for_leaks() -> LeakResult {
let pid = std::process::id();
let output = match Command::new("leaks")
.args([pid.to_string(), "-c".to_string()])
.output()
{
Ok(output) => output,
Err(e) => return LeakResult::Error(format!("Failed to execute leaks command: {e}")),
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("not debuggable") || stderr.contains("security restrictions") {
return LeakResult::NotDebuggable;
}
if !stdout.is_empty() {
println!("leaks stdout:\n{stdout}");
}
if !stderr.is_empty() {
println!("leaks stderr:\n{stderr}");
}
if stdout.contains("0 leaks for 0 total leaked bytes") {
return LeakResult::NoLeaks;
}
let leak_count = stdout
.lines()
.find(|line| line.contains("leaks for") && line.contains("total leaked bytes"))
.and_then(|line| {
line.split("leaks for")
.next()
.and_then(|prefix| prefix.split_whitespace().last())
.and_then(|s| s.parse::<usize>().ok())
})
.unwrap_or(0);
let apple_framework_leaks = stdout.contains("CMCapture")
|| stdout.contains("FigRemoteOperationReceiver")
|| stdout.contains("SCStream(SCContentSharing)")
|| stdout.contains("CoreMedia")
|| stdout.contains("VideoToolbox");
let our_code_leaks = stdout.contains("screencapturekit");
if apple_framework_leaks && !our_code_leaks {
return LeakResult::AppleFrameworkLeaksOnly(leak_count);
}
LeakResult::LeaksDetected(stdout.to_string())
}