1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Stream identification and HDR configuration
//!
//! This module provides methods to configure stream identification and HDR capture settings.
use super::internal::SCStreamConfiguration;
#[cfg(feature = "macos_14_0")]
use super::InteriorNulError;
#[cfg(feature = "macos_14_0")]
use crate::error::{SCError, SCResult};
#[cfg(feature = "macos_14_0")]
use crate::utils::ffi_string::{ffi_string_from_buffer, SMALL_BUFFER_SIZE};
/// Dynamic range mode for capture (macOS 15.0+)
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SCCaptureDynamicRange {
/// Standard Dynamic Range (SDR) - default mode
#[default]
SDR = 0,
/// HDR with local display tone mapping
HDRLocalDisplay = 1,
/// HDR with canonical display tone mapping
HDRCanonicalDisplay = 2,
}
impl SCCaptureDynamicRange {
pub const fn from_raw(raw: i32) -> Option<Self> {
match raw {
0 => Some(Self::SDR),
1 => Some(Self::HDRLocalDisplay),
2 => Some(Self::HDRCanonicalDisplay),
_ => None,
}
}
}
impl SCStreamConfiguration {
/// Set the stream name for identification
///
/// Assigns a name to the stream that can be used for debugging and identification
/// purposes. The name appears in system logs and debugging tools.
///
/// Available on macOS 14.0+.
///
/// # Errors
///
/// Returns [`SCError::InvalidConfiguration`] — leaving the configuration
/// unchanged — if `name` contains an interior NUL byte, and
/// [`SCError::FeatureNotAvailable`] before macOS 14.0.
///
/// # Examples
///
/// ```rust,no_run
/// use screencapturekit::prelude::*;
///
/// let config = SCStreamConfiguration::new()
/// .with_stream_name(Some("MyApp-MainCapture"))
/// .expect("stream name has no NUL byte");
/// ```
#[cfg(feature = "macos_14_0")]
pub fn set_stream_name(&mut self, name: Option<&str>) -> SCResult<&mut Self> {
let c_name = name
.map(|stream_name| std::ffi::CString::new(stream_name).map_err(|_| InteriorNulError))
.transpose()?;
let applied = unsafe {
crate::ffi::sc_stream_configuration_set_stream_name(
self.as_ptr(),
c_name.as_ref().map_or(std::ptr::null(), |n| n.as_ptr()),
)
};
applied.then_some(self).ok_or_else(|| {
SCError::feature_not_available("SCStreamConfiguration.streamName", "14.0")
})
}
/// Set the stream name (builder pattern)
#[cfg(feature = "macos_14_0")]
#[allow(clippy::missing_errors_doc)]
pub fn with_stream_name(mut self, name: Option<&str>) -> SCResult<Self> {
self.set_stream_name(name)?;
Ok(self)
}
/// Get the configured stream name
///
/// Returns the name assigned to this stream, if any.
#[cfg(feature = "macos_14_0")]
pub fn stream_name(&self) -> Option<String> {
unsafe {
ffi_string_from_buffer(SMALL_BUFFER_SIZE, |buf, len| {
crate::ffi::sc_stream_configuration_get_stream_name(self.as_ptr(), buf, len)
})
}
}
/// Set the dynamic range mode for capture (macOS 15.0+)
///
/// Controls whether to capture in SDR or HDR mode and how HDR content
/// should be tone-mapped for display.
///
/// # Availability
/// macOS 15.0+. Requires the `macos_15_0` feature flag to be enabled.
///
/// # Modes
/// - `SDR`: Standard dynamic range capture (default)
/// - `HDRLocalDisplay`: HDR with tone mapping optimized for the local display
/// - `HDRCanonicalDisplay`: HDR with canonical tone mapping for portability
///
/// # Examples
///
/// ```rust,no_run
/// use screencapturekit::prelude::*;
/// use screencapturekit::stream::configuration::stream_properties::SCCaptureDynamicRange;
///
/// let config = SCStreamConfiguration::new()
/// .with_width(1920)
/// .with_height(1080)
/// .with_capture_dynamic_range(SCCaptureDynamicRange::HDRLocalDisplay)
/// .expect("macOS 15.0 or later");
/// ```
#[cfg(feature = "macos_15_0")]
#[allow(clippy::missing_errors_doc)]
pub fn set_capture_dynamic_range(
&mut self,
dynamic_range: SCCaptureDynamicRange,
) -> SCResult<&mut Self> {
let applied = unsafe {
crate::ffi::sc_stream_configuration_set_capture_dynamic_range(
self.as_ptr(),
dynamic_range as i32,
)
};
applied.then_some(self).ok_or_else(|| {
SCError::feature_not_available("SCStreamConfiguration.captureDynamicRange", "15.0")
})
}
/// Set the dynamic range mode (builder pattern)
#[cfg(feature = "macos_15_0")]
#[allow(clippy::missing_errors_doc)]
pub fn with_capture_dynamic_range(
mut self,
dynamic_range: SCCaptureDynamicRange,
) -> SCResult<Self> {
self.set_capture_dynamic_range(dynamic_range)?;
Ok(self)
}
/// Get the configured dynamic range mode (macOS 15.0+)
///
/// Returns the current HDR capture mode setting.
///
/// Requires the `macos_15_0` feature flag to be enabled.
#[cfg(feature = "macos_15_0")]
#[allow(clippy::missing_errors_doc)]
pub fn capture_dynamic_range(&self) -> SCResult<SCCaptureDynamicRange> {
let mut raw = 0_i32;
let available = unsafe {
crate::ffi::sc_stream_configuration_get_capture_dynamic_range(
self.as_ptr(),
&raw mut raw,
)
};
if !available {
return Err(SCError::feature_not_available(
"SCStreamConfiguration.captureDynamicRange",
"15.0",
));
}
SCCaptureDynamicRange::from_raw(raw).ok_or_else(|| SCError::UnknownValue {
type_name: "SCCaptureDynamicRange",
raw: i64::from(raw),
})
}
}
#[cfg(all(test, feature = "macos_15_0"))]
mod tests {
use super::{SCCaptureDynamicRange, SCStreamConfiguration};
#[test]
fn bridge_rejects_unknown_capture_dynamic_range_raw_values() {
let mut config = SCStreamConfiguration::new();
config
.set_capture_dynamic_range(SCCaptureDynamicRange::HDRCanonicalDisplay)
.expect("macOS 15.0 or later");
for raw in [3, -1, i32::MAX, i32::MIN] {
let applied = unsafe {
crate::ffi::sc_stream_configuration_set_capture_dynamic_range(config.as_ptr(), raw)
};
assert!(!applied, "the bridge accepted raw value {raw}");
assert_eq!(
config.capture_dynamic_range(),
Ok(SCCaptureDynamicRange::HDRCanonicalDisplay)
);
}
}
}