screencapturekit/lib.rs
1#![doc = include_str!("../README.md")]
2//!
3//! ---
4//!
5//! # API Documentation
6//!
7//! Safe, idiomatic Rust bindings for Apple's [ScreenCaptureKit] framework.
8//!
9//! Capture screen content, windows, and applications with high performance on macOS 13.0+.
10//!
11//! [ScreenCaptureKit]: https://developer.apple.com/documentation/screencapturekit
12//!
13//! ## Features
14//!
15//! - **Screen and window capture** - Capture displays, windows, or specific applications
16//! - **Audio capture** - System audio and microphone input (macOS 13.0+)
17//! - **Real-time frame processing** - High-performance callbacks with custom dispatch queues
18//! - **Async support** - Runtime-agnostic async API (Tokio, async-std, smol, etc.)
19//! - **Zero-copy GPU access** - Direct [`IOSurface`] access for Metal/OpenGL integration
20//! - **Screenshots** - Single-frame capture without streaming (macOS 14.0+)
21//! - **Recording** - Direct-to-file video recording (macOS 15.0+)
22//! - **Content Picker** - System UI for user content selection (macOS 14.0+)
23//!
24//! ## Installation
25//!
26//! Add to your `Cargo.toml`:
27//!
28//! ```toml
29//! [dependencies]
30//! screencapturekit = "8"
31//! ```
32//!
33//! For async support:
34//!
35//! ```toml
36//! [dependencies]
37//! screencapturekit = { version = "8", features = ["async"] }
38//! ```
39//!
40//! ## Quick Start
41//!
42//! ### 1. Request Permission
43//!
44//! Screen recording requires user permission. Add to your `Info.plist`:
45//!
46//! ```xml
47//! <key>NSScreenCaptureUsageDescription</key>
48//! <string>This app needs screen recording permission.</string>
49//! ```
50//!
51//! ### 2. Implement a Frame Handler
52//!
53//! You can use either a struct or a closure:
54//!
55//! **Struct-based handler:**
56//! ```rust,no_run
57//! use screencapturekit::prelude::*;
58//! use std::sync::atomic::{AtomicUsize, Ordering};
59//! use std::sync::Arc;
60//!
61//! struct FrameHandler {
62//! count: Arc<AtomicUsize>,
63//! }
64//!
65//! impl SCStreamOutputTrait for FrameHandler {
66//! fn did_output_sample_buffer(&self, sample: CMSampleBuffer, of_type: SCStreamOutputType) {
67//! match of_type {
68//! SCStreamOutputType::Screen => {
69//! let n = self.count.fetch_add(1, Ordering::Relaxed);
70//! if n % 60 == 0 {
71//! println!("Frame {n}");
72//! }
73//! }
74//! SCStreamOutputType::Audio => {
75//! println!("Got audio samples!");
76//! }
77//! _ => {}
78//! }
79//! }
80//! }
81//! ```
82//!
83//! **Closure-based handler:**
84//! ```rust,no_run
85//! use screencapturekit::prelude::*;
86//! use std::sync::atomic::{AtomicUsize, Ordering};
87//! use std::sync::Arc;
88//!
89//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
90//! # let content = SCShareableContent::get()?;
91//! # let display = content.displays().into_iter().next().unwrap();
92//! # let filter = SCContentFilter::create().with_display(&display).with_excluding_windows(&[]).build()?;
93//! # let config = SCStreamConfiguration::new();
94//! let frame_count = Arc::new(AtomicUsize::new(0));
95//! let count_clone = frame_count.clone();
96//!
97//! let mut stream = SCStream::new(&filter, &config)?;
98//! stream.add_output_handler(
99//! move |_sample: CMSampleBuffer, _of_type: SCStreamOutputType| {
100//! count_clone.fetch_add(1, Ordering::Relaxed);
101//! },
102//! SCStreamOutputType::Screen
103//! )?;
104//! # Ok(())
105//! # }
106//! ```
107//!
108//! ### 3. Start Capturing
109//!
110//! ```rust,no_run
111//! use screencapturekit::prelude::*;
112//!
113//! # struct MyHandler;
114//! # impl SCStreamOutputTrait for MyHandler {
115//! # fn did_output_sample_buffer(&self, _: CMSampleBuffer, _: SCStreamOutputType) {}
116//! # }
117//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
118//! // Get available displays
119//! let content = SCShareableContent::get()?;
120//! let display = content.displays().into_iter().next().ok_or("No display")?;
121//!
122//! // Configure what to capture
123//! let filter = SCContentFilter::create()
124//! .with_display(&display)
125//! .with_excluding_windows(&[])
126//! .build()?;
127//!
128//! // Configure how to capture
129//! let config = SCStreamConfiguration::new()
130//! .with_width(1920)
131//! .with_height(1080)
132//! .with_pixel_format(PixelFormat::BGRA)
133//! .with_shows_cursor(true);
134//!
135//! // Create stream and add handler
136//! let mut stream = SCStream::new(&filter, &config)?;
137//! stream.add_output_handler(MyHandler, SCStreamOutputType::Screen)?;
138//!
139//! // Start capturing
140//! stream.start_capture()?;
141//!
142//! // ... capture runs in background ...
143//! std::thread::sleep(std::time::Duration::from_secs(5));
144//!
145//! stream.stop_capture()?;
146//! # Ok(())
147//! # }
148//! ```
149//!
150//! ## Configuration Options
151//!
152//! Use the builder pattern for fluent configuration:
153//!
154//! ```rust
155//! use screencapturekit::prelude::*;
156//!
157//! // For 60 FPS, use CMTime to specify frame interval
158//! let frame_interval = CMTime::new(1, 60); // 1/60th of a second
159//!
160//! let config = SCStreamConfiguration::new()
161//! // Video settings
162//! .with_width(1920)
163//! .with_height(1080)
164//! .with_pixel_format(PixelFormat::BGRA)
165//! .with_shows_cursor(true)
166//! .with_minimum_frame_interval(&frame_interval)
167//!
168//! // Audio settings
169//! .with_captures_audio(true)
170//! .with_sample_rate(48000)
171//! .with_channel_count(2);
172//! ```
173//!
174//! ### Available Pixel Formats
175//!
176//! | Format | Description | Use Case |
177//! |--------|-------------|----------|
178//! | [`PixelFormat::BGRA`] | 32-bit BGRA | General purpose, easy to use |
179//! | [`PixelFormat::l10r`] | 10-bit RGB | HDR content |
180//! | [`PixelFormat::YCbCr_420v`] | YCbCr 4:2:0 | Video encoding (H.264/HEVC) |
181//! | [`PixelFormat::YCbCr_420f`] | YCbCr 4:2:0 full range | Video encoding |
182//!
183//! ## Accessing Frame Data
184//!
185//! ### Pixel Data (CPU)
186//!
187//! Lock the pixel buffer for direct CPU access:
188//!
189//! ```rust,no_run
190//! use screencapturekit::prelude::*;
191//! use screencapturekit::cv::{CVPixelBuffer, CVPixelBufferLockFlags, PixelBufferCursorExt};
192//! use std::io::{Read, Seek, SeekFrom};
193//!
194//! # fn handle(sample: CMSampleBuffer) {
195//! if let Some(buffer) = sample.pixel_buffer() {
196//! if let Ok(guard) = buffer.lock(CVPixelBufferLockFlags::READ_ONLY) {
197//! // Method 1: Direct slice access (fast)
198//! let Some(pixels) = (unsafe { guard.as_slice() }) else { return };
199//! let width = guard.width();
200//! let height = guard.height();
201//!
202//! // Method 2: Use cursor for reading specific pixels
203//! let Some(mut cursor) = (unsafe { guard.cursor() }) else { return };
204//!
205//! // Read first pixel (BGRA)
206//! if let Ok(pixel) = cursor.read_pixel() {
207//! println!("First pixel: {:?}", pixel);
208//! }
209//!
210//! // Seek to center pixel
211//! let center_x = width / 2;
212//! let center_y = height / 2;
213//! if cursor.seek_to_pixel(center_x, center_y, guard.bytes_per_row()).is_ok() {
214//! if let Ok(pixel) = cursor.read_pixel() {
215//! println!("Center pixel: {:?}", pixel);
216//! }
217//! }
218//! }
219//! }
220//! # }
221//! ```
222//!
223//! ### [`IOSurface`] (GPU)
224//!
225//! For Metal/OpenGL integration, access the underlying [`IOSurface`]:
226//!
227//! ```rust,no_run
228//! use screencapturekit::prelude::*;
229//! use screencapturekit::cm::IOSurfaceLockOptions;
230//! use screencapturekit::cv::PixelBufferCursorExt;
231//!
232//! # fn handle(sample: CMSampleBuffer) {
233//! if let Some(buffer) = sample.pixel_buffer() {
234//! // Check if IOSurface-backed (usually true for ScreenCaptureKit)
235//! if buffer.is_backed_by_io_surface() {
236//! if let Some(surface) = buffer.io_surface() {
237//! println!("Dimensions: {}x{}", surface.width(), surface.height());
238//! println!("Pixel format: 0x{:08X}", surface.pixel_format());
239//! println!("Bytes per row: {}", surface.bytes_per_row());
240//! println!("In use: {}", surface.is_in_use());
241//!
242//! // Lock for CPU access to IOSurface data
243//! if let Ok(guard) = surface.lock(IOSurfaceLockOptions::READ_ONLY) {
244//! let Some(mut cursor) = (unsafe { guard.cursor() }) else { return };
245//! if let Ok(pixel) = cursor.read_pixel() {
246//! println!("First pixel: {:?}", pixel);
247//! }
248//! }
249//! }
250//! }
251//! }
252//! # }
253//! ```
254//!
255//! ## Audio + Video Capture
256//!
257//! Capture system audio alongside video:
258//!
259//! ```rust,no_run
260//! use screencapturekit::prelude::*;
261//! use std::sync::atomic::{AtomicUsize, Ordering};
262//! use std::sync::Arc;
263//!
264//! struct AVHandler {
265//! video_count: Arc<AtomicUsize>,
266//! audio_count: Arc<AtomicUsize>,
267//! }
268//!
269//! impl SCStreamOutputTrait for AVHandler {
270//! fn did_output_sample_buffer(&self, _sample: CMSampleBuffer, of_type: SCStreamOutputType) {
271//! match of_type {
272//! SCStreamOutputType::Screen => {
273//! self.video_count.fetch_add(1, Ordering::Relaxed);
274//! }
275//! SCStreamOutputType::Audio => {
276//! self.audio_count.fetch_add(1, Ordering::Relaxed);
277//! }
278//! SCStreamOutputType::Microphone => {
279//! // Requires macOS 15.0+ and .with_captures_microphone(true)
280//! }
281//! }
282//! }
283//! }
284//!
285//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
286//! let content = SCShareableContent::get()?;
287//! let display = content.displays().into_iter().next().ok_or("No display")?;
288//!
289//! let filter = SCContentFilter::create()
290//! .with_display(&display)
291//! .with_excluding_windows(&[])
292//! .build()?;
293//!
294//! let config = SCStreamConfiguration::new()
295//! .with_width(1920)
296//! .with_height(1080)
297//! .with_captures_audio(true) // Enable system audio
298//! .with_sample_rate(48000) // 48kHz
299//! .with_channel_count(2); // Stereo
300//!
301//! let handler = AVHandler {
302//! video_count: Arc::new(AtomicUsize::new(0)),
303//! audio_count: Arc::new(AtomicUsize::new(0)),
304//! };
305//!
306//! let mut stream = SCStream::new(&filter, &config)?;
307//! stream.add_output_handler(handler, SCStreamOutputType::Screen)?;
308//! stream.start_capture()?;
309//! # Ok(())
310//! # }
311//! ```
312//!
313//! ## Dynamic Stream Updates
314//!
315//! Update configuration or content filter while streaming.
316//! `update_configuration` needs the `macos_14_0` feature;
317//! `update_content_filter` is part of the baseline.
318//!
319//! ```rust,no_run
320//! # #[cfg(feature = "macos_14_0")]
321//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
322//! use screencapturekit::prelude::*;
323//!
324//! # let content = SCShareableContent::get()?;
325//! # let display = content.displays().into_iter().next().unwrap();
326//! # let filter = SCContentFilter::create().with_display(&display).with_excluding_windows(&[]).build()?;
327//! # let config = SCStreamConfiguration::new().with_width(640).with_height(480);
328//! # struct MyHandler;
329//! # impl SCStreamOutputTrait for MyHandler {
330//! # fn did_output_sample_buffer(&self, _: CMSampleBuffer, _: SCStreamOutputType) {}
331//! # }
332//! let mut stream = SCStream::new(&filter, &config)?;
333//! stream.add_output_handler(MyHandler, SCStreamOutputType::Screen).expect("register output handler");
334//! stream.start_capture()?;
335//!
336//! // Capture at initial resolution...
337//! std::thread::sleep(std::time::Duration::from_secs(2));
338//!
339//! // Update to higher resolution while streaming
340//! let new_config = SCStreamConfiguration::new()
341//! .with_width(1920)
342//! .with_height(1080);
343//! stream.update_configuration(&new_config)?;
344//!
345//! // Switch to a different window
346//! let windows = content.windows();
347//! if let Some(window) = windows.iter().find(|w| w.is_on_screen()) {
348//! let window_filter = SCContentFilter::create().with_window(window).build()?;
349//! stream.update_content_filter(&window_filter)?;
350//! }
351//!
352//! stream.stop_capture()?;
353//! # Ok(())
354//! # }
355//! # #[cfg(not(feature = "macos_14_0"))]
356//! # fn main() {}
357//! ```
358//!
359//! ## Error Handling with Delegates
360//!
361//! Handle stream errors gracefully using delegates:
362//!
363//! ```rust,no_run
364//! use screencapturekit::prelude::*;
365//! use screencapturekit::stream::ErrorHandler;
366//!
367//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
368//! # let content = SCShareableContent::get()?;
369//! # let display = content.displays().into_iter().next().unwrap();
370//! # let filter = SCContentFilter::create().with_display(&display).with_excluding_windows(&[]).build()?;
371//! # let config = SCStreamConfiguration::new();
372//! // Create an error handler using a closure
373//! let error_handler = ErrorHandler::new(|error| {
374//! eprintln!("Stream error: {error}");
375//! });
376//!
377//! // Create stream with delegate
378//! let mut stream = SCStream::new_with_delegate(&filter, &config, error_handler)?;
379//! stream.add_output_handler(
380//! |_sample, _type| { /* process frames */ },
381//! SCStreamOutputType::Screen
382//! )?;
383//! stream.start_capture()?;
384//! # Ok(())
385//! # }
386//! ```
387//!
388//! ## Custom Dispatch Queues
389//!
390//! Control which thread/queue handles frame callbacks:
391//!
392//! ```rust,no_run
393//! use screencapturekit::prelude::*;
394//! use screencapturekit::dispatch_queue::{DispatchQueue, DispatchQoS};
395//!
396//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
397//! # let content = SCShareableContent::get()?;
398//! # let display = content.displays().into_iter().next().unwrap();
399//! # let filter = SCContentFilter::create().with_display(&display).with_excluding_windows(&[]).build()?;
400//! # let config = SCStreamConfiguration::new();
401//! let mut stream = SCStream::new(&filter, &config)?;
402//!
403//! // Create a high-priority queue for frame processing
404//! let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
405//!
406//! stream.add_output_handler_with_queue(
407//! |_sample, _type| { /* called on custom queue */ },
408//! SCStreamOutputType::Screen,
409//! Some(&queue)
410//! )?;
411//! # Ok(())
412//! # }
413//! ```
414//!
415//! ## Async API
416//!
417//! Enable the `async` feature for async/await support. The async API is
418//! **executor-agnostic** and works with Tokio, async-std, smol, or any runtime:
419//!
420//! ```ignore
421//! use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
422//! use screencapturekit::prelude::*;
423//!
424//! async fn capture() -> Result<(), Box<dyn std::error::Error>> {
425//! // Get content asynchronously (true async - no blocking)
426//! let content = AsyncSCShareableContent::get().await?;
427//! let display = &content.displays()[0];
428//!
429//! let filter = SCContentFilter::create()
430//! .with_display(display)
431//! .with_excluding_windows(&[])
432//! .build()?;
433//!
434//! let config = SCStreamConfiguration::new()
435//! .with_width(1920)
436//! .with_height(1080);
437//!
438//! // Create async stream with 30-frame buffer
439//! let stream = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen)?;
440//! stream.start_capture()?;
441//!
442//! // Async iteration over frames
443//! let mut count = 0;
444//! while count < 100 {
445//! if let Some(_frame) = stream.next().await {
446//! count += 1;
447//! }
448//! }
449//!
450//! stream.stop_capture()?;
451//! Ok(())
452//! }
453//!
454//! // Concurrent async operations
455//! async fn concurrent_queries() -> Result<(), Box<dyn std::error::Error>> {
456//! let (result1, result2) = tokio::join!(
457//! AsyncSCShareableContent::get(),
458//! AsyncSCShareableContent::create()
459//! .with_on_screen_windows_only(true)
460//! .get(),
461//! );
462//! Ok(())
463//! }
464//! ```
465//!
466//! ## Screenshots (macOS 14.0+)
467//!
468//! Take single screenshots without setting up a stream:
469//!
470//! ```ignore
471//! use screencapturekit::prelude::*;
472//! use screencapturekit::screenshot_manager::SCScreenshotManager;
473//!
474//! let content = SCShareableContent::get()?;
475//! let display = &content.displays()[0];
476//!
477//! let filter = SCContentFilter::create()
478//! .with_display(display)
479//! .with_excluding_windows(&[])
480//! .build()?;
481//!
482//! let config = SCStreamConfiguration::new()
483//! .with_width(1920)
484//! .with_height(1080);
485//!
486//! // Capture screenshot as CGImage
487//! let image = SCScreenshotManager::capture_image(&filter, &config)?;
488//! println!("Screenshot: {}x{}", image.width(), image.height());
489//!
490//! // Or capture as CMSampleBuffer for more control
491//! let sample_buffer = SCScreenshotManager::capture_sample_buffer(&filter, &config)?;
492//! ```
493//!
494//! ## Recording (macOS 15.0+)
495//!
496//! Record directly to a video file:
497//!
498//! ```ignore
499//! use screencapturekit::prelude::*;
500//! use screencapturekit::recording_output::{
501//! SCRecordingOutput, SCRecordingOutputConfiguration,
502//! SCRecordingOutputCodec, SCRecordingOutputFileType
503//! };
504//! use std::path::PathBuf;
505//!
506//! let content = SCShareableContent::get()?;
507//! let display = &content.displays()[0];
508//!
509//! let filter = SCContentFilter::create()
510//! .with_display(display)
511//! .with_excluding_windows(&[])
512//! .build()?;
513//!
514//! let stream_config = SCStreamConfiguration::new()
515//! .with_width(1920)
516//! .with_height(1080);
517//!
518//! // Configure recording output
519//! let output_path = PathBuf::from("/tmp/screen_recording.mp4");
520//! let recording_config = SCRecordingOutputConfiguration::new()?
521//! .with_output_url(&output_path)?
522//! .with_video_codec(SCRecordingOutputCodec::H264)
523//! .with_output_file_type(SCRecordingOutputFileType::MP4);
524//!
525//! let recording_output = SCRecordingOutput::new(&recording_config)
526//! .ok_or("Failed to create recording output")?;
527//!
528//! // Start stream and add recording
529//! let stream = SCStream::new(&filter, &stream_config)?;
530//! stream.add_recording_output(&recording_output)?;
531//! stream.start_capture()?;
532//!
533//! // Record for 10 seconds
534//! std::thread::sleep(std::time::Duration::from_secs(10));
535//!
536//! // Check recording stats
537//! let duration = recording_output.recorded_duration();
538//! let file_size = recording_output.recorded_file_size();
539//! println!("Recorded {}/{} seconds, {} bytes", duration.value, duration.timescale, file_size);
540//!
541//! stream.remove_recording_output(&recording_output)?;
542//! stream.stop_capture()?;
543//! ```
544//!
545//! ## Module Organization
546//!
547//! | Module | Description |
548//! |--------|-------------|
549//! | [`stream`] | Stream configuration and management ([`SCStream`], [`SCContentFilter`]) |
550//! | [`shareable_content`] | Display, window, and application enumeration |
551//! | [`cm`] | Core Media types ([`CMSampleBuffer`], [`CMTime`], [`IOSurface`]) |
552//! | [`cv`] | Core Video types ([`CVPixelBuffer`], lock guards) |
553//! | [`cg`] | Core Graphics types ([`CGRect`], [`CGSize`]) |
554//! | [`metal`] | Metal texture helpers for zero-copy GPU rendering |
555//! | [`dispatch_queue`] | Custom dispatch queues for callbacks |
556//! | [`error`] | Error types and result aliases |
557//! | `async_api` | Async wrappers (requires `async` feature) |
558//! | [`screenshot_manager`] | Single-frame capture (macOS 14.0+) |
559//! | `recording_output` | Direct file recording (macOS 15.0+) |
560//!
561//! [`SCStream`]: stream::sc_stream::SCStream
562//! [`SCContentFilter`]: stream::content_filter::SCContentFilter
563//! [`CMSampleBuffer`]: cm::CMSampleBuffer
564//! [`CMTime`]: cm::CMTime
565//! [`IOSurface`]: cm::IOSurface
566//! [`CVPixelBuffer`]: cv::CVPixelBuffer
567//! [`CGRect`]: cg::CGRect
568//! [`CGSize`]: cg::CGSize
569//!
570//! ## Feature Flags
571//!
572//! | Feature | Description |
573//! |---------|-------------|
574//! | `async` | Runtime-agnostic async API |
575//! | `macos_13_0` | macOS 13.0+ APIs (audio capture, synchronization clock) |
576//! | `macos_14_0` | macOS 14.0+ APIs (screenshots, content picker) |
577//! | `macos_14_2` | macOS 14.2+ APIs (menu bar, child windows, presenter overlay) |
578//! | `macos_14_4` | macOS 14.4+ APIs (current process shareable content) |
579//! | `macos_15_0` | macOS 15.0+ APIs (recording output, HDR, microphone) |
580//! | `macos_15_2` | macOS 15.2+ APIs (screenshot in rect, stream delegates) |
581//! | `macos_26_0` | macOS 26.0+ APIs (advanced screenshot config, HDR output) |
582//!
583//! Features are cumulative: enabling `macos_15_0` also enables all earlier versions.
584//!
585//! ## Platform Requirements
586//!
587//! - **macOS 13.0+** (Ventura) - `ScreenCaptureKit` itself starts at 12.3, but
588//! this crate's Swift bridge is built with a 13.0 deployment target and uses
589//! the macOS 13 audio APIs unconditionally
590//! - **Screen Recording Permission** - Must be granted by user in System Preferences
591//! - **Hardened Runtime** - Required for notarized apps
592//!
593//! ## Examples
594//!
595//! See the [examples directory](https://github.com/doom-fish/screencapturekit-rs/tree/main/examples):
596//!
597//! | Example | Description |
598//! |---------|-------------|
599//! | `01_basic_capture` | Simplest screen capture |
600//! | `02_window_capture` | Capture specific windows |
601//! | `03_audio_capture` | Audio + video capture |
602//! | `04_pixel_access` | Read pixel data with cursor API |
603//! | `05_screenshot` | Single screenshot (macOS 14.0+) |
604//! | `06_iosurface` | Zero-copy GPU buffer access |
605//! | `07_list_content` | List available displays, windows, apps |
606//! | `08_async` | Async/await API with any runtime |
607//! | `09_closure_handlers` | Closure-based handlers |
608//! | `10_recording_output` | Direct video recording (macOS 15.0+) |
609//! | `11_content_picker` | System content picker UI (macOS 14.0+) |
610//! | `12_stream_updates` | Dynamic config/filter updates |
611//! | `13_advanced_config` | HDR, presets, microphone (macOS 15.0+) |
612//! | `14_app_capture` | Application-based filtering |
613//! | `15_memory_leak_check` | Memory leak detection |
614//! | `16_full_metal_app` | Full Metal GUI application |
615//! | `17_metal_textures` | Metal texture creation from `IOSurface` |
616//!
617//! ## Common Patterns
618//!
619//! ### Capture Window by Title
620//!
621//! ```rust,no_run
622//! use screencapturekit::prelude::*;
623//!
624//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
625//! let content = SCShareableContent::get()?;
626//! let windows = content.windows();
627//! let window = windows
628//! .iter()
629//! .find(|w| w.title().is_some_and(|t| t.contains("Safari")))
630//! .ok_or("Window not found")?;
631//!
632//! let filter = SCContentFilter::create()
633//! .with_window(window)
634//! .build()?;
635//! # Ok(())
636//! # }
637//! ```
638//!
639//! ### Capture Specific Application
640//!
641//! ```rust,no_run
642//! use screencapturekit::prelude::*;
643//!
644//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
645//! let content = SCShareableContent::get()?;
646//! let display = content.displays().into_iter().next().ok_or("No display")?;
647//!
648//! // Find app by bundle ID
649//! let apps = content.applications();
650//! let safari = apps
651//! .iter()
652//! .find(|app| app.bundle_identifier() == "com.apple.Safari")
653//! .ok_or("Safari not found")?;
654//!
655//! // Capture only windows from this app
656//! let filter = SCContentFilter::create()
657//! .with_display(&display)
658//! .with_including_applications(&[safari], &[]) // Include Safari, no excepted windows
659//! .build()?;
660//! # Ok(())
661//! # }
662//! ```
663//!
664//! ### Exclude Your Own App's Windows
665//!
666//! ```rust,no_run
667//! use screencapturekit::prelude::*;
668//!
669//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
670//! let content = SCShareableContent::get()?;
671//! let display = content.displays().into_iter().next().ok_or("No display")?;
672//!
673//! // Find our app's windows
674//! let windows = content.windows();
675//! let my_windows: Vec<&SCWindow> = windows
676//! .iter()
677//! .filter(|w| w.owning_application()
678//! .map(|app| app.bundle_identifier() == "com.mycompany.myapp")
679//! .unwrap_or(false))
680//! .collect();
681//!
682//! // Capture everything except our windows
683//! let filter = SCContentFilter::create()
684//! .with_display(&display)
685//! .with_excluding_windows(&my_windows)
686//! .build()?;
687//! # Ok(())
688//! # }
689//! ```
690//!
691//! ### List All Available Content
692//!
693//! ```rust,no_run
694//! use screencapturekit::prelude::*;
695//!
696//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
697//! let content = SCShareableContent::get()?;
698//!
699//! println!("=== Displays ===");
700//! for display in content.displays() {
701//! println!(" Display {}: {}x{}", display.display_id(), display.width(), display.height());
702//! }
703//!
704//! println!("\n=== Windows ===");
705//! for window in content.windows().iter().filter(|w| w.is_on_screen()) {
706//! println!(" [{}] {} - {}",
707//! window.window_id(),
708//! window.owning_application()
709//! .map(|app| app.application_name())
710//! .unwrap_or_default(),
711//! window.title().unwrap_or_default()
712//! );
713//! }
714//!
715//! println!("\n=== Applications ===");
716//! for app in content.applications() {
717//! println!(" {} ({})", app.application_name(), app.bundle_identifier());
718//! }
719//! # Ok(())
720//! # }
721//! ```
722//!
723//! [`PixelFormat::BGRA`]: stream::configuration::PixelFormat::BGRA
724//! [`PixelFormat::l10r`]: stream::configuration::PixelFormat::l10r
725//! [`PixelFormat::YCbCr_420v`]: stream::configuration::PixelFormat::YCbCr_420v
726//! [`PixelFormat::YCbCr_420f`]: stream::configuration::PixelFormat::YCbCr_420f
727
728#![doc(html_root_url = "https://docs.rs/screencapturekit")]
729#![cfg_attr(docsrs, feature(doc_cfg))]
730#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
731#![allow(clippy::must_use_candidate)]
732#![allow(clippy::missing_const_for_fn)]
733
734pub mod audio_devices;
735pub mod cg;
736pub mod cm;
737#[cfg(feature = "macos_14_0")]
738#[cfg_attr(docsrs, doc(cfg(feature = "macos_14_0")))]
739pub mod content_sharing_picker;
740pub mod cv;
741pub mod dispatch_queue;
742pub mod error;
743pub mod ffi;
744pub mod metal;
745
746pub use apple_cf::cg::CGImage;
747/// Re-export of the lightweight [`apple-metal`](https://crates.io/crates/apple-metal)
748/// crate so downstream code can use either `ScreenCaptureKit`'s full
749/// Metal renderer ([`crate::metal`]) or the minimal core Metal
750/// device/texture surface from `apple-metal` without an extra
751/// `Cargo.toml` line.
752///
753/// `IOSurface` helpers remain available on [`crate::metal::IOSurfaceMetalExt`],
754/// and `screencapturekit::metal::MetalDevice::as_apple_metal()` bridges
755/// between the two device handles.
756pub use apple_metal;
757#[cfg(feature = "macos_15_0")]
758#[cfg_attr(docsrs, doc(cfg(feature = "macos_15_0")))]
759pub mod recording_output;
760#[cfg(feature = "macos_14_0")]
761#[cfg_attr(docsrs, doc(cfg(feature = "macos_14_0")))]
762pub mod screenshot_manager;
763pub mod shareable_content;
764pub mod stream;
765pub mod utils;
766
767#[cfg(feature = "async")]
768#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
769pub mod async_api;
770
771// Re-export commonly used types
772pub use cm::{
773 codec_types, media_types, AudioBuffer, AudioBufferList, CMFormatDescription, CMSampleBuffer,
774 CMSampleTimingInfo, CMTime, IOSurface, SCFrameStatus,
775};
776pub use cv::{CVPixelBuffer, CVPixelBufferPool};
777pub use utils::FourCharCode;
778
779/// Prelude module for convenient imports
780///
781/// Import everything you need with:
782/// ```rust
783/// use screencapturekit::prelude::*;
784/// ```
785///
786/// # What's NOT in the prelude
787///
788/// The prelude intentionally only contains the **always-available**
789/// types — anything in a feature-gated module is omitted to avoid
790/// `cargo doc` warnings and conditional re-export complexity. To use
791/// the version-gated APIs, import them explicitly:
792///
793/// | Feature | Module to import explicitly |
794/// |---|---|
795/// | `macos_14_0` | `screencapturekit::screenshot_manager`, `screencapturekit::content_sharing_picker` |
796/// | `macos_15_0` | `screencapturekit::recording_output` |
797/// | `async` | `screencapturekit::async_api` |
798///
799/// Example:
800/// ```rust,no_run
801/// # #[cfg(feature = "macos_14_0")]
802/// use screencapturekit::prelude::*;
803/// # #[cfg(feature = "macos_14_0")]
804/// use screencapturekit::screenshot_manager::SCScreenshotManager;
805/// ```
806pub mod prelude {
807 pub use crate::audio_devices::AudioInputDevice;
808 pub use crate::cg::{CGPoint, CGRect, CGSize};
809 pub use crate::cm::{CMSampleBuffer, CMSampleBufferExt, CMSampleBufferSCExt, CMTime};
810 pub use crate::dispatch_queue::{DispatchQoS, DispatchQueue};
811 pub use crate::error::{SCError, SCResult};
812 pub use crate::shareable_content::{
813 SCDisplay, SCRunningApplication, SCShareableContent, SCWindow,
814 };
815 pub use crate::stream::{
816 configuration::{PixelFormat, SCStreamConfiguration},
817 content_filter::SCContentFilter,
818 delegate_trait::SCStreamDelegateTrait,
819 output_trait::SCStreamOutputTrait,
820 output_type::SCStreamOutputType,
821 sc_stream::{SCStream, StreamIdentity},
822 ErrorHandler,
823 };
824}