screencapturekit/
async_api.rs

1//! Async API for `ScreenCaptureKit`
2//!
3//! This module provides async versions of operations when the `async` feature is enabled.
4//! The async API is **executor-agnostic** and works with any async runtime (Tokio, async-std, smol, etc.).
5//!
6//! ## Available Types
7//!
8//! | Type | Description |
9//! |------|-------------|
10//! | [`AsyncSCShareableContent`] | Async content queries |
11//! | [`AsyncSCStream`] | Async stream with frame iteration |
12//! | [`AsyncSCScreenshotManager`] | Async screenshot capture (macOS 14.0+) |
13//! | [`AsyncSCContentSharingPicker`] | Async content picker UI (macOS 14.0+) |
14//! | [`AsyncSCRecordingOutput`] | Async recording with events (macOS 15.0+) |
15//!
16//! ## Runtime Agnostic Design
17//!
18//! This async API uses only `std` types and works with **any** async runtime:
19//! - Uses callback-based Swift FFI for true async operations
20//! - Uses `std::sync::{Arc, Mutex}` for synchronization
21//! - Uses `std::task::{Poll, Waker}` for async primitives
22//! - Uses `std::future::Future` trait
23//!
24//! ## Examples
25//!
26//! ### Basic Async Content Query
27//!
28//! ```rust,no_run
29//! # #[tokio::main]
30//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
31//! use screencapturekit::async_api::AsyncSCShareableContent;
32//!
33//! let content = AsyncSCShareableContent::get().await?;
34//! println!("Found {} displays", content.displays().len());
35//! println!("Found {} windows", content.windows().len());
36//! # Ok(())
37//! # }
38//! ```
39//!
40//! ### Async Stream with Frame Iteration
41//!
42//! ```rust,no_run
43//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
44//! use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
45//! use screencapturekit::stream::configuration::SCStreamConfiguration;
46//! use screencapturekit::stream::content_filter::SCContentFilter;
47//! use screencapturekit::stream::output_type::SCStreamOutputType;
48//!
49//! let content = AsyncSCShareableContent::get().await?;
50//! let display = &content.displays()[0];
51//! let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
52//! let config = SCStreamConfiguration::new().with_width(1920).with_height(1080);
53//!
54//! let stream = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen);
55//! stream.start_capture()?;
56//!
57//! // Process frames asynchronously
58//! for _ in 0..100 {
59//!     if let Some(frame) = stream.next().await {
60//!         println!("Got frame at {:?}", frame.presentation_timestamp());
61//!     }
62//! }
63//!
64//! stream.stop_capture()?;
65//! # Ok(())
66//! # }
67//! ```
68
69use crate::error::SCError;
70use crate::shareable_content::SCShareableContent;
71use crate::stream::configuration::SCStreamConfiguration;
72use crate::stream::content_filter::SCContentFilter;
73use crate::utils::completion::{error_from_cstr, AsyncCompletion, AsyncCompletionFuture};
74use std::ffi::c_void;
75use std::future::Future;
76use std::pin::Pin;
77use std::sync::{Arc, Mutex};
78use std::task::{Context, Poll, Waker};
79
80// ============================================================================
81// AsyncSCShareableContent - True async with callback-based FFI
82// ============================================================================
83
84/// Callback from Swift FFI for shareable content
85extern "C" fn shareable_content_callback(
86    content: *const c_void,
87    error: *const i8,
88    user_data: *mut c_void,
89) {
90    if !error.is_null() {
91        let error_msg = unsafe { error_from_cstr(error) };
92        unsafe { AsyncCompletion::<SCShareableContent>::complete_err(user_data, error_msg) };
93    } else if !content.is_null() {
94        let sc = unsafe { SCShareableContent::from_ptr(content) };
95        unsafe { AsyncCompletion::complete_ok(user_data, sc) };
96    } else {
97        unsafe {
98            AsyncCompletion::<SCShareableContent>::complete_err(
99                user_data,
100                "Unknown error".to_string(),
101            );
102        };
103    }
104}
105
106/// Future for async shareable content retrieval
107pub struct AsyncShareableContentFuture {
108    inner: AsyncCompletionFuture<SCShareableContent>,
109}
110
111impl std::fmt::Debug for AsyncShareableContentFuture {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.debug_struct("AsyncShareableContentFuture")
114            .finish_non_exhaustive()
115    }
116}
117
118impl Future for AsyncShareableContentFuture {
119    type Output = Result<SCShareableContent, SCError>;
120
121    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
122        Pin::new(&mut self.inner)
123            .poll(cx)
124            .map(|r| r.map_err(SCError::NoShareableContent))
125    }
126}
127
128/// Async wrapper for `SCShareableContent`
129///
130/// Provides async methods to retrieve displays, windows, and applications
131/// without blocking. **Executor-agnostic** - works with any async runtime.
132#[derive(Debug, Clone, Copy)]
133pub struct AsyncSCShareableContent;
134
135impl AsyncSCShareableContent {
136    /// Asynchronously get the shareable content (displays, windows, applications)
137    ///
138    /// Uses callback-based Swift FFI for true async operation.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if:
143    /// - Screen recording permission is not granted
144    /// - The system fails to retrieve shareable content
145    pub fn get() -> AsyncShareableContentFuture {
146        Self::create().get()
147    }
148
149    /// Create options builder for customizing shareable content retrieval
150    #[must_use]
151    pub fn create() -> AsyncSCShareableContentOptions {
152        AsyncSCShareableContentOptions::default()
153    }
154}
155
156/// Options for async shareable content retrieval
157#[derive(Default, Debug, Clone, PartialEq, Eq)]
158pub struct AsyncSCShareableContentOptions {
159    exclude_desktop_windows: bool,
160    on_screen_windows_only: bool,
161}
162
163impl AsyncSCShareableContentOptions {
164    /// Exclude desktop windows from the shareable content
165    #[must_use]
166    pub fn with_exclude_desktop_windows(mut self, exclude: bool) -> Self {
167        self.exclude_desktop_windows = exclude;
168        self
169    }
170
171    /// Include only on-screen windows in the shareable content
172    #[must_use]
173    pub fn with_on_screen_windows_only(mut self, on_screen_only: bool) -> Self {
174        self.on_screen_windows_only = on_screen_only;
175        self
176    }
177
178    /// Asynchronously get the shareable content with these options
179    pub fn get(self) -> AsyncShareableContentFuture {
180        let (future, context) = AsyncCompletion::create();
181
182        unsafe {
183            crate::ffi::sc_shareable_content_get_with_options(
184                self.exclude_desktop_windows,
185                self.on_screen_windows_only,
186                shareable_content_callback,
187                context,
188            );
189        }
190
191        AsyncShareableContentFuture { inner: future }
192    }
193
194    /// Asynchronously get shareable content with only windows below a reference window
195    ///
196    /// This returns windows that are stacked below the specified reference window
197    /// in the window layering order.
198    ///
199    /// # Arguments
200    ///
201    /// * `reference_window` - The window to use as the reference point
202    pub fn below_window(
203        self,
204        reference_window: &crate::shareable_content::SCWindow,
205    ) -> AsyncShareableContentFuture {
206        let (future, context) = AsyncCompletion::create();
207
208        unsafe {
209            crate::ffi::sc_shareable_content_get_below_window(
210                self.exclude_desktop_windows,
211                reference_window.as_ptr(),
212                shareable_content_callback,
213                context,
214            );
215        }
216
217        AsyncShareableContentFuture { inner: future }
218    }
219
220    /// Asynchronously get shareable content with only windows above a reference window
221    ///
222    /// This returns windows that are stacked above the specified reference window
223    /// in the window layering order.
224    ///
225    /// # Arguments
226    ///
227    /// * `reference_window` - The window to use as the reference point
228    pub fn above_window(
229        self,
230        reference_window: &crate::shareable_content::SCWindow,
231    ) -> AsyncShareableContentFuture {
232        let (future, context) = AsyncCompletion::create();
233
234        unsafe {
235            crate::ffi::sc_shareable_content_get_above_window(
236                self.exclude_desktop_windows,
237                reference_window.as_ptr(),
238                shareable_content_callback,
239                context,
240            );
241        }
242
243        AsyncShareableContentFuture { inner: future }
244    }
245}
246
247impl AsyncSCShareableContent {
248    /// Asynchronously get shareable content for the current process only (macOS 14.4+)
249    ///
250    /// This retrieves content that the current process can capture without
251    /// requiring user authorization via TCC (Transparency, Consent, and Control).
252    ///
253    /// # Examples
254    ///
255    /// ```rust,no_run
256    /// # #[tokio::main]
257    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
258    /// use screencapturekit::async_api::AsyncSCShareableContent;
259    ///
260    /// // Get content capturable without TCC authorization
261    /// let content = AsyncSCShareableContent::current_process().await?;
262    /// println!("Found {} windows for current process", content.windows().len());
263    /// # Ok(())
264    /// # }
265    /// ```
266    #[cfg(feature = "macos_14_4")]
267    pub fn current_process() -> AsyncShareableContentFuture {
268        let (future, context) = AsyncCompletion::create();
269
270        unsafe {
271            crate::ffi::sc_shareable_content_get_current_process_displays(
272                shareable_content_callback,
273                context,
274            );
275        }
276
277        AsyncShareableContentFuture { inner: future }
278    }
279}
280
281// ============================================================================
282// AsyncSCStream - Async stream with integrated frame iteration
283// ============================================================================
284
285/// Async iterator over sample buffers
286struct AsyncSampleIteratorState {
287    buffer: std::collections::VecDeque<crate::cm::CMSampleBuffer>,
288    waker: Option<Waker>,
289    closed: bool,
290    capacity: usize,
291}
292
293/// Internal sender for async sample iterator
294struct AsyncSampleSender {
295    inner: Arc<Mutex<AsyncSampleIteratorState>>,
296}
297
298impl crate::stream::output_trait::SCStreamOutputTrait for AsyncSampleSender {
299    fn did_output_sample_buffer(
300        &self,
301        sample_buffer: crate::cm::CMSampleBuffer,
302        _of_type: crate::stream::output_type::SCStreamOutputType,
303    ) {
304        let Ok(mut state) = self.inner.lock() else {
305            return;
306        };
307
308        // Drop oldest if at capacity
309        if state.buffer.len() >= state.capacity {
310            state.buffer.pop_front();
311        }
312
313        state.buffer.push_back(sample_buffer);
314
315        if let Some(waker) = state.waker.take() {
316            waker.wake();
317        }
318    }
319}
320
321impl Drop for AsyncSampleSender {
322    fn drop(&mut self) {
323        if let Ok(mut state) = self.inner.lock() {
324            state.closed = true;
325            if let Some(waker) = state.waker.take() {
326                waker.wake();
327            }
328        }
329    }
330}
331
332/// Future for getting the next sample buffer
333pub struct NextSample<'a> {
334    state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
335}
336
337impl std::fmt::Debug for NextSample<'_> {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        f.debug_struct("NextSample").finish_non_exhaustive()
340    }
341}
342
343impl Future for NextSample<'_> {
344    type Output = Option<crate::cm::CMSampleBuffer>;
345
346    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
347        let Ok(mut state) = self.state.lock() else {
348            return Poll::Ready(None);
349        };
350
351        if let Some(sample) = state.buffer.pop_front() {
352            return Poll::Ready(Some(sample));
353        }
354
355        if state.closed {
356            Poll::Ready(None)
357        } else {
358            state.waker = Some(cx.waker().clone());
359            Poll::Pending
360        }
361    }
362}
363
364unsafe impl Send for AsyncSampleSender {}
365unsafe impl Sync for AsyncSampleSender {}
366
367/// Async wrapper for `SCStream` with integrated frame iteration
368///
369/// Provides async methods for stream lifecycle and frame iteration.
370/// **Executor-agnostic** - works with any async runtime.
371///
372/// # Examples
373///
374/// ```rust,no_run
375/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
376/// use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
377/// use screencapturekit::stream::configuration::SCStreamConfiguration;
378/// use screencapturekit::stream::content_filter::SCContentFilter;
379/// use screencapturekit::stream::output_type::SCStreamOutputType;
380///
381/// let content = AsyncSCShareableContent::get().await?;
382/// let display = &content.displays()[0];
383/// let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
384/// let config = SCStreamConfiguration::new()
385///     .with_width(1920)
386///     .with_height(1080);
387///
388/// let stream = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen);
389/// stream.start_capture()?;
390///
391/// // Process frames asynchronously
392/// while let Some(frame) = stream.next().await {
393///     println!("Got frame!");
394/// }
395/// # Ok(())
396/// # }
397/// ```
398pub struct AsyncSCStream {
399    stream: crate::stream::SCStream,
400    iterator_state: Arc<Mutex<AsyncSampleIteratorState>>,
401}
402
403impl AsyncSCStream {
404    /// Create a new async stream
405    ///
406    /// # Arguments
407    ///
408    /// * `filter` - Content filter specifying what to capture
409    /// * `config` - Stream configuration
410    /// * `buffer_capacity` - Max frames to buffer (oldest dropped when full)
411    /// * `output_type` - Type of output (Screen, Audio, Microphone)
412    #[must_use]
413    pub fn new(
414        filter: &SCContentFilter,
415        config: &SCStreamConfiguration,
416        buffer_capacity: usize,
417        output_type: crate::stream::output_type::SCStreamOutputType,
418    ) -> Self {
419        let state = Arc::new(Mutex::new(AsyncSampleIteratorState {
420            buffer: std::collections::VecDeque::with_capacity(buffer_capacity),
421            waker: None,
422            closed: false,
423            capacity: buffer_capacity,
424        }));
425
426        let sender = AsyncSampleSender {
427            inner: Arc::clone(&state),
428        };
429
430        let mut stream = crate::stream::SCStream::new(filter, config);
431        stream.add_output_handler(sender, output_type);
432
433        Self {
434            stream,
435            iterator_state: state,
436        }
437    }
438
439    /// Get the next sample buffer asynchronously
440    ///
441    /// Returns `None` when the stream is closed.
442    pub fn next(&self) -> NextSample<'_> {
443        NextSample {
444            state: &self.iterator_state,
445        }
446    }
447
448    /// Try to get a sample without waiting
449    #[must_use]
450    pub fn try_next(&self) -> Option<crate::cm::CMSampleBuffer> {
451        self.iterator_state.lock().ok()?.buffer.pop_front()
452    }
453
454    /// Check if the stream has been closed
455    #[must_use]
456    pub fn is_closed(&self) -> bool {
457        self.iterator_state.lock().map(|s| s.closed).unwrap_or(true)
458    }
459
460    /// Get the number of buffered samples
461    #[must_use]
462    pub fn buffered_count(&self) -> usize {
463        self.iterator_state
464            .lock()
465            .map(|s| s.buffer.len())
466            .unwrap_or(0)
467    }
468
469    /// Clear all buffered samples
470    pub fn clear_buffer(&self) {
471        if let Ok(mut state) = self.iterator_state.lock() {
472            state.buffer.clear();
473        }
474    }
475
476    /// Start capture (synchronous - returns immediately)
477    ///
478    /// # Errors
479    ///
480    /// Returns an error if capture fails to start.
481    pub fn start_capture(&self) -> Result<(), SCError> {
482        self.stream.start_capture()
483    }
484
485    /// Stop capture (synchronous - returns immediately)
486    ///
487    /// # Errors
488    ///
489    /// Returns an error if capture fails to stop.
490    pub fn stop_capture(&self) -> Result<(), SCError> {
491        self.stream.stop_capture()
492    }
493
494    /// Update stream configuration
495    ///
496    /// # Errors
497    ///
498    /// Returns an error if the update fails.
499    pub fn update_configuration(&self, config: &SCStreamConfiguration) -> Result<(), SCError> {
500        self.stream.update_configuration(config)
501    }
502
503    /// Update content filter
504    ///
505    /// # Errors
506    ///
507    /// Returns an error if the update fails.
508    pub fn update_content_filter(&self, filter: &SCContentFilter) -> Result<(), SCError> {
509        self.stream.update_content_filter(filter)
510    }
511
512    /// Get a reference to the underlying stream
513    #[must_use]
514    pub fn inner(&self) -> &crate::stream::SCStream {
515        &self.stream
516    }
517}
518
519impl std::fmt::Debug for AsyncSCStream {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        f.debug_struct("AsyncSCStream")
522            .field("stream", &self.stream)
523            .field("buffered_count", &self.buffered_count())
524            .field("is_closed", &self.is_closed())
525            .finish_non_exhaustive()
526    }
527}
528
529// ============================================================================
530// AsyncSCScreenshotManager - Async screenshot capture (macOS 14.0+)
531// ============================================================================
532
533/// Async wrapper for `SCScreenshotManager`
534///
535/// Provides async methods for single-frame screenshot capture.
536/// **Executor-agnostic** - works with any async runtime.
537///
538/// Requires the `macos_14_0` feature flag.
539///
540/// # Examples
541///
542/// ```rust,no_run
543/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
544/// use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCScreenshotManager};
545/// use screencapturekit::stream::configuration::SCStreamConfiguration;
546/// use screencapturekit::stream::content_filter::SCContentFilter;
547///
548/// let content = AsyncSCShareableContent::get().await?;
549/// let display = &content.displays()[0];
550/// let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
551/// let config = SCStreamConfiguration::new()
552///     .with_width(1920)
553///     .with_height(1080);
554///
555/// let image = AsyncSCScreenshotManager::capture_image(&filter, &config).await?;
556/// println!("Screenshot: {}x{}", image.width(), image.height());
557/// # Ok(())
558/// # }
559/// ```
560#[cfg(feature = "macos_14_0")]
561#[derive(Debug, Clone, Copy)]
562pub struct AsyncSCScreenshotManager;
563
564/// Callback for async `CGImage` capture
565#[cfg(feature = "macos_14_0")]
566extern "C" fn screenshot_image_callback(
567    image_ptr: *const c_void,
568    error_ptr: *const i8,
569    user_data: *mut c_void,
570) {
571    if !error_ptr.is_null() {
572        let error = unsafe { error_from_cstr(error_ptr) };
573        unsafe {
574            AsyncCompletion::<crate::screenshot_manager::CGImage>::complete_err(user_data, error);
575        }
576    } else if !image_ptr.is_null() {
577        let image = crate::screenshot_manager::CGImage::from_ptr(image_ptr);
578        unsafe { AsyncCompletion::complete_ok(user_data, image) };
579    } else {
580        unsafe {
581            AsyncCompletion::<crate::screenshot_manager::CGImage>::complete_err(
582                user_data,
583                "Unknown error".to_string(),
584            );
585        };
586    }
587}
588
589/// Callback for async `CMSampleBuffer` capture
590#[cfg(feature = "macos_14_0")]
591extern "C" fn screenshot_buffer_callback(
592    buffer_ptr: *const c_void,
593    error_ptr: *const i8,
594    user_data: *mut c_void,
595) {
596    if !error_ptr.is_null() {
597        let error = unsafe { error_from_cstr(error_ptr) };
598        unsafe { AsyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(user_data, error) };
599    } else if !buffer_ptr.is_null() {
600        let buffer = unsafe { crate::cm::CMSampleBuffer::from_ptr(buffer_ptr.cast_mut()) };
601        unsafe { AsyncCompletion::complete_ok(user_data, buffer) };
602    } else {
603        unsafe {
604            AsyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(
605                user_data,
606                "Unknown error".to_string(),
607            );
608        };
609    }
610}
611
612/// Future for async screenshot capture
613#[cfg(feature = "macos_14_0")]
614pub struct AsyncScreenshotFuture<T> {
615    inner: AsyncCompletionFuture<T>,
616}
617
618#[cfg(feature = "macos_14_0")]
619impl<T> std::fmt::Debug for AsyncScreenshotFuture<T> {
620    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
621        f.debug_struct("AsyncScreenshotFuture")
622            .finish_non_exhaustive()
623    }
624}
625
626#[cfg(feature = "macos_14_0")]
627impl<T> Future for AsyncScreenshotFuture<T> {
628    type Output = Result<T, SCError>;
629
630    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
631        Pin::new(&mut self.inner)
632            .poll(cx)
633            .map(|r| r.map_err(SCError::ScreenshotError))
634    }
635}
636
637#[cfg(feature = "macos_14_0")]
638impl AsyncSCScreenshotManager {
639    /// Capture a single screenshot as a `CGImage` asynchronously
640    ///
641    /// # Errors
642    /// Returns an error if:
643    /// - Screen recording permission is not granted
644    /// - The capture fails for any reason
645    pub fn capture_image(
646        content_filter: &crate::stream::content_filter::SCContentFilter,
647        configuration: &SCStreamConfiguration,
648    ) -> AsyncScreenshotFuture<crate::screenshot_manager::CGImage> {
649        let (future, context) = AsyncCompletion::create();
650
651        unsafe {
652            crate::ffi::sc_screenshot_manager_capture_image(
653                content_filter.as_ptr(),
654                configuration.as_ptr(),
655                screenshot_image_callback,
656                context,
657            );
658        }
659
660        AsyncScreenshotFuture { inner: future }
661    }
662
663    /// Capture a single screenshot as a `CMSampleBuffer` asynchronously
664    ///
665    /// # Errors
666    /// Returns an error if:
667    /// - Screen recording permission is not granted
668    /// - The capture fails for any reason
669    pub fn capture_sample_buffer(
670        content_filter: &crate::stream::content_filter::SCContentFilter,
671        configuration: &SCStreamConfiguration,
672    ) -> AsyncScreenshotFuture<crate::cm::CMSampleBuffer> {
673        let (future, context) = AsyncCompletion::create();
674
675        unsafe {
676            crate::ffi::sc_screenshot_manager_capture_sample_buffer(
677                content_filter.as_ptr(),
678                configuration.as_ptr(),
679                screenshot_buffer_callback,
680                context,
681            );
682        }
683
684        AsyncScreenshotFuture { inner: future }
685    }
686
687    /// Capture a screenshot of a specific screen region asynchronously (macOS 15.2+)
688    ///
689    /// This method captures the content within the specified rectangle,
690    /// which can span multiple displays.
691    ///
692    /// # Arguments
693    /// * `rect` - The rectangle to capture, in screen coordinates (points)
694    ///
695    /// # Errors
696    /// Returns an error if:
697    /// - The system is not macOS 15.2+
698    /// - Screen recording permission is not granted
699    /// - The capture fails for any reason
700    #[cfg(feature = "macos_15_2")]
701    pub fn capture_image_in_rect(
702        rect: crate::cg::CGRect,
703    ) -> AsyncScreenshotFuture<crate::screenshot_manager::CGImage> {
704        let (future, context) = AsyncCompletion::create();
705
706        unsafe {
707            crate::ffi::sc_screenshot_manager_capture_image_in_rect(
708                rect.x,
709                rect.y,
710                rect.width,
711                rect.height,
712                screenshot_image_callback,
713                context,
714            );
715        }
716
717        AsyncScreenshotFuture { inner: future }
718    }
719
720    /// Capture a screenshot with advanced configuration asynchronously (macOS 26.0+)
721    ///
722    /// This method uses the new `SCScreenshotConfiguration` for more control
723    /// over the screenshot output, including HDR support and file saving.
724    ///
725    /// # Arguments
726    /// * `content_filter` - The content filter specifying what to capture
727    /// * `configuration` - The screenshot configuration
728    ///
729    /// # Errors
730    /// Returns an error if the capture fails
731    #[cfg(feature = "macos_26_0")]
732    pub fn capture_screenshot(
733        content_filter: &crate::stream::content_filter::SCContentFilter,
734        configuration: &crate::screenshot_manager::SCScreenshotConfiguration,
735    ) -> AsyncScreenshotFuture<crate::screenshot_manager::SCScreenshotOutput> {
736        let (future, context) = AsyncCompletion::create();
737
738        unsafe {
739            crate::ffi::sc_screenshot_manager_capture_screenshot(
740                content_filter.as_ptr(),
741                configuration.as_ptr(),
742                screenshot_output_callback,
743                context,
744            );
745        }
746
747        AsyncScreenshotFuture { inner: future }
748    }
749
750    /// Capture a screenshot of a specific region with advanced configuration asynchronously (macOS 26.0+)
751    ///
752    /// # Arguments
753    /// * `rect` - The rectangle to capture, in screen coordinates (points)
754    /// * `configuration` - The screenshot configuration
755    ///
756    /// # Errors
757    /// Returns an error if the capture fails
758    #[cfg(feature = "macos_26_0")]
759    pub fn capture_screenshot_in_rect(
760        rect: crate::cg::CGRect,
761        configuration: &crate::screenshot_manager::SCScreenshotConfiguration,
762    ) -> AsyncScreenshotFuture<crate::screenshot_manager::SCScreenshotOutput> {
763        let (future, context) = AsyncCompletion::create();
764
765        unsafe {
766            crate::ffi::sc_screenshot_manager_capture_screenshot_in_rect(
767                rect.x,
768                rect.y,
769                rect.width,
770                rect.height,
771                configuration.as_ptr(),
772                screenshot_output_callback,
773                context,
774            );
775        }
776
777        AsyncScreenshotFuture { inner: future }
778    }
779}
780
781/// Callback for async `SCScreenshotOutput` capture (macOS 26.0+)
782#[cfg(feature = "macos_26_0")]
783extern "C" fn screenshot_output_callback(
784    output_ptr: *const c_void,
785    error_ptr: *const i8,
786    user_data: *mut c_void,
787) {
788    if !error_ptr.is_null() {
789        let error = unsafe { error_from_cstr(error_ptr) };
790        unsafe {
791            AsyncCompletion::<crate::screenshot_manager::SCScreenshotOutput>::complete_err(
792                user_data, error,
793            );
794        }
795    } else if !output_ptr.is_null() {
796        let output = crate::screenshot_manager::SCScreenshotOutput::from_ptr(output_ptr);
797        unsafe { AsyncCompletion::complete_ok(user_data, output) };
798    } else {
799        unsafe {
800            AsyncCompletion::<crate::screenshot_manager::SCScreenshotOutput>::complete_err(
801                user_data,
802                "Unknown error".to_string(),
803            );
804        };
805    }
806}
807
808// ============================================================================
809// AsyncSCContentSharingPicker - Async content sharing picker (macOS 14.0+)
810// ============================================================================
811
812/// Result from the async picker callback
813#[cfg(feature = "macos_14_0")]
814struct AsyncPickerCallbackResult {
815    code: i32,
816    ptr: *const c_void,
817}
818
819#[cfg(feature = "macos_14_0")]
820unsafe impl Send for AsyncPickerCallbackResult {}
821
822/// Callback for async picker
823#[cfg(feature = "macos_14_0")]
824extern "C" fn async_picker_callback(result_code: i32, ptr: *const c_void, user_data: *mut c_void) {
825    let result = AsyncPickerCallbackResult {
826        code: result_code,
827        ptr,
828    };
829    unsafe { AsyncCompletion::complete_ok(user_data, result) };
830}
831
832/// Future for async picker with full result
833#[cfg(feature = "macos_14_0")]
834pub struct AsyncPickerFuture {
835    inner: AsyncCompletionFuture<AsyncPickerCallbackResult>,
836}
837
838#[cfg(feature = "macos_14_0")]
839impl std::fmt::Debug for AsyncPickerFuture {
840    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
841        f.debug_struct("AsyncPickerFuture").finish_non_exhaustive()
842    }
843}
844
845#[cfg(feature = "macos_14_0")]
846impl Future for AsyncPickerFuture {
847    type Output = crate::content_sharing_picker::SCPickerOutcome;
848
849    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
850        use crate::content_sharing_picker::{SCPickerOutcome, SCPickerResult};
851
852        match Pin::new(&mut self.inner).poll(cx) {
853            Poll::Pending => Poll::Pending,
854            Poll::Ready(Ok(result)) => {
855                let outcome = match result.code {
856                    1 if !result.ptr.is_null() => {
857                        SCPickerOutcome::Picked(SCPickerResult::from_ptr(result.ptr))
858                    }
859                    0 => SCPickerOutcome::Cancelled,
860                    _ => SCPickerOutcome::Error("Picker failed".to_string()),
861                };
862                Poll::Ready(outcome)
863            }
864            Poll::Ready(Err(e)) => Poll::Ready(SCPickerOutcome::Error(e)),
865        }
866    }
867}
868
869/// Future for async picker returning filter only
870#[cfg(feature = "macos_14_0")]
871pub struct AsyncPickerFilterFuture {
872    inner: AsyncCompletionFuture<AsyncPickerCallbackResult>,
873}
874
875#[cfg(feature = "macos_14_0")]
876impl std::fmt::Debug for AsyncPickerFilterFuture {
877    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
878        f.debug_struct("AsyncPickerFilterFuture")
879            .finish_non_exhaustive()
880    }
881}
882
883#[cfg(feature = "macos_14_0")]
884impl Future for AsyncPickerFilterFuture {
885    type Output = crate::content_sharing_picker::SCPickerFilterOutcome;
886
887    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
888        use crate::content_sharing_picker::SCPickerFilterOutcome;
889
890        match Pin::new(&mut self.inner).poll(cx) {
891            Poll::Pending => Poll::Pending,
892            Poll::Ready(Ok(result)) => {
893                let outcome = match result.code {
894                    1 if !result.ptr.is_null() => {
895                        SCPickerFilterOutcome::Filter(SCContentFilter::from_picker_ptr(result.ptr))
896                    }
897                    0 => SCPickerFilterOutcome::Cancelled,
898                    _ => SCPickerFilterOutcome::Error("Picker failed".to_string()),
899                };
900                Poll::Ready(outcome)
901            }
902            Poll::Ready(Err(e)) => Poll::Ready(SCPickerFilterOutcome::Error(e)),
903        }
904    }
905}
906
907/// Async wrapper for `SCContentSharingPicker` (macOS 14.0+)
908///
909/// Provides async methods to show the system content sharing picker UI.
910/// **Executor-agnostic** - works with any async runtime.
911///
912/// # Examples
913///
914/// ```no_run
915/// use screencapturekit::async_api::AsyncSCContentSharingPicker;
916/// use screencapturekit::content_sharing_picker::*;
917///
918/// async fn pick_content() {
919///     let config = SCContentSharingPickerConfiguration::new();
920///     match AsyncSCContentSharingPicker::show(&config).await {
921///         SCPickerOutcome::Picked(result) => {
922///             let (width, height) = result.pixel_size();
923///             let filter = result.filter();
924///             println!("Selected content: {}x{}", width, height);
925///         }
926///         SCPickerOutcome::Cancelled => println!("User cancelled"),
927///         SCPickerOutcome::Error(e) => eprintln!("Error: {}", e),
928///     }
929/// }
930/// ```
931#[cfg(feature = "macos_14_0")]
932#[derive(Debug, Clone, Copy)]
933pub struct AsyncSCContentSharingPicker;
934
935#[cfg(feature = "macos_14_0")]
936impl AsyncSCContentSharingPicker {
937    /// Show the picker UI asynchronously and return `SCPickerResult` with filter and metadata
938    ///
939    /// This is the main API - use when you need content dimensions or want to build custom filters.
940    /// The picker UI will be shown on the main thread, and the future will resolve when the user
941    /// makes a selection or cancels.
942    ///
943    /// # Example
944    /// ```no_run
945    /// use screencapturekit::async_api::AsyncSCContentSharingPicker;
946    /// use screencapturekit::content_sharing_picker::*;
947    ///
948    /// async fn example() {
949    ///     let config = SCContentSharingPickerConfiguration::new();
950    ///     if let SCPickerOutcome::Picked(result) = AsyncSCContentSharingPicker::show(&config).await {
951    ///         let (width, height) = result.pixel_size();
952    ///         let filter = result.filter();
953    ///     }
954    /// }
955    /// ```
956    pub fn show(
957        config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
958    ) -> AsyncPickerFuture {
959        let (future, context) = AsyncCompletion::create();
960
961        unsafe {
962            crate::ffi::sc_content_sharing_picker_show_with_result(
963                config.as_ptr(),
964                async_picker_callback,
965                context,
966            );
967        }
968
969        AsyncPickerFuture { inner: future }
970    }
971
972    /// Show the picker UI asynchronously and return an `SCContentFilter` directly
973    ///
974    /// This is the simple API - use when you just need the filter without metadata.
975    ///
976    /// # Example
977    /// ```no_run
978    /// use screencapturekit::async_api::AsyncSCContentSharingPicker;
979    /// use screencapturekit::content_sharing_picker::*;
980    ///
981    /// async fn example() {
982    ///     let config = SCContentSharingPickerConfiguration::new();
983    ///     if let SCPickerFilterOutcome::Filter(filter) = AsyncSCContentSharingPicker::show_filter(&config).await {
984    ///         // Use filter with SCStream
985    ///     }
986    /// }
987    /// ```
988    pub fn show_filter(
989        config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
990    ) -> AsyncPickerFilterFuture {
991        let (future, context) = AsyncCompletion::create();
992
993        unsafe {
994            crate::ffi::sc_content_sharing_picker_show(
995                config.as_ptr(),
996                async_picker_callback,
997                context,
998            );
999        }
1000
1001        AsyncPickerFilterFuture { inner: future }
1002    }
1003
1004    /// Show the picker UI for an existing stream to change source while capturing
1005    ///
1006    /// Use this when you have an active `SCStream` and want to let the user
1007    /// select a new content source. The result can be used with `stream.update_content_filter()`.
1008    ///
1009    /// # Example
1010    /// ```no_run
1011    /// use screencapturekit::async_api::AsyncSCContentSharingPicker;
1012    /// use screencapturekit::content_sharing_picker::*;
1013    /// use screencapturekit::stream::SCStream;
1014    /// use screencapturekit::stream::configuration::SCStreamConfiguration;
1015    /// use screencapturekit::stream::content_filter::SCContentFilter;
1016    /// use screencapturekit::shareable_content::SCShareableContent;
1017    ///
1018    /// async fn example() -> Option<()> {
1019    ///     let content = SCShareableContent::get().ok()?;
1020    ///     let displays = content.displays();
1021    ///     let display = displays.first()?;
1022    ///     let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
1023    ///     let stream_config = SCStreamConfiguration::new();
1024    ///     let stream = SCStream::new(&filter, &stream_config);
1025    ///
1026    ///     // When stream is active and user wants to change source
1027    ///     let config = SCContentSharingPickerConfiguration::new();
1028    ///     if let SCPickerOutcome::Picked(result) = AsyncSCContentSharingPicker::show_for_stream(&config, &stream).await {
1029    ///         // Use result.filter() with stream.update_content_filter()
1030    ///         let _ = result.filter();
1031    ///     }
1032    ///     Some(())
1033    /// }
1034    /// ```
1035    pub fn show_for_stream(
1036        config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
1037        stream: &crate::stream::SCStream,
1038    ) -> AsyncPickerFuture {
1039        let (future, context) = AsyncCompletion::create();
1040
1041        unsafe {
1042            crate::ffi::sc_content_sharing_picker_show_for_stream(
1043                config.as_ptr(),
1044                stream.as_ptr(),
1045                async_picker_callback,
1046                context,
1047            );
1048        }
1049
1050        AsyncPickerFuture { inner: future }
1051    }
1052}
1053
1054// ============================================================================
1055// AsyncSCRecordingOutput - Async recording with event stream (macOS 15.0+)
1056// ============================================================================
1057
1058/// Recording lifecycle event
1059#[cfg(feature = "macos_15_0")]
1060#[derive(Debug, Clone, PartialEq, Eq)]
1061pub enum RecordingEvent {
1062    /// Recording started successfully
1063    Started,
1064    /// Recording finished successfully
1065    Finished,
1066    /// Recording failed with an error
1067    Failed(String),
1068}
1069
1070#[cfg(feature = "macos_15_0")]
1071struct AsyncRecordingState {
1072    events: std::collections::VecDeque<RecordingEvent>,
1073    waker: Option<Waker>,
1074    finished: bool,
1075}
1076
1077#[cfg(feature = "macos_15_0")]
1078struct AsyncRecordingDelegate {
1079    state: Arc<Mutex<AsyncRecordingState>>,
1080}
1081
1082#[cfg(feature = "macos_15_0")]
1083impl crate::recording_output::SCRecordingOutputDelegate for AsyncRecordingDelegate {
1084    fn recording_did_start(&self) {
1085        if let Ok(mut state) = self.state.lock() {
1086            state.events.push_back(RecordingEvent::Started);
1087            if let Some(waker) = state.waker.take() {
1088                waker.wake();
1089            }
1090        }
1091    }
1092
1093    fn recording_did_fail(&self, error: String) {
1094        if let Ok(mut state) = self.state.lock() {
1095            state.events.push_back(RecordingEvent::Failed(error));
1096            state.finished = true;
1097            if let Some(waker) = state.waker.take() {
1098                waker.wake();
1099            }
1100        }
1101    }
1102
1103    fn recording_did_finish(&self) {
1104        if let Ok(mut state) = self.state.lock() {
1105            state.events.push_back(RecordingEvent::Finished);
1106            state.finished = true;
1107            if let Some(waker) = state.waker.take() {
1108                waker.wake();
1109            }
1110        }
1111    }
1112}
1113
1114/// Future for getting the next recording event
1115#[cfg(feature = "macos_15_0")]
1116pub struct NextRecordingEvent<'a> {
1117    state: &'a Arc<Mutex<AsyncRecordingState>>,
1118}
1119
1120#[cfg(feature = "macos_15_0")]
1121impl std::fmt::Debug for NextRecordingEvent<'_> {
1122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1123        f.debug_struct("NextRecordingEvent").finish_non_exhaustive()
1124    }
1125}
1126
1127#[cfg(feature = "macos_15_0")]
1128impl Future for NextRecordingEvent<'_> {
1129    type Output = Option<RecordingEvent>;
1130
1131    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
1132        let Ok(mut state) = self.state.lock() else {
1133            return Poll::Ready(None);
1134        };
1135
1136        if let Some(event) = state.events.pop_front() {
1137            return Poll::Ready(Some(event));
1138        }
1139
1140        if state.finished {
1141            Poll::Ready(None)
1142        } else {
1143            state.waker = Some(cx.waker().clone());
1144            Poll::Pending
1145        }
1146    }
1147}
1148
1149/// Async wrapper for `SCRecordingOutput` with event stream (macOS 15.0+)
1150///
1151/// Provides async iteration over recording lifecycle events.
1152/// **Executor-agnostic** - works with any async runtime.
1153///
1154/// # Examples
1155///
1156/// ```no_run
1157/// use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCRecordingOutput, RecordingEvent};
1158/// use screencapturekit::recording_output::SCRecordingOutputConfiguration;
1159/// use screencapturekit::stream::{SCStream, configuration::SCStreamConfiguration, content_filter::SCContentFilter};
1160/// use std::path::Path;
1161///
1162/// async fn record_screen() -> Option<()> {
1163///     let content = AsyncSCShareableContent::get().await.ok()?;
1164///     let displays = content.displays();
1165///     let display = displays.first()?;
1166///     let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
1167///     let config = SCStreamConfiguration::new().with_width(1920).with_height(1080);
1168///
1169///     let rec_config = SCRecordingOutputConfiguration::new()
1170///         .with_output_url(Path::new("/tmp/recording.mp4"));
1171///
1172///     let (recording, events) = AsyncSCRecordingOutput::new(&rec_config)?;
1173///
1174///     let mut stream = SCStream::new(&filter, &config);
1175///     stream.add_recording_output(&recording).ok()?;
1176///     stream.start_capture().ok()?;
1177///
1178///     // Wait for recording events
1179///     while let Some(event) = events.next().await {
1180///         match event {
1181///             RecordingEvent::Started => println!("Recording started!"),
1182///             RecordingEvent::Finished => {
1183///                 println!("Recording finished!");
1184///                 break;
1185///             }
1186///             RecordingEvent::Failed(e) => {
1187///                 eprintln!("Recording failed: {}", e);
1188///                 break;
1189///             }
1190///         }
1191///     }
1192///
1193///     Some(())
1194/// }
1195/// ```
1196#[cfg(feature = "macos_15_0")]
1197pub struct AsyncSCRecordingOutput {
1198    state: Arc<Mutex<AsyncRecordingState>>,
1199}
1200
1201#[cfg(feature = "macos_15_0")]
1202impl std::fmt::Debug for AsyncSCRecordingOutput {
1203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1204        f.debug_struct("AsyncSCRecordingOutput")
1205            .finish_non_exhaustive()
1206    }
1207}
1208
1209#[cfg(feature = "macos_15_0")]
1210impl AsyncSCRecordingOutput {
1211    /// Create a new async recording output
1212    ///
1213    /// Returns a tuple of (`SCRecordingOutput`, `AsyncSCRecordingOutput`).
1214    /// The `SCRecordingOutput` should be added to an `SCStream`, while
1215    /// the `AsyncSCRecordingOutput` provides async event iteration.
1216    ///
1217    /// # Errors
1218    ///
1219    /// Returns `None` if the recording output cannot be created (requires macOS 15.0+).
1220    #[must_use]
1221    pub fn new(
1222        config: &crate::recording_output::SCRecordingOutputConfiguration,
1223    ) -> Option<(crate::recording_output::SCRecordingOutput, Self)> {
1224        let state = Arc::new(Mutex::new(AsyncRecordingState {
1225            events: std::collections::VecDeque::new(),
1226            waker: None,
1227            finished: false,
1228        }));
1229
1230        let delegate = AsyncRecordingDelegate {
1231            state: Arc::clone(&state),
1232        };
1233
1234        let recording =
1235            crate::recording_output::SCRecordingOutput::new_with_delegate(config, delegate)?;
1236
1237        Some((recording, Self { state }))
1238    }
1239
1240    /// Get the next recording event asynchronously
1241    ///
1242    /// Returns `None` when the recording has finished or failed.
1243    pub fn next(&self) -> NextRecordingEvent<'_> {
1244        NextRecordingEvent { state: &self.state }
1245    }
1246
1247    /// Check if the recording has finished
1248    #[must_use]
1249    pub fn is_finished(&self) -> bool {
1250        self.state.lock().map(|s| s.finished).unwrap_or(true)
1251    }
1252
1253    /// Get any pending events without waiting
1254    #[must_use]
1255    pub fn try_next(&self) -> Option<RecordingEvent> {
1256        self.state.lock().ok()?.events.pop_front()
1257    }
1258}