use crate::error::SCError;
use crate::shareable_content::SCShareableContent;
use crate::stream::configuration::SCStreamConfiguration;
use crate::stream::content_filter::SCContentFilter;
use crate::stream::output_type::SCStreamOutputType;
use crate::utils::completion::{
error_from_cstr, is_timeout_error, AsyncCompletion, AsyncCompletionFuture,
};
use std::ffi::c_void;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
struct RegisteredWaker {
token: Arc<()>,
waker: Waker,
}
fn register_waker(
waiters: &mut Vec<RegisteredWaker>,
token: &Arc<()>,
waker: Waker,
) -> Option<Waker> {
if let Some(waiter) = waiters
.iter_mut()
.find(|waiter| Arc::ptr_eq(&waiter.token, token))
{
return Some(if waiter.waker.will_wake(&waker) {
waker
} else {
std::mem::replace(&mut waiter.waker, waker)
});
}
waiters.push(RegisteredWaker {
token: Arc::clone(token),
waker,
});
None
}
fn unregister_waker(
waiters: &mut Vec<RegisteredWaker>,
token: &Arc<()>,
) -> Option<RegisteredWaker> {
waiters
.iter()
.position(|waiter| Arc::ptr_eq(&waiter.token, token))
.map(|index| waiters.swap_remove(index))
}
fn wake_all(waiters: Vec<RegisteredWaker>) {
for waiter in waiters {
waiter.waker.wake();
}
}
extern "C" fn shareable_content_callback(
content: *const c_void,
error: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("shareable_content_callback", move || {
if !error.is_null() {
let error_msg = unsafe { error_from_cstr(error) };
unsafe { AsyncCompletion::<SCShareableContent>::complete_err(user_data, error_msg) };
} else if !content.is_null() {
let sc = unsafe { SCShareableContent::from_ptr(content) };
unsafe { AsyncCompletion::complete_ok(user_data, sc) };
} else {
unsafe {
AsyncCompletion::<SCShareableContent>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
pub struct AsyncShareableContentFuture {
inner: AsyncCompletionFuture<SCShareableContent>,
}
impl std::fmt::Debug for AsyncShareableContentFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncShareableContentFuture")
.finish_non_exhaustive()
}
}
impl Future for AsyncShareableContentFuture {
type Output = Result<SCShareableContent, SCError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(SCError::NoShareableContent))
}
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncSCShareableContent;
impl AsyncSCShareableContent {
pub fn get() -> AsyncShareableContentFuture {
Self::create().get()
}
#[must_use]
pub fn create() -> AsyncSCShareableContentOptions {
AsyncSCShareableContentOptions::default()
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AsyncSCShareableContentOptions {
exclude_desktop_windows: bool,
on_screen_windows_only: bool,
}
impl AsyncSCShareableContentOptions {
#[must_use]
pub fn with_exclude_desktop_windows(mut self, exclude: bool) -> Self {
self.exclude_desktop_windows = exclude;
self
}
#[must_use]
pub fn with_on_screen_windows_only(mut self, on_screen_only: bool) -> Self {
self.on_screen_windows_only = on_screen_only;
self
}
pub fn get(self) -> AsyncShareableContentFuture {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_shareable_content_get_with_options(
self.exclude_desktop_windows,
self.on_screen_windows_only,
shareable_content_callback,
context,
);
}
AsyncShareableContentFuture { inner: future }
}
pub fn below_window(
self,
reference_window: &crate::shareable_content::SCWindow,
) -> AsyncShareableContentFuture {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_shareable_content_get_below_window(
self.exclude_desktop_windows,
reference_window.as_ptr(),
shareable_content_callback,
context,
);
}
AsyncShareableContentFuture { inner: future }
}
pub fn above_window(
self,
reference_window: &crate::shareable_content::SCWindow,
) -> AsyncShareableContentFuture {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_shareable_content_get_above_window(
self.exclude_desktop_windows,
reference_window.as_ptr(),
shareable_content_callback,
context,
);
}
AsyncShareableContentFuture { inner: future }
}
}
impl AsyncSCShareableContent {
#[cfg(feature = "macos_14_4")]
pub fn current_process() -> AsyncShareableContentFuture {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_shareable_content_get_current_process_displays(
shareable_content_callback,
context,
);
}
AsyncShareableContentFuture { inner: future }
}
}
struct AsyncSampleIteratorState {
buffer: std::collections::VecDeque<(crate::cm::CMSampleBuffer, SCStreamOutputType)>,
waiters: Vec<RegisteredWaker>,
closed: bool,
stopped: bool,
capacity: usize,
senders: usize,
stop_error: Option<SCError>,
}
struct AsyncSampleSender {
inner: Arc<Mutex<AsyncSampleIteratorState>>,
}
impl AsyncSampleSender {
fn new(state: &Arc<Mutex<AsyncSampleIteratorState>>) -> Self {
if let Ok(mut s) = state.lock() {
s.senders += 1;
}
Self {
inner: Arc::clone(state),
}
}
}
impl crate::stream::output_trait::SCStreamOutputTrait for AsyncSampleSender {
fn did_output_sample_buffer(
&self,
sample_buffer: crate::cm::CMSampleBuffer,
of_type: SCStreamOutputType,
) {
let Ok(mut state) = self.inner.lock() else {
return;
};
let evicted = if state.buffer.len() >= state.capacity {
state.buffer.pop_front()
} else {
None
};
state.buffer.push_back((sample_buffer, of_type));
let waiters = std::mem::take(&mut state.waiters);
drop(state);
drop(evicted);
wake_all(waiters);
}
}
impl Drop for AsyncSampleSender {
fn drop(&mut self) {
let Ok(mut state) = self.inner.lock() else {
return;
};
state.senders = state.senders.saturating_sub(1);
if state.senders > 0 {
return;
}
state.closed = true;
let waiters = std::mem::take(&mut state.waiters);
drop(state);
wake_all(waiters);
}
}
fn poll_next_sample(
state: &Arc<Mutex<AsyncSampleIteratorState>>,
waiter: &Arc<()>,
cx: &Context<'_>,
) -> Poll<Option<(crate::cm::CMSampleBuffer, SCStreamOutputType)>> {
let waker = cx.waker().clone();
let Ok(mut state) = state.lock() else {
return Poll::Ready(None);
};
if let Some(sample) = state.buffer.pop_front() {
let removed = unregister_waker(&mut state.waiters, waiter);
drop(state);
drop(removed);
drop(waker);
return Poll::Ready(Some(sample));
}
if state.closed {
let removed = unregister_waker(&mut state.waiters, waiter);
drop(state);
drop(removed);
drop(waker);
Poll::Ready(None)
} else {
let replaced = register_waker(&mut state.waiters, waiter, waker);
drop(state);
drop(replaced);
Poll::Pending
}
}
pub struct NextSample<'a> {
state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
waiter: Arc<()>,
}
impl std::fmt::Debug for NextSample<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NextSample").finish_non_exhaustive()
}
}
impl Future for NextSample<'_> {
type Output = Option<crate::cm::CMSampleBuffer>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
poll_next_sample(self.state, &self.waiter, cx)
.map(|opt| opt.map(|(buffer, _of_type)| buffer))
}
}
impl Drop for NextSample<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
pub struct NextSampleTyped<'a> {
state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
waiter: Arc<()>,
}
impl std::fmt::Debug for NextSampleTyped<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NextSampleTyped").finish_non_exhaustive()
}
}
impl Future for NextSampleTyped<'_> {
type Output = Option<(crate::cm::CMSampleBuffer, SCStreamOutputType)>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
poll_next_sample(self.state, &self.waiter, cx)
}
}
impl Drop for NextSampleTyped<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
pub struct SampleStream<'a> {
state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
waiter: Arc<()>,
}
impl std::fmt::Debug for SampleStream<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SampleStream").finish_non_exhaustive()
}
}
impl futures_core::Stream for SampleStream<'_> {
type Item = crate::cm::CMSampleBuffer;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
poll_next_sample(self.state, &self.waiter, cx)
.map(|opt| opt.map(|(buffer, _of_type)| buffer))
}
}
impl Drop for SampleStream<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
pub struct TypedSampleStream<'a> {
state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
waiter: Arc<()>,
}
impl std::fmt::Debug for TypedSampleStream<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TypedSampleStream").finish_non_exhaustive()
}
}
impl futures_core::Stream for TypedSampleStream<'_> {
type Item = (crate::cm::CMSampleBuffer, SCStreamOutputType);
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
poll_next_sample(self.state, &self.waiter, cx)
}
}
impl Drop for TypedSampleStream<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
unsafe impl Send for AsyncSampleSender {}
unsafe impl Sync for AsyncSampleSender {}
struct AsyncStreamDelegate {
state: Arc<Mutex<AsyncSampleIteratorState>>,
}
impl crate::stream::delegate_trait::SCStreamDelegateTrait for AsyncStreamDelegate {
fn did_stop_with_error(&self, error: SCError) {
close_sample_state(&self.state, Some(error), false);
}
}
fn close_sample_state(
state: &Arc<Mutex<AsyncSampleIteratorState>>,
error: Option<SCError>,
stopped: bool,
) {
let Ok(mut state) = state.lock() else {
return;
};
if let Some(error) = error {
state.stop_error = Some(error);
}
state.stopped |= stopped;
state.closed = true;
let waiters = std::mem::take(&mut state.waiters);
drop(state);
wake_all(waiters);
}
fn reopen_sample_state(state: &Arc<Mutex<AsyncSampleIteratorState>>) {
let Ok(mut state) = state.lock() else {
return;
};
if state.stopped {
return;
}
state.closed = false;
state.stop_error = None;
}
unsafe impl Send for AsyncStreamDelegate {}
unsafe impl Sync for AsyncStreamDelegate {}
extern "C" fn stream_control_callback(context: *mut c_void, success: bool, msg: *const i8) {
crate::utils::panic_safe::catch_user_panic("stream_control_callback", move || {
if success {
unsafe { AsyncCompletion::<()>::complete_ok(context, ()) };
} else {
let error = unsafe { error_from_cstr(msg) };
unsafe { AsyncCompletion::<()>::complete_err(context, error) };
}
});
}
#[must_use = "the operation starts eagerly, but you must .await the future to observe success or failure"]
pub struct StreamControlFuture {
inner: AsyncCompletionFuture<()>,
map_err: fn(String) -> SCError,
}
impl StreamControlFuture {
fn succeeded(map_err: fn(String) -> SCError) -> Self {
let (inner, context) = AsyncCompletion::<()>::create();
unsafe { AsyncCompletion::<()>::complete_ok(context, ()) };
Self { inner, map_err }
}
fn failed(map_err: fn(String) -> SCError, error: String) -> Self {
let (inner, context) = AsyncCompletion::<()>::create();
unsafe { AsyncCompletion::<()>::complete_err(context, error) };
Self { inner, map_err }
}
}
impl std::fmt::Debug for StreamControlFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StreamControlFuture")
.finish_non_exhaustive()
}
}
impl Future for StreamControlFuture {
type Output = Result<(), SCError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let map_err = self.map_err;
let Poll::Ready(result) = Pin::new(&mut self.inner).poll(cx) else {
return Poll::Pending;
};
Poll::Ready(result.map_err(map_err))
}
}
pub struct AsyncSCStream {
stream: crate::stream::SCStream,
iterator_state: Arc<Mutex<AsyncSampleIteratorState>>,
}
impl AsyncSCStream {
#[must_use]
pub fn new(
filter: &SCContentFilter,
config: &SCStreamConfiguration,
buffer_capacity: usize,
output_type: crate::stream::output_type::SCStreamOutputType,
) -> Self {
let state = Arc::new(Mutex::new(AsyncSampleIteratorState {
buffer: std::collections::VecDeque::with_capacity(buffer_capacity),
waiters: Vec::new(),
closed: false,
stopped: false,
capacity: buffer_capacity.max(1),
senders: 0,
stop_error: None,
}));
let sender = AsyncSampleSender::new(&state);
let delegate = AsyncStreamDelegate {
state: Arc::clone(&state),
};
let mut stream = crate::stream::SCStream::new_with_delegate(filter, config, delegate);
if stream.add_output_handler(sender, output_type).is_none() {
if let Ok(mut s) = state.lock() {
s.stop_error = Some(SCError::StreamError(
"failed to register stream output handler".to_string(),
));
}
}
Self {
stream,
iterator_state: state,
}
}
pub fn next(&self) -> NextSample<'_> {
NextSample {
state: &self.iterator_state,
waiter: Arc::new(()),
}
}
pub fn next_typed(&self) -> NextSampleTyped<'_> {
NextSampleTyped {
state: &self.iterator_state,
waiter: Arc::new(()),
}
}
#[must_use]
pub fn frames(&self) -> SampleStream<'_> {
SampleStream {
state: &self.iterator_state,
waiter: Arc::new(()),
}
}
#[must_use]
pub fn frames_typed(&self) -> TypedSampleStream<'_> {
TypedSampleStream {
state: &self.iterator_state,
waiter: Arc::new(()),
}
}
pub fn add_output_type(&mut self, output_type: SCStreamOutputType) -> bool {
let sender = AsyncSampleSender::new(&self.iterator_state);
self.stream
.add_output_handler(sender, output_type)
.is_some()
}
#[must_use]
pub fn try_next(&self) -> Option<crate::cm::CMSampleBuffer> {
self.iterator_state
.lock()
.ok()?
.buffer
.pop_front()
.map(|(buffer, _of_type)| buffer)
}
#[must_use]
pub fn try_next_typed(&self) -> Option<(crate::cm::CMSampleBuffer, SCStreamOutputType)> {
self.iterator_state.lock().ok()?.buffer.pop_front()
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.iterator_state.lock().map_or(true, |s| s.closed)
}
#[must_use]
pub fn take_error(&self) -> Option<SCError> {
self.iterator_state.lock().ok()?.stop_error.take()
}
#[must_use]
pub fn buffered_count(&self) -> usize {
self.iterator_state.lock().map_or(0, |s| s.buffer.len())
}
pub fn clear_buffer(&self) {
let Ok(mut state) = self.iterator_state.lock() else {
return;
};
let discarded = std::mem::take(&mut state.buffer);
drop(state);
drop(discarded);
}
pub fn start_capture(&self) -> StreamControlFuture {
if self.iterator_state.lock().is_ok_and(|s| s.stopped) {
return StreamControlFuture::failed(
SCError::CaptureStartFailed,
"an SCStream cannot be restarted after stop_capture(); create a new AsyncSCStream"
.to_string(),
);
}
let capture_state = self.stream.capture_state();
let start_unconfirmed = self.stream.start_unconfirmed_state();
if !crate::stream::sc_stream::claim_start(&capture_state, &start_unconfirmed) {
return StreamControlFuture::succeeded(SCError::CaptureStartFailed);
}
reopen_sample_state(&self.iterator_state);
let iterator_state = Arc::clone(&self.iterator_state);
let (future, context) =
AsyncCompletion::<()>::create_with_hook(move |result| match result {
Ok(()) => capture_state.store(true, std::sync::atomic::Ordering::Release),
Err(message) if is_timeout_error(message) => {
start_unconfirmed.store(true, std::sync::atomic::Ordering::Release);
}
Err(message) => {
capture_state.store(false, std::sync::atomic::Ordering::Release);
close_sample_state(
&iterator_state,
Some(SCError::CaptureStartFailed(message.clone())),
false,
);
}
});
unsafe {
crate::ffi::sc_stream_start_capture(
self.stream.as_ptr(),
context,
stream_control_callback,
);
}
StreamControlFuture {
inner: future,
map_err: SCError::CaptureStartFailed,
}
}
pub fn stop_capture(&self) -> StreamControlFuture {
let capture_state = self.stream.capture_state();
let iterator_state = Arc::clone(&self.iterator_state);
let (future, context) = AsyncCompletion::<()>::create_with_hook(move |result| {
if result.is_ok() {
capture_state.store(false, std::sync::atomic::Ordering::Release);
close_sample_state(&iterator_state, None, true);
}
});
unsafe {
crate::ffi::sc_stream_stop_capture(
self.stream.as_ptr(),
context,
stream_control_callback,
);
}
StreamControlFuture {
inner: future,
map_err: SCError::CaptureStopFailed,
}
}
#[cfg(feature = "macos_14_0")]
pub fn update_configuration(&self, config: &SCStreamConfiguration) -> StreamControlFuture {
let (future, context) = AsyncCompletion::<()>::create();
unsafe {
crate::ffi::sc_stream_update_configuration(
self.stream.as_ptr(),
config.as_ptr(),
context,
stream_control_callback,
);
}
StreamControlFuture {
inner: future,
map_err: SCError::StreamError,
}
}
pub fn update_content_filter(&self, filter: &SCContentFilter) -> StreamControlFuture {
let (future, context) = AsyncCompletion::<()>::create();
unsafe {
crate::ffi::sc_stream_update_content_filter(
self.stream.as_ptr(),
filter.as_ptr(),
context,
stream_control_callback,
);
}
StreamControlFuture {
inner: future,
map_err: SCError::StreamError,
}
}
#[must_use]
pub fn inner(&self) -> &crate::stream::SCStream {
&self.stream
}
}
impl std::fmt::Debug for AsyncSCStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncSCStream")
.field("stream", &self.stream)
.field("buffered_count", &self.buffered_count())
.field("is_closed", &self.is_closed())
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_14_0")]
#[derive(Debug, Clone, Copy)]
pub struct AsyncSCScreenshotManager;
#[cfg(feature = "macos_14_0")]
extern "C" fn screenshot_image_callback(
image_ptr: *const c_void,
error_ptr: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("screenshot_image_callback", move || {
if !error_ptr.is_null() {
let error = unsafe { error_from_cstr(error_ptr) };
unsafe {
AsyncCompletion::<crate::screenshot_manager::CGImage>::complete_err(
user_data, error,
);
}
} else if !image_ptr.is_null() {
let image = unsafe { crate::screenshot_manager::cgimage_from_retained_ptr(image_ptr) };
unsafe { AsyncCompletion::complete_ok(user_data, image) };
} else {
unsafe {
AsyncCompletion::<crate::screenshot_manager::CGImage>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
#[cfg(feature = "macos_14_0")]
extern "C" fn screenshot_buffer_callback(
buffer_ptr: *const c_void,
error_ptr: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("screenshot_buffer_callback", move || {
if !error_ptr.is_null() {
let error = unsafe { error_from_cstr(error_ptr) };
unsafe { AsyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(user_data, error) };
} else if !buffer_ptr.is_null() {
let buffer = unsafe { crate::cm::CMSampleBuffer::from_ptr(buffer_ptr.cast_mut()) };
unsafe { AsyncCompletion::complete_ok(user_data, buffer) };
} else {
unsafe {
AsyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
#[cfg(feature = "macos_14_0")]
pub struct AsyncScreenshotFuture<T: Send + 'static> {
inner: AsyncCompletionFuture<T>,
}
#[cfg(feature = "macos_14_0")]
impl<T: Send + 'static> std::fmt::Debug for AsyncScreenshotFuture<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncScreenshotFuture")
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_14_0")]
impl<T: Send + 'static> Future for AsyncScreenshotFuture<T> {
type Output = Result<T, SCError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.inner)
.poll(cx)
.map(|r| r.map_err(SCError::ScreenshotError))
}
}
#[cfg(feature = "macos_14_0")]
impl AsyncSCScreenshotManager {
pub fn capture_image(
content_filter: &crate::stream::content_filter::SCContentFilter,
configuration: &SCStreamConfiguration,
) -> AsyncScreenshotFuture<crate::screenshot_manager::CGImage> {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_screenshot_manager_capture_image(
content_filter.as_ptr(),
configuration.as_ptr(),
screenshot_image_callback,
context,
);
}
AsyncScreenshotFuture { inner: future }
}
pub fn capture_sample_buffer(
content_filter: &crate::stream::content_filter::SCContentFilter,
configuration: &SCStreamConfiguration,
) -> AsyncScreenshotFuture<crate::cm::CMSampleBuffer> {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_screenshot_manager_capture_sample_buffer(
content_filter.as_ptr(),
configuration.as_ptr(),
screenshot_buffer_callback,
context,
);
}
AsyncScreenshotFuture { inner: future }
}
#[cfg(feature = "macos_15_2")]
pub fn capture_image_in_rect(
rect: crate::cg::CGRect,
) -> AsyncScreenshotFuture<crate::screenshot_manager::CGImage> {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_screenshot_manager_capture_image_in_rect(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
screenshot_image_callback,
context,
);
}
AsyncScreenshotFuture { inner: future }
}
#[cfg(feature = "macos_26_0")]
pub fn capture_screenshot(
content_filter: &crate::stream::content_filter::SCContentFilter,
configuration: &crate::screenshot_manager::SCScreenshotConfiguration,
) -> AsyncScreenshotFuture<crate::screenshot_manager::SCScreenshotOutput> {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_screenshot_manager_capture_screenshot(
content_filter.as_ptr(),
configuration.as_ptr(),
screenshot_output_callback,
context,
);
}
AsyncScreenshotFuture { inner: future }
}
#[cfg(feature = "macos_26_0")]
pub fn capture_screenshot_in_rect(
rect: crate::cg::CGRect,
configuration: &crate::screenshot_manager::SCScreenshotConfiguration,
) -> AsyncScreenshotFuture<crate::screenshot_manager::SCScreenshotOutput> {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_screenshot_manager_capture_screenshot_in_rect(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
configuration.as_ptr(),
screenshot_output_callback,
context,
);
}
AsyncScreenshotFuture { inner: future }
}
}
#[cfg(feature = "macos_26_0")]
extern "C" fn screenshot_output_callback(
output_ptr: *const c_void,
error_ptr: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("screenshot_output_callback", move || {
if !error_ptr.is_null() {
let error = unsafe { error_from_cstr(error_ptr) };
unsafe {
AsyncCompletion::<crate::screenshot_manager::SCScreenshotOutput>::complete_err(
user_data, error,
);
}
} else if !output_ptr.is_null() {
let output = crate::screenshot_manager::SCScreenshotOutput::from_ptr(output_ptr);
unsafe { AsyncCompletion::complete_ok(user_data, output) };
} else {
unsafe {
AsyncCompletion::<crate::screenshot_manager::SCScreenshotOutput>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
#[cfg(feature = "macos_14_0")]
struct AsyncPickerCallbackResult {
code: i32,
ptr: *const c_void,
ownership: AsyncPickerOwnership,
}
#[cfg(feature = "macos_14_0")]
#[derive(Clone, Copy)]
enum AsyncPickerOwnership {
Result,
Filter,
}
#[cfg(feature = "macos_14_0")]
impl AsyncPickerCallbackResult {
fn take_ptr(&mut self) -> *const c_void {
std::mem::replace(&mut self.ptr, std::ptr::null())
}
}
#[cfg(feature = "macos_14_0")]
impl Drop for AsyncPickerCallbackResult {
fn drop(&mut self) {
if self.ptr.is_null() {
return;
}
unsafe {
match self.ownership {
AsyncPickerOwnership::Result => crate::ffi::sc_picker_result_release(self.ptr),
AsyncPickerOwnership::Filter => crate::ffi::sc_content_filter_release(self.ptr),
}
}
}
}
#[cfg(feature = "macos_14_0")]
unsafe impl Send for AsyncPickerCallbackResult {}
#[cfg(feature = "macos_14_0")]
fn complete_async_picker(
result_code: i32,
ptr: *const c_void,
ownership: AsyncPickerOwnership,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("async_picker_callback", move || {
let result = AsyncPickerCallbackResult {
code: result_code,
ptr,
ownership,
};
unsafe { AsyncCompletion::complete_ok(user_data, result) };
});
}
#[cfg(feature = "macos_14_0")]
extern "C" fn async_picker_result_callback(
result_code: i32,
ptr: *const c_void,
user_data: *mut c_void,
) {
complete_async_picker(result_code, ptr, AsyncPickerOwnership::Result, user_data);
}
#[cfg(feature = "macos_14_0")]
extern "C" fn async_picker_filter_callback(
result_code: i32,
ptr: *const c_void,
user_data: *mut c_void,
) {
complete_async_picker(result_code, ptr, AsyncPickerOwnership::Filter, user_data);
}
#[cfg(feature = "macos_14_0")]
pub struct AsyncPickerFuture {
inner: AsyncCompletionFuture<AsyncPickerCallbackResult>,
}
#[cfg(feature = "macos_14_0")]
impl std::fmt::Debug for AsyncPickerFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncPickerFuture").finish_non_exhaustive()
}
}
#[cfg(feature = "macos_14_0")]
impl Future for AsyncPickerFuture {
type Output = crate::content_sharing_picker::SCPickerOutcome;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
use crate::content_sharing_picker::{SCPickerOutcome, SCPickerResult};
match Pin::new(&mut self.inner).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(mut result)) => {
let outcome = match result.code {
1 if !result.ptr.is_null() => {
SCPickerOutcome::Picked(SCPickerResult::from_ptr(result.take_ptr()))
}
0 => SCPickerOutcome::Cancelled,
_ => SCPickerOutcome::Error("Picker failed".to_string()),
};
Poll::Ready(outcome)
}
Poll::Ready(Err(e)) => Poll::Ready(SCPickerOutcome::Error(e)),
}
}
}
#[cfg(feature = "macos_14_0")]
pub struct AsyncPickerFilterFuture {
inner: AsyncCompletionFuture<AsyncPickerCallbackResult>,
}
#[cfg(feature = "macos_14_0")]
impl std::fmt::Debug for AsyncPickerFilterFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncPickerFilterFuture")
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_14_0")]
impl Future for AsyncPickerFilterFuture {
type Output = crate::content_sharing_picker::SCPickerFilterOutcome;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
use crate::content_sharing_picker::SCPickerFilterOutcome;
match Pin::new(&mut self.inner).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(mut result)) => {
let outcome = match result.code {
1 if !result.ptr.is_null() => SCPickerFilterOutcome::Filter(
SCContentFilter::from_picker_ptr(result.take_ptr()),
),
0 => SCPickerFilterOutcome::Cancelled,
_ => SCPickerFilterOutcome::Error("Picker failed".to_string()),
};
Poll::Ready(outcome)
}
Poll::Ready(Err(e)) => Poll::Ready(SCPickerFilterOutcome::Error(e)),
}
}
}
#[cfg(feature = "macos_14_0")]
#[derive(Debug, Clone, Copy)]
pub struct AsyncSCContentSharingPicker;
#[cfg(feature = "macos_14_0")]
impl AsyncSCContentSharingPicker {
pub fn show(
config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
) -> AsyncPickerFuture {
let (future, context) = AsyncCompletion::create_unbounded();
unsafe {
crate::ffi::sc_content_sharing_picker_show_with_result(
config.as_ptr(),
async_picker_result_callback,
context,
);
}
AsyncPickerFuture { inner: future }
}
pub fn show_filter(
config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
) -> AsyncPickerFilterFuture {
let (future, context) = AsyncCompletion::create_unbounded();
unsafe {
crate::ffi::sc_content_sharing_picker_show(
config.as_ptr(),
async_picker_filter_callback,
context,
);
}
AsyncPickerFilterFuture { inner: future }
}
pub fn show_for_stream(
config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
stream: &crate::stream::SCStream,
) -> AsyncPickerFuture {
let (future, context) = AsyncCompletion::create_unbounded();
unsafe {
crate::ffi::sc_content_sharing_picker_show_for_stream(
config.as_ptr(),
stream.as_ptr(),
async_picker_result_callback,
context,
);
}
AsyncPickerFuture { inner: future }
}
}
#[cfg(feature = "macos_15_0")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RecordingEvent {
Started,
Finished,
Failed(String),
}
#[cfg(feature = "macos_15_0")]
struct AsyncRecordingState {
events: std::collections::VecDeque<RecordingEvent>,
waiters: Vec<RegisteredWaker>,
finished: bool,
}
#[cfg(feature = "macos_15_0")]
struct AsyncRecordingDelegate {
state: Arc<Mutex<AsyncRecordingState>>,
}
#[cfg(feature = "macos_15_0")]
fn push_recording_event(state: &Arc<Mutex<AsyncRecordingState>>, event: Option<RecordingEvent>) {
let Ok(mut guard) = state.lock() else {
return;
};
match event {
Some(event) => {
guard.finished |= matches!(event, RecordingEvent::Finished | RecordingEvent::Failed(_));
guard.events.push_back(event);
}
None => guard.finished = true,
}
let waiters = std::mem::take(&mut guard.waiters);
drop(guard);
wake_all(waiters);
}
#[cfg(feature = "macos_15_0")]
impl crate::recording_output::SCRecordingOutputDelegate for AsyncRecordingDelegate {
fn recording_did_start(&self) {
push_recording_event(&self.state, Some(RecordingEvent::Started));
}
fn recording_did_fail(&self, error: String) {
push_recording_event(&self.state, Some(RecordingEvent::Failed(error)));
}
fn recording_did_finish(&self) {
push_recording_event(&self.state, Some(RecordingEvent::Finished));
}
}
#[cfg(feature = "macos_15_0")]
impl Drop for AsyncRecordingDelegate {
fn drop(&mut self) {
push_recording_event(&self.state, None);
}
}
#[cfg(feature = "macos_15_0")]
pub struct NextRecordingEvent<'a> {
state: &'a Arc<Mutex<AsyncRecordingState>>,
waiter: Arc<()>,
}
#[cfg(feature = "macos_15_0")]
impl std::fmt::Debug for NextRecordingEvent<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NextRecordingEvent").finish_non_exhaustive()
}
}
#[cfg(feature = "macos_15_0")]
impl Future for NextRecordingEvent<'_> {
type Output = Option<RecordingEvent>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
poll_next_recording_event(self.state, &self.waiter, cx)
}
}
#[cfg(feature = "macos_15_0")]
impl Drop for NextRecordingEvent<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
#[cfg(feature = "macos_15_0")]
fn poll_next_recording_event(
state: &Arc<Mutex<AsyncRecordingState>>,
waiter: &Arc<()>,
cx: &Context<'_>,
) -> Poll<Option<RecordingEvent>> {
let waker = cx.waker().clone();
let Ok(mut state) = state.lock() else {
return Poll::Ready(None);
};
if let Some(event) = state.events.pop_front() {
let removed = unregister_waker(&mut state.waiters, waiter);
drop(state);
drop(removed);
drop(waker);
return Poll::Ready(Some(event));
}
if state.finished {
let removed = unregister_waker(&mut state.waiters, waiter);
drop(state);
drop(removed);
drop(waker);
Poll::Ready(None)
} else {
let replaced = register_waker(&mut state.waiters, waiter, waker);
drop(state);
drop(replaced);
Poll::Pending
}
}
#[cfg(feature = "macos_15_0")]
pub struct RecordingEventStream<'a> {
state: &'a Arc<Mutex<AsyncRecordingState>>,
waiter: Arc<()>,
}
#[cfg(feature = "macos_15_0")]
impl std::fmt::Debug for RecordingEventStream<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RecordingEventStream")
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_15_0")]
impl futures_core::Stream for RecordingEventStream<'_> {
type Item = RecordingEvent;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
poll_next_recording_event(self.state, &self.waiter, cx)
}
}
#[cfg(feature = "macos_15_0")]
impl Drop for RecordingEventStream<'_> {
fn drop(&mut self) {
let removed = self
.state
.lock()
.ok()
.and_then(|mut state| unregister_waker(&mut state.waiters, &self.waiter));
drop(removed);
}
}
#[cfg(feature = "macos_15_0")]
pub struct AsyncSCRecordingOutput {
state: Arc<Mutex<AsyncRecordingState>>,
}
#[cfg(feature = "macos_15_0")]
impl std::fmt::Debug for AsyncSCRecordingOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncSCRecordingOutput")
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_15_0")]
impl AsyncSCRecordingOutput {
#[must_use]
pub fn new(
config: &crate::recording_output::SCRecordingOutputConfiguration,
) -> Option<(crate::recording_output::SCRecordingOutput, Self)> {
let state = Arc::new(Mutex::new(AsyncRecordingState {
events: std::collections::VecDeque::new(),
waiters: Vec::new(),
finished: false,
}));
let delegate = AsyncRecordingDelegate {
state: Arc::clone(&state),
};
let recording =
crate::recording_output::SCRecordingOutput::new_with_delegate(config, delegate)?;
Some((recording, Self { state }))
}
pub fn next(&self) -> NextRecordingEvent<'_> {
NextRecordingEvent {
state: &self.state,
waiter: Arc::new(()),
}
}
#[must_use]
pub fn events(&self) -> RecordingEventStream<'_> {
RecordingEventStream {
state: &self.state,
waiter: Arc::new(()),
}
}
#[must_use]
pub fn is_finished(&self) -> bool {
self.state.lock().map_or(true, |s| s.finished)
}
#[must_use]
pub fn try_next(&self) -> Option<RecordingEvent> {
self.state.lock().ok()?.events.pop_front()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dropped_sample_future_unregisters_its_waker() {
let state = Arc::new(Mutex::new(AsyncSampleIteratorState {
buffer: std::collections::VecDeque::new(),
waiters: Vec::new(),
closed: false,
stopped: false,
capacity: 1,
senders: 1,
stop_error: None,
}));
let mut future = Box::pin(NextSample {
state: &state,
waiter: Arc::new(()),
});
let waker = Waker::noop();
let mut context = Context::from_waker(waker);
assert!(future.as_mut().poll(&mut context).is_pending());
assert_eq!(state.lock().unwrap().waiters.len(), 1);
drop(future);
assert!(state.lock().unwrap().waiters.is_empty());
}
fn idle_state() -> Arc<Mutex<AsyncSampleIteratorState>> {
Arc::new(Mutex::new(AsyncSampleIteratorState {
buffer: std::collections::VecDeque::new(),
waiters: Vec::new(),
closed: false,
stopped: false,
capacity: 1,
senders: 1,
stop_error: None,
}))
}
#[test]
fn failed_start_closes_the_queue_but_a_retry_reopens_it() {
let state = idle_state();
close_sample_state(
&state,
Some(SCError::CaptureStartFailed("denied".to_string())),
false,
);
assert!(state.lock().unwrap().closed);
reopen_sample_state(&state);
let (closed, stale_error) = {
let state = state.lock().unwrap();
(state.closed, state.stop_error.is_some())
};
assert!(!closed, "a retry must not see a permanently closed queue");
assert!(!stale_error, "the stale error must not survive");
}
#[test]
fn reopen_refuses_once_stop_capture_has_succeeded() {
let state = idle_state();
close_sample_state(&state, None, true);
reopen_sample_state(&state);
let closed = state.lock().unwrap().closed;
assert!(closed, "a stopped SCStream cannot be restarted");
}
}