use crate::error::SCError;
use crate::stream::configuration::SCStreamConfiguration;
use crate::stream::content_filter::SCContentFilter;
use crate::utils::completion::{error_from_cstr, SyncCompletion};
use std::ffi::c_void;
#[cfg(feature = "macos_15_2")]
use crate::cg::CGRect;
#[doc(no_inline)]
pub use apple_cf::cg::CGImage;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ImageFormat {
Png,
Jpeg(f32),
Tiff,
Gif,
Bmp,
Heic(f32),
}
impl ImageFormat {
fn to_format_id(self) -> i32 {
match self {
Self::Png => 0,
Self::Jpeg(_) => 1,
Self::Tiff => 2,
Self::Gif => 3,
Self::Bmp => 4,
Self::Heic(_) => 5,
}
}
fn quality(self) -> f32 {
match self {
Self::Jpeg(q) | Self::Heic(q) => q.clamp(0.0, 1.0),
_ => 1.0,
}
}
#[must_use]
pub const fn extension(&self) -> &'static str {
match self {
Self::Png => "png",
Self::Jpeg(_) => "jpg",
Self::Tiff => "tiff",
Self::Gif => "gif",
Self::Bmp => "bmp",
Self::Heic(_) => "heic",
}
}
}
pub(crate) unsafe fn cgimage_from_retained_ptr(ptr: *const c_void) -> CGImage {
unsafe { CGImage::from_raw(ptr.cast_mut()) }
}
extern "C" fn image_callback(
image_ptr: *const c_void,
error_ptr: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("image_callback", move || {
if !error_ptr.is_null() {
let error = unsafe { error_from_cstr(error_ptr) };
unsafe { SyncCompletion::<CGImage>::complete_err(user_data, error) };
} else if !image_ptr.is_null() {
unsafe { SyncCompletion::complete_ok(user_data, cgimage_from_retained_ptr(image_ptr)) };
} else {
unsafe {
SyncCompletion::<CGImage>::complete_err(user_data, "Unknown error".to_string());
};
}
});
}
extern "C" fn buffer_callback(
buffer_ptr: *const c_void,
error_ptr: *const i8,
user_data: *mut c_void,
) {
crate::utils::panic_safe::catch_user_panic("buffer_callback", move || {
if !error_ptr.is_null() {
let error = unsafe { error_from_cstr(error_ptr) };
unsafe { SyncCompletion::<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 { SyncCompletion::complete_ok(user_data, buffer) };
} else {
unsafe {
SyncCompletion::<crate::cm::CMSampleBuffer>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
#[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 { SyncCompletion::<SCScreenshotOutput>::complete_err(user_data, error) };
} else if !output_ptr.is_null() {
unsafe {
SyncCompletion::complete_ok(user_data, SCScreenshotOutput::from_ptr(output_ptr));
};
} else {
unsafe {
SyncCompletion::<SCScreenshotOutput>::complete_err(
user_data,
"Unknown error".to_string(),
);
};
}
});
}
pub trait CGImageExt {
fn rgba_data(&self) -> Result<Vec<u8>, SCError>;
fn bgra_data(&self) -> Result<Vec<u8>, SCError>;
fn rgba_data_into(&self, dest: &mut [u8]) -> Result<usize, SCError>;
fn bgra_data_into(&self, dest: &mut [u8]) -> Result<usize, SCError>;
fn rgba_data_into_strided(
&self,
dest: &mut [u8],
dest_bytes_per_row: usize,
) -> Result<usize, SCError>;
fn bgra_data_into_strided(
&self,
dest: &mut [u8],
dest_bytes_per_row: usize,
) -> Result<usize, SCError>;
fn save(&self, path: &str, format: ImageFormat) -> Result<(), SCError>;
}
#[derive(Debug, Clone, Copy)]
enum PixelLayout {
Rgba,
Bgra,
}
impl PixelLayout {
const fn name(self) -> &'static str {
match self {
Self::Rgba => "RGBA",
Self::Bgra => "BGRA",
}
}
unsafe fn render(self, ptr: *const c_void, dest: *mut u8, capacity: usize) -> usize {
unsafe {
match self {
Self::Rgba => crate::ffi::cgimage_render_rgba_into(ptr, dest, capacity),
Self::Bgra => crate::ffi::cgimage_render_bgra_into(ptr, dest, capacity),
}
}
}
unsafe fn render_strided(
self,
ptr: *const c_void,
dest: *mut u8,
capacity: usize,
bytes_per_row: usize,
) -> usize {
unsafe {
match self {
Self::Rgba => {
crate::ffi::cgimage_render_rgba_into_strided(ptr, dest, capacity, bytes_per_row)
}
Self::Bgra => {
crate::ffi::cgimage_render_bgra_into_strided(ptr, dest, capacity, bytes_per_row)
}
}
}
}
}
impl CGImageExt for CGImage {
fn rgba_data(&self) -> Result<Vec<u8>, SCError> {
render_pixel_data(self, PixelLayout::Rgba)
}
fn bgra_data(&self) -> Result<Vec<u8>, SCError> {
render_pixel_data(self, PixelLayout::Bgra)
}
fn rgba_data_into(&self, dest: &mut [u8]) -> Result<usize, SCError> {
render_pixel_data_into(self, dest, PixelLayout::Rgba)
}
fn bgra_data_into(&self, dest: &mut [u8]) -> Result<usize, SCError> {
render_pixel_data_into(self, dest, PixelLayout::Bgra)
}
fn rgba_data_into_strided(
&self,
dest: &mut [u8],
dest_bytes_per_row: usize,
) -> Result<usize, SCError> {
render_pixel_data_into_strided(self, dest, dest_bytes_per_row, PixelLayout::Rgba)
}
fn bgra_data_into_strided(
&self,
dest: &mut [u8],
dest_bytes_per_row: usize,
) -> Result<usize, SCError> {
render_pixel_data_into_strided(self, dest, dest_bytes_per_row, PixelLayout::Bgra)
}
fn save(&self, path: &str, format: ImageFormat) -> Result<(), SCError> {
let c_path = std::ffi::CString::new(path)
.map_err(|_| SCError::internal_error("Path contains null bytes"))?;
let success = unsafe {
crate::ffi::cgimage_save_to_file(
self.as_ptr(),
c_path.as_ptr(),
format.to_format_id(),
format.quality(),
)
};
if success {
Ok(())
} else {
Err(SCError::internal_error(format!(
"Failed to save image as {}",
format.extension().to_uppercase()
)))
}
}
}
fn render_pixel_data(image: &CGImage, layout: PixelLayout) -> Result<Vec<u8>, SCError> {
let total_bytes = required_byte_size(image)?;
if total_bytes == 0 {
return Ok(Vec::new());
}
let mut data: Vec<u8> = Vec::with_capacity(total_bytes);
let written = unsafe { layout.render(image.as_ptr(), data.as_mut_ptr(), total_bytes) };
if written != total_bytes {
return Err(SCError::internal_error(format!(
"Failed to render CGImage into {} buffer",
layout.name()
)));
}
unsafe { data.set_len(total_bytes) };
Ok(data)
}
fn render_pixel_data_into(
image: &CGImage,
dest: &mut [u8],
layout: PixelLayout,
) -> Result<usize, SCError> {
let total_bytes = required_byte_size(image)?;
if dest.len() < total_bytes {
return Err(SCError::internal_error(format!(
"Destination buffer too small: need {total_bytes} bytes, got {}",
dest.len()
)));
}
if total_bytes == 0 {
return Ok(0);
}
let written = unsafe { layout.render(image.as_ptr(), dest.as_mut_ptr(), total_bytes) };
if written != total_bytes {
return Err(SCError::internal_error(format!(
"Failed to render CGImage into {} buffer",
layout.name()
)));
}
Ok(written)
}
fn render_pixel_data_into_strided(
image: &CGImage,
dest: &mut [u8],
dest_bytes_per_row: usize,
layout: PixelLayout,
) -> Result<usize, SCError> {
let width = image.width();
let height = image.height();
let min_bytes_per_row = width
.checked_mul(4)
.ok_or_else(|| SCError::internal_error("CGImage row size overflows usize"))?;
if dest_bytes_per_row < min_bytes_per_row {
return Err(SCError::internal_error(format!(
"Destination row stride too small: need at least {min_bytes_per_row} bytes, got {dest_bytes_per_row}"
)));
}
let required = height
.checked_mul(dest_bytes_per_row)
.ok_or_else(|| SCError::internal_error("CGImage strided size overflows usize"))?;
if dest.len() < required {
return Err(SCError::internal_error(format!(
"Destination buffer too small: need {required} bytes, got {}",
dest.len()
)));
}
if required == 0 {
return Ok(0);
}
let written = unsafe {
layout.render_strided(
image.as_ptr(),
dest.as_mut_ptr(),
dest.len(),
dest_bytes_per_row,
)
};
if written != required {
return Err(SCError::internal_error(format!(
"Failed to render CGImage into {} buffer",
layout.name()
)));
}
Ok(written)
}
fn required_byte_size(image: &CGImage) -> Result<usize, SCError> {
image
.width()
.checked_mul(image.height())
.and_then(|n| n.checked_mul(4))
.ok_or_else(|| SCError::internal_error("CGImage dimensions overflow usize"))
}
#[derive(Debug)]
pub struct SCScreenshotManager;
impl SCScreenshotManager {
pub fn capture_image(
content_filter: &SCContentFilter,
configuration: &SCStreamConfiguration,
) -> Result<CGImage, SCError> {
let (completion, context) = SyncCompletion::<CGImage>::new();
unsafe {
crate::ffi::sc_screenshot_manager_capture_image(
content_filter.as_ptr(),
configuration.as_ptr(),
image_callback,
context,
);
}
completion.wait().map_err(SCError::ScreenshotError)
}
pub fn capture_sample_buffer(
content_filter: &SCContentFilter,
configuration: &SCStreamConfiguration,
) -> Result<crate::cm::CMSampleBuffer, SCError> {
let (completion, context) = SyncCompletion::<crate::cm::CMSampleBuffer>::new();
unsafe {
crate::ffi::sc_screenshot_manager_capture_sample_buffer(
content_filter.as_ptr(),
configuration.as_ptr(),
buffer_callback,
context,
);
}
completion.wait().map_err(SCError::ScreenshotError)
}
#[cfg(feature = "macos_15_2")]
pub fn capture_image_in_rect(rect: CGRect) -> Result<CGImage, SCError> {
let (completion, context) = SyncCompletion::<CGImage>::new();
unsafe {
crate::ffi::sc_screenshot_manager_capture_image_in_rect(
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
image_callback,
context,
);
}
completion.wait().map_err(SCError::ScreenshotError)
}
#[cfg(feature = "macos_26_0")]
pub fn capture_screenshot(
content_filter: &SCContentFilter,
configuration: &SCScreenshotConfiguration,
) -> Result<SCScreenshotOutput, SCError> {
let (completion, context) = SyncCompletion::<SCScreenshotOutput>::new();
unsafe {
crate::ffi::sc_screenshot_manager_capture_screenshot(
content_filter.as_ptr(),
configuration.as_ptr(),
screenshot_output_callback,
context,
);
}
completion.wait().map_err(SCError::ScreenshotError)
}
#[cfg(feature = "macos_26_0")]
pub fn capture_screenshot_in_rect(
rect: crate::cg::CGRect,
configuration: &SCScreenshotConfiguration,
) -> Result<SCScreenshotOutput, SCError> {
let (completion, context) = SyncCompletion::<SCScreenshotOutput>::new();
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,
);
}
completion.wait().map_err(SCError::ScreenshotError)
}
}
#[cfg(feature = "macos_26_0")]
const UTTYPE_IDENTIFIER_BUFFER: usize = crate::utils::ffi_string::SMALL_BUFFER_SIZE;
#[cfg(feature = "macos_26_0")]
fn owned_path<F: FnOnce() -> *mut i8>(ffi_call: F) -> Option<std::path::PathBuf> {
use std::os::unix::ffi::OsStrExt;
let ptr = ffi_call();
if ptr.is_null() {
return None;
}
let bytes = unsafe { std::ffi::CStr::from_ptr(ptr) }.to_bytes().to_vec();
unsafe { crate::ffi::sc_free_string(ptr) };
if bytes.is_empty() {
return None;
}
Some(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(
&bytes,
)))
}
#[cfg(feature = "macos_26_0")]
fn read_rect<F>(ffi_call: F) -> crate::cg::CGRect
where
F: FnOnce(*mut f64, *mut f64, *mut f64, *mut f64),
{
let mut x = 0.0;
let mut y = 0.0;
let mut width = 0.0;
let mut height = 0.0;
ffi_call(&mut x, &mut y, &mut width, &mut height);
crate::cg::CGRect::new(x, y, width, height)
}
#[cfg(feature = "macos_26_0")]
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCScreenshotDisplayIntent {
#[default]
Canonical = 0,
Local = 1,
}
#[cfg(feature = "macos_26_0")]
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCScreenshotDynamicRange {
#[default]
SDR = 0,
HDR = 1,
BothSDRAndHDR = 2,
}
#[cfg(feature = "macos_26_0")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InvalidScreenshotPath {
NotUtf8,
InteriorNul,
}
#[cfg(feature = "macos_26_0")]
impl std::fmt::Display for InvalidScreenshotPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotUtf8 => f.write_str("screenshot path is not valid UTF-8"),
Self::InteriorNul => f.write_str("screenshot path contains an interior NUL byte"),
}
}
}
#[cfg(feature = "macos_26_0")]
impl std::error::Error for InvalidScreenshotPath {}
#[cfg(feature = "macos_26_0")]
pub struct SCScreenshotConfiguration {
ptr: *const c_void,
}
#[cfg(feature = "macos_26_0")]
impl SCScreenshotConfiguration {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { crate::ffi::sc_screenshot_configuration_create() };
assert!(!ptr.is_null(), "Failed to create SCScreenshotConfiguration");
Self { ptr }
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn with_width(self, width: usize) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_width(self.ptr, width as isize);
}
self
}
#[must_use]
#[allow(clippy::cast_possible_wrap)]
pub fn with_height(self, height: usize) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_height(self.ptr, height as isize);
}
self
}
#[must_use]
pub fn with_shows_cursor(self, shows_cursor: bool) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_shows_cursor(self.ptr, shows_cursor);
}
self
}
#[must_use]
pub fn with_source_rect(self, rect: crate::cg::CGRect) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_source_rect(
self.ptr,
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
);
}
self
}
#[must_use]
pub fn with_destination_rect(self, rect: crate::cg::CGRect) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_destination_rect(
self.ptr,
rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height,
);
}
self
}
#[must_use]
pub fn with_ignore_shadows(self, ignore_shadows: bool) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_ignore_shadows(self.ptr, ignore_shadows);
}
self
}
#[must_use]
pub fn with_ignore_clipping(self, ignore_clipping: bool) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_ignore_clipping(self.ptr, ignore_clipping);
}
self
}
#[must_use]
pub fn with_include_child_windows(self, include_child_windows: bool) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_include_child_windows(
self.ptr,
include_child_windows,
);
}
self
}
#[must_use]
pub fn with_display_intent(self, display_intent: SCScreenshotDisplayIntent) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_display_intent(
self.ptr,
display_intent as i32,
);
}
self
}
#[must_use]
pub fn with_dynamic_range(self, dynamic_range: SCScreenshotDynamicRange) -> Self {
unsafe {
crate::ffi::sc_screenshot_configuration_set_dynamic_range(
self.ptr,
dynamic_range as i32,
);
}
self
}
pub fn set_file_path(&mut self, path: impl AsRef<std::path::Path>) -> &mut Self {
if let Err(error) = self.try_set_file_path(path) {
eprintln!("SCScreenshotConfiguration: {error}; file path was not changed");
}
self
}
pub fn try_set_file_path(
&mut self,
path: impl AsRef<std::path::Path>,
) -> Result<&mut Self, InvalidScreenshotPath> {
let path = path
.as_ref()
.to_str()
.ok_or(InvalidScreenshotPath::NotUtf8)?;
let c_path =
std::ffi::CString::new(path).map_err(|_| InvalidScreenshotPath::InteriorNul)?;
unsafe {
crate::ffi::sc_screenshot_configuration_set_file_url(self.ptr, c_path.as_ptr());
}
Ok(self)
}
#[must_use]
pub fn with_file_path(mut self, path: impl AsRef<std::path::Path>) -> Self {
self.set_file_path(path);
self
}
#[must_use]
pub fn without_file_path(self) -> Self {
unsafe { crate::ffi::sc_screenshot_configuration_clear_file_url(self.ptr) };
self
}
pub fn clear_file_path(&mut self) -> &mut Self {
unsafe { crate::ffi::sc_screenshot_configuration_clear_file_url(self.ptr) };
self
}
#[must_use]
pub fn file_path(&self) -> Option<std::path::PathBuf> {
owned_path(|| unsafe {
crate::ffi::sc_screenshot_configuration_get_file_path_owned(self.ptr)
})
}
#[must_use]
pub fn width(&self) -> usize {
let width = unsafe { crate::ffi::sc_screenshot_configuration_get_width(self.ptr) };
usize::try_from(width).unwrap_or(0)
}
#[must_use]
pub fn height(&self) -> usize {
let height = unsafe { crate::ffi::sc_screenshot_configuration_get_height(self.ptr) };
usize::try_from(height).unwrap_or(0)
}
#[must_use]
pub fn shows_cursor(&self) -> bool {
unsafe { crate::ffi::sc_screenshot_configuration_get_shows_cursor(self.ptr) }
}
#[must_use]
pub fn source_rect(&self) -> crate::cg::CGRect {
read_rect(|x, y, w, h| unsafe {
crate::ffi::sc_screenshot_configuration_get_source_rect(self.ptr, x, y, w, h);
})
}
#[must_use]
pub fn destination_rect(&self) -> crate::cg::CGRect {
read_rect(|x, y, w, h| unsafe {
crate::ffi::sc_screenshot_configuration_get_destination_rect(self.ptr, x, y, w, h);
})
}
#[must_use]
pub fn ignore_shadows(&self) -> bool {
unsafe { crate::ffi::sc_screenshot_configuration_get_ignore_shadows(self.ptr) }
}
#[must_use]
pub fn ignore_clipping(&self) -> bool {
unsafe { crate::ffi::sc_screenshot_configuration_get_ignore_clipping(self.ptr) }
}
#[must_use]
pub fn include_child_windows(&self) -> bool {
unsafe { crate::ffi::sc_screenshot_configuration_get_include_child_windows(self.ptr) }
}
#[must_use]
pub fn display_intent(&self) -> Option<SCScreenshotDisplayIntent> {
match unsafe { crate::ffi::sc_screenshot_configuration_get_display_intent(self.ptr) } {
0 => Some(SCScreenshotDisplayIntent::Canonical),
1 => Some(SCScreenshotDisplayIntent::Local),
_ => None,
}
}
#[must_use]
pub fn dynamic_range(&self) -> Option<SCScreenshotDynamicRange> {
match unsafe { crate::ffi::sc_screenshot_configuration_get_dynamic_range(self.ptr) } {
0 => Some(SCScreenshotDynamicRange::SDR),
1 => Some(SCScreenshotDynamicRange::HDR),
2 => Some(SCScreenshotDynamicRange::BothSDRAndHDR),
_ => None,
}
}
#[must_use]
pub fn with_content_type(self, identifier: &str) -> Self {
if let Ok(c_id) = std::ffi::CString::new(identifier) {
unsafe {
crate::ffi::sc_screenshot_configuration_set_content_type(self.ptr, c_id.as_ptr());
}
} else {
eprintln!(
"SCScreenshotConfiguration: content type contains an interior NUL byte; \
content type was not changed"
);
}
self
}
#[must_use]
pub fn content_type(&self) -> Option<String> {
unsafe {
crate::utils::ffi_string::ffi_string_from_buffer(
UTTYPE_IDENTIFIER_BUFFER,
|buffer, len| {
crate::ffi::sc_screenshot_configuration_get_content_type(
self.ptr,
buffer,
usize::try_from(len).unwrap_or(0),
)
},
)
}
}
#[must_use]
pub fn supported_content_types() -> Vec<String> {
let count =
unsafe { crate::ffi::sc_screenshot_configuration_get_supported_content_types_count() };
(0..count)
.filter_map(|i| unsafe {
crate::utils::ffi_string::ffi_string_from_buffer(
UTTYPE_IDENTIFIER_BUFFER,
|buffer, len| {
crate::ffi::sc_screenshot_configuration_get_supported_content_type_at(
i,
buffer,
usize::try_from(len).unwrap_or(0),
)
},
)
})
.collect()
}
#[must_use]
pub const fn as_ptr(&self) -> *const c_void {
self.ptr
}
}
#[cfg(feature = "macos_26_0")]
impl std::fmt::Debug for SCScreenshotConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SCScreenshotConfiguration")
.field("content_type", &self.content_type())
.finish_non_exhaustive()
}
}
#[cfg(feature = "macos_26_0")]
impl Default for SCScreenshotConfiguration {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "macos_26_0")]
crate::utils::retained::sc_retained!(
SCScreenshotConfiguration,
field = ptr,
release = crate::ffi::sc_screenshot_configuration_release,
);
#[cfg(feature = "macos_26_0")]
unsafe impl Send for SCScreenshotConfiguration {}
#[cfg(feature = "macos_26_0")]
unsafe impl Sync for SCScreenshotConfiguration {}
#[cfg(feature = "macos_26_0")]
pub struct SCScreenshotOutput {
ptr: *const c_void,
}
#[cfg(feature = "macos_26_0")]
impl SCScreenshotOutput {
pub(crate) fn from_ptr(ptr: *const c_void) -> Self {
Self { ptr }
}
#[must_use]
pub fn sdr_image(&self) -> Option<CGImage> {
let ptr = unsafe { crate::ffi::sc_screenshot_output_get_sdr_image(self.ptr) };
if ptr.is_null() {
None
} else {
Some(unsafe { cgimage_from_retained_ptr(ptr) })
}
}
#[must_use]
pub fn hdr_image(&self) -> Option<CGImage> {
let ptr = unsafe { crate::ffi::sc_screenshot_output_get_hdr_image(self.ptr) };
if ptr.is_null() {
None
} else {
Some(unsafe { cgimage_from_retained_ptr(ptr) })
}
}
#[must_use]
pub fn file_path(&self) -> Option<std::path::PathBuf> {
owned_path(|| unsafe { crate::ffi::sc_screenshot_output_get_file_path_owned(self.ptr) })
}
}
#[cfg(feature = "macos_26_0")]
impl std::fmt::Debug for SCScreenshotOutput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SCScreenshotOutput")
.field(
"sdr_image",
&self.sdr_image().map(|i| (i.width(), i.height())),
)
.field(
"hdr_image",
&self.hdr_image().map(|i| (i.width(), i.height())),
)
.field("file_path", &self.file_path())
.finish()
}
}
#[cfg(feature = "macos_26_0")]
crate::utils::retained::sc_retained!(
SCScreenshotOutput,
field = ptr,
release = crate::ffi::sc_screenshot_output_release,
);
#[cfg(feature = "macos_26_0")]
unsafe impl Send for SCScreenshotOutput {}
#[cfg(feature = "macos_26_0")]
unsafe impl Sync for SCScreenshotOutput {}