use std::fmt;
pub type SCResult<T> = Result<T, SCError>;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SCError {
InvalidConfiguration(String),
InvalidDimension { field: String, value: usize },
InvalidPixelFormat(String),
NoShareableContent(String),
DisplayNotFound(String),
WindowNotFound(String),
ApplicationNotFound(String),
StreamError(String),
CaptureStartFailed(String),
CaptureStopFailed(String),
BufferLockError(String),
BufferUnlockError(String),
InvalidBuffer(String),
ScreenshotError(String),
PermissionDenied(String),
FeatureNotAvailable {
feature: String,
required_version: String,
},
FFIError(String),
NullPointer(String),
Timeout(String),
InternalError(String),
OSError { code: i32, message: String },
SCStreamError {
code: SCStreamErrorCode,
message: Option<String>,
},
}
impl fmt::Display for SCError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidConfiguration(msg) => write!(f, "Invalid configuration: {msg}"),
Self::InvalidDimension { field, value } => {
write!(
f,
"Invalid dimension: {field} must be greater than 0 (got {value})"
)
}
Self::InvalidPixelFormat(msg) => write!(f, "Invalid pixel format: {msg}"),
Self::NoShareableContent(msg) => write!(f, "No shareable content available: {msg}"),
Self::DisplayNotFound(msg) => write!(f, "Display not found: {msg}"),
Self::WindowNotFound(msg) => write!(f, "Window not found: {msg}"),
Self::ApplicationNotFound(msg) => write!(f, "Application not found: {msg}"),
Self::StreamError(msg) => write!(f, "Stream error: {msg}"),
Self::CaptureStartFailed(msg) => write!(f, "Failed to start capture: {msg}"),
Self::CaptureStopFailed(msg) => write!(f, "Failed to stop capture: {msg}"),
Self::BufferLockError(msg) => write!(f, "Failed to lock pixel buffer: {msg}"),
Self::BufferUnlockError(msg) => write!(f, "Failed to unlock pixel buffer: {msg}"),
Self::InvalidBuffer(msg) => write!(f, "Invalid buffer: {msg}"),
Self::ScreenshotError(msg) => write!(f, "Screenshot capture failed: {msg}"),
Self::PermissionDenied(msg) => {
write!(f, "Permission denied: {msg}. Check System Preferences → Security & Privacy → Screen Recording")
}
Self::FeatureNotAvailable {
feature,
required_version,
} => {
write!(
f,
"Feature not available: {feature} requires macOS {required_version}+"
)
}
Self::FFIError(msg) => write!(f, "FFI error: {msg}"),
Self::NullPointer(msg) => write!(f, "Null pointer: {msg}"),
Self::Timeout(msg) => write!(f, "Operation timed out: {msg}"),
Self::InternalError(msg) => write!(f, "Internal error: {msg}"),
Self::OSError { code, message } => write!(f, "OS error {code}: {message}"),
Self::SCStreamError { code, message } => {
if let Some(msg) = message {
write!(f, "SCStream error ({code}): {msg}")
} else {
write!(f, "SCStream error: {code}")
}
}
}
}
}
impl std::error::Error for SCError {}
impl From<SCStreamErrorCode> for SCError {
fn from(code: SCStreamErrorCode) -> Self {
Self::from_stream_error_code(code)
}
}
impl SCError {
pub fn invalid_config(message: impl Into<String>) -> Self {
Self::InvalidConfiguration(message.into())
}
pub fn invalid_dimension(field: impl Into<String>, value: usize) -> Self {
Self::InvalidDimension {
field: field.into(),
value,
}
}
pub fn stream_error(message: impl Into<String>) -> Self {
Self::StreamError(message.into())
}
pub fn permission_denied(message: impl Into<String>) -> Self {
Self::PermissionDenied(message.into())
}
pub fn ffi_error(message: impl Into<String>) -> Self {
Self::FFIError(message.into())
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::InternalError(message.into())
}
pub fn null_pointer(context: impl Into<String>) -> Self {
Self::NullPointer(context.into())
}
pub fn feature_not_available(feature: impl Into<String>, version: impl Into<String>) -> Self {
Self::FeatureNotAvailable {
feature: feature.into(),
required_version: version.into(),
}
}
pub fn buffer_lock_error(message: impl Into<String>) -> Self {
Self::BufferLockError(message.into())
}
pub fn os_error(code: i32, message: impl Into<String>) -> Self {
Self::OSError {
code,
message: message.into(),
}
}
pub fn from_stream_error_code(code: SCStreamErrorCode) -> Self {
Self::SCStreamError {
code,
message: None,
}
}
pub fn from_stream_error_code_with_message(
code: SCStreamErrorCode,
message: impl Into<String>,
) -> Self {
Self::SCStreamError {
code,
message: Some(message.into()),
}
}
pub fn from_error_code(code: i32) -> Self {
SCStreamErrorCode::from_raw(code).map_or_else(
|| Self::OSError {
code,
message: "Unknown error".to_string(),
},
Self::from_stream_error_code,
)
}
pub fn stream_error_code(&self) -> Option<SCStreamErrorCode> {
match self {
Self::SCStreamError { code, .. } => Some(*code),
_ => None,
}
}
}
pub const SC_STREAM_ERROR_DOMAIN: &str = "com.apple.screencapturekit";
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SCStreamErrorCode {
UserDeclined = -3801,
FailedToStart = -3802,
MissingEntitlements = -3803,
FailedApplicationConnectionInvalid = -3804,
FailedApplicationConnectionInterrupted = -3805,
FailedNoMatchingApplicationContext = -3806,
AttemptToStartStreamState = -3807,
AttemptToStopStreamState = -3808,
AttemptToUpdateFilterState = -3809,
AttemptToConfigState = -3810,
InternalError = -3811,
InvalidParameter = -3812,
NoWindowList = -3813,
NoDisplayList = -3814,
NoCaptureSource = -3815,
RemovingStream = -3816,
UserStopped = -3817,
FailedToStartAudioCapture = -3818,
FailedToStopAudioCapture = -3819,
FailedToStartMicrophoneCapture = -3820,
SystemStoppedStream = -3821,
}
impl SCStreamErrorCode {
pub fn from_raw(code: i32) -> Option<Self> {
match code {
-3801 => Some(Self::UserDeclined),
-3802 => Some(Self::FailedToStart),
-3803 => Some(Self::MissingEntitlements),
-3804 => Some(Self::FailedApplicationConnectionInvalid),
-3805 => Some(Self::FailedApplicationConnectionInterrupted),
-3806 => Some(Self::FailedNoMatchingApplicationContext),
-3807 => Some(Self::AttemptToStartStreamState),
-3808 => Some(Self::AttemptToStopStreamState),
-3809 => Some(Self::AttemptToUpdateFilterState),
-3810 => Some(Self::AttemptToConfigState),
-3811 => Some(Self::InternalError),
-3812 => Some(Self::InvalidParameter),
-3813 => Some(Self::NoWindowList),
-3814 => Some(Self::NoDisplayList),
-3815 => Some(Self::NoCaptureSource),
-3816 => Some(Self::RemovingStream),
-3817 => Some(Self::UserStopped),
-3818 => Some(Self::FailedToStartAudioCapture),
-3819 => Some(Self::FailedToStopAudioCapture),
-3820 => Some(Self::FailedToStartMicrophoneCapture),
-3821 => Some(Self::SystemStoppedStream),
_ => None,
}
}
pub const fn as_raw(self) -> i32 {
self as i32
}
}
impl std::fmt::Display for SCStreamErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UserDeclined => write!(f, "User declined screen recording"),
Self::FailedToStart => write!(f, "Failed to start stream"),
Self::MissingEntitlements => write!(f, "Missing entitlements"),
Self::FailedApplicationConnectionInvalid => {
write!(f, "Application connection invalid")
}
Self::FailedApplicationConnectionInterrupted => {
write!(f, "Application connection interrupted")
}
Self::FailedNoMatchingApplicationContext => {
write!(f, "No matching application context")
}
Self::AttemptToStartStreamState => write!(f, "Stream is already running"),
Self::AttemptToStopStreamState => write!(f, "Stream is not running"),
Self::AttemptToUpdateFilterState => write!(f, "Cannot update filter while streaming"),
Self::AttemptToConfigState => write!(f, "Cannot configure while streaming"),
Self::InternalError => write!(f, "Internal error"),
Self::InvalidParameter => write!(f, "Invalid parameter"),
Self::NoWindowList => write!(f, "No window list provided"),
Self::NoDisplayList => write!(f, "No display list provided"),
Self::NoCaptureSource => write!(f, "No capture source provided"),
Self::RemovingStream => write!(f, "Failed to remove stream"),
Self::UserStopped => write!(f, "User stopped the stream"),
Self::FailedToStartAudioCapture => write!(f, "Failed to start audio capture"),
Self::FailedToStopAudioCapture => write!(f, "Failed to stop audio capture"),
Self::FailedToStartMicrophoneCapture => write!(f, "Failed to start microphone capture"),
Self::SystemStoppedStream => write!(f, "System stopped the stream"),
}
}
}