use crate::error::SCError;
use crate::shareable_content::SCShareableContent;
use crate::stream::configuration::SCStreamConfiguration;
use crate::stream::content_filter::SCContentFilter;
use crate::utils::completion::{error_from_cstr, 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};
extern "C" fn shareable_content_callback(
content: *const c_void,
error: *const i8,
user_data: *mut c_void,
) {
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>,
waker: Option<Waker>,
closed: bool,
capacity: usize,
}
struct AsyncSampleSender {
inner: Arc<Mutex<AsyncSampleIteratorState>>,
}
impl crate::stream::output_trait::SCStreamOutputTrait for AsyncSampleSender {
fn did_output_sample_buffer(
&self,
sample_buffer: crate::cm::CMSampleBuffer,
_of_type: crate::stream::output_type::SCStreamOutputType,
) {
let Ok(mut state) = self.inner.lock() else {
return;
};
if state.buffer.len() >= state.capacity {
state.buffer.pop_front();
}
state.buffer.push_back(sample_buffer);
if let Some(waker) = state.waker.take() {
waker.wake();
}
}
}
impl Drop for AsyncSampleSender {
fn drop(&mut self) {
if let Ok(mut state) = self.inner.lock() {
state.closed = true;
if let Some(waker) = state.waker.take() {
waker.wake();
}
}
}
}
pub struct NextSample<'a> {
state: &'a Arc<Mutex<AsyncSampleIteratorState>>,
}
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> {
let Ok(mut state) = self.state.lock() else {
return Poll::Ready(None);
};
if let Some(sample) = state.buffer.pop_front() {
return Poll::Ready(Some(sample));
}
if state.closed {
Poll::Ready(None)
} else {
state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
unsafe impl Send for AsyncSampleSender {}
unsafe impl Sync for AsyncSampleSender {}
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),
waker: None,
closed: false,
capacity: buffer_capacity,
}));
let sender = AsyncSampleSender {
inner: Arc::clone(&state),
};
let mut stream = crate::stream::SCStream::new(filter, config);
stream.add_output_handler(sender, output_type);
Self {
stream,
iterator_state: state,
}
}
pub fn next(&self) -> NextSample<'_> {
NextSample {
state: &self.iterator_state,
}
}
#[must_use]
pub fn try_next(&self) -> Option<crate::cm::CMSampleBuffer> {
self.iterator_state.lock().ok()?.buffer.pop_front()
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.iterator_state.lock().map(|s| s.closed).unwrap_or(true)
}
#[must_use]
pub fn buffered_count(&self) -> usize {
self.iterator_state
.lock()
.map(|s| s.buffer.len())
.unwrap_or(0)
}
pub fn clear_buffer(&self) {
if let Ok(mut state) = self.iterator_state.lock() {
state.buffer.clear();
}
}
pub fn start_capture(&self) -> Result<(), SCError> {
self.stream.start_capture()
}
pub fn stop_capture(&self) -> Result<(), SCError> {
self.stream.stop_capture()
}
pub fn update_configuration(&self, config: &SCStreamConfiguration) -> Result<(), SCError> {
self.stream.update_configuration(config)
}
pub fn update_content_filter(&self, filter: &SCContentFilter) -> Result<(), SCError> {
self.stream.update_content_filter(filter)
}
#[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,
) {
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 = crate::screenshot_manager::CGImage::from_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,
) {
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> {
inner: AsyncCompletionFuture<T>,
}
#[cfg(feature = "macos_14_0")]
impl<T> 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> 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.x,
rect.y,
rect.width,
rect.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.x,
rect.y,
rect.width,
rect.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,
) {
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,
}
#[cfg(feature = "macos_14_0")]
unsafe impl Send for AsyncPickerCallbackResult {}
#[cfg(feature = "macos_14_0")]
extern "C" fn async_picker_callback(result_code: i32, ptr: *const c_void, user_data: *mut c_void) {
let result = AsyncPickerCallbackResult {
code: result_code,
ptr,
};
unsafe { AsyncCompletion::complete_ok(user_data, result) };
}
#[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(result)) => {
let outcome = match result.code {
1 if !result.ptr.is_null() => {
SCPickerOutcome::Picked(SCPickerResult::from_ptr(result.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(result)) => {
let outcome = match result.code {
1 if !result.ptr.is_null() => {
SCPickerFilterOutcome::Filter(SCContentFilter::from_picker_ptr(result.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();
unsafe {
crate::ffi::sc_content_sharing_picker_show_with_result(
config.as_ptr(),
async_picker_callback,
context,
);
}
AsyncPickerFuture { inner: future }
}
pub fn show_filter(
config: &crate::content_sharing_picker::SCContentSharingPickerConfiguration,
) -> AsyncPickerFilterFuture {
let (future, context) = AsyncCompletion::create();
unsafe {
crate::ffi::sc_content_sharing_picker_show(
config.as_ptr(),
async_picker_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();
unsafe {
crate::ffi::sc_content_sharing_picker_show_for_stream(
config.as_ptr(),
stream.as_ptr(),
async_picker_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>,
waker: Option<Waker>,
finished: bool,
}
#[cfg(feature = "macos_15_0")]
struct AsyncRecordingDelegate {
state: Arc<Mutex<AsyncRecordingState>>,
}
#[cfg(feature = "macos_15_0")]
impl crate::recording_output::SCRecordingOutputDelegate for AsyncRecordingDelegate {
fn recording_did_start(&self) {
if let Ok(mut state) = self.state.lock() {
state.events.push_back(RecordingEvent::Started);
if let Some(waker) = state.waker.take() {
waker.wake();
}
}
}
fn recording_did_fail(&self, error: String) {
if let Ok(mut state) = self.state.lock() {
state.events.push_back(RecordingEvent::Failed(error));
state.finished = true;
if let Some(waker) = state.waker.take() {
waker.wake();
}
}
}
fn recording_did_finish(&self) {
if let Ok(mut state) = self.state.lock() {
state.events.push_back(RecordingEvent::Finished);
state.finished = true;
if let Some(waker) = state.waker.take() {
waker.wake();
}
}
}
}
#[cfg(feature = "macos_15_0")]
pub struct NextRecordingEvent<'a> {
state: &'a Arc<Mutex<AsyncRecordingState>>,
}
#[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> {
let Ok(mut state) = self.state.lock() else {
return Poll::Ready(None);
};
if let Some(event) = state.events.pop_front() {
return Poll::Ready(Some(event));
}
if state.finished {
Poll::Ready(None)
} else {
state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
#[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(),
waker: None,
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 }
}
#[must_use]
pub fn is_finished(&self) -> bool {
self.state.lock().map(|s| s.finished).unwrap_or(true)
}
#[must_use]
pub fn try_next(&self) -> Option<RecordingEvent> {
self.state.lock().ok()?.events.pop_front()
}
}