Skip to main content

apple_cf/
dispatch_queue.rs

1//! Dispatch Queue wrapper for custom queue management
2//!
3//! This module provides a safe Rust wrapper around GCD (Grand Central Dispatch) queues
4//! that can be used with `ScreenCaptureKit` streams.
5//!
6//! ## When to Use Custom Queues
7//!
8//! By default, stream output handlers are called on a system-managed queue. Use a custom
9//! queue when you need:
10//!
11//! - **Priority control** - Use `UserInteractive` `QoS` for low-latency UI updates
12//! - **Thread isolation** - Ensure handlers run on a specific queue
13//! - **Performance tuning** - Adjust queue priority based on your app's needs
14//!
15//! ## Example
16//!
17#![allow(clippy::missing_panics_doc)]
18
19//! ```rust,no_run
20//! use apple_cf::dispatch_queue::{DispatchQueue, DispatchQoS};
21//!
22//! // Create a high-priority queue for frame processing
23//! let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
24//! // Pass `queue.as_ptr()` into any framework that accepts a `dispatch_queue_t`.
25//! ```
26
27use std::ffi::{c_void, CString};
28use std::fmt;
29use std::time::Duration;
30
31/// Quality of Service levels for dispatch queues
32///
33/// These `QoS` levels help the system prioritize work appropriately.
34///
35/// # Examples
36///
37/// ```
38/// use apple_cf::dispatch_queue::{DispatchQueue, DispatchQoS};
39///
40/// // High priority for UI-affecting work
41/// let queue = DispatchQueue::new("com.myapp.ui", DispatchQoS::UserInteractive);
42///
43/// // Lower priority for background tasks
44/// let bg_queue = DispatchQueue::new("com.myapp.background", DispatchQoS::Background);
45/// ```
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
47pub enum DispatchQoS {
48    /// Background `QoS` - for maintenance or cleanup tasks
49    Background = 0,
50    /// Utility `QoS` - for tasks that may take some time
51    Utility = 1,
52    /// Default `QoS` - standard priority
53    #[default]
54    Default = 2,
55    /// User Initiated `QoS` - for tasks initiated by the user
56    UserInitiated = 3,
57    /// User Interactive `QoS` - for tasks that affect the UI
58    UserInteractive = 4,
59}
60
61/// A wrapper around GCD `DispatchQueue`
62///
63/// This allows you to provide a custom dispatch queue for stream output handling
64/// instead of using the default queue.
65///
66/// # Example
67///
68/// ```no_run
69/// use apple_cf::dispatch_queue::{DispatchQueue, DispatchQoS};
70///
71/// let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
72/// ```
73pub struct DispatchQueue {
74    ptr: *const c_void,
75}
76
77unsafe impl Send for DispatchQueue {}
78unsafe impl Sync for DispatchQueue {}
79
80impl DispatchQueue {
81    /// Creates a new dispatch queue with the specified label and `QoS`
82    ///
83    /// # Arguments
84    ///
85    /// * `label` - A string label for the queue (e.g., "com.myapp.capture")
86    /// * `qos` - The quality of service level for the queue
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use apple_cf::dispatch_queue::{DispatchQueue, DispatchQoS};
92    ///
93    /// let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
94    /// // Use the queue with SCStream's add_output_handler_with_queue
95    /// ```
96    ///
97    /// # Panics
98    ///
99    /// Panics if the label contains null bytes or if queue creation fails
100    #[must_use]
101    pub fn new(label: &str, qos: DispatchQoS) -> Self {
102        let c_label = CString::new(label).expect("Label contains null byte");
103        let ptr = unsafe { crate::ffi::dispatch_queue_create(c_label.as_ptr(), qos as i32) };
104        assert!(!ptr.is_null(), "Failed to create dispatch queue");
105        Self { ptr }
106    }
107
108    /// Returns the raw pointer to the dispatch queue
109    ///
110    /// This is used internally for FFI calls (and for testing)
111    #[must_use]
112    pub const fn as_ptr(&self) -> *const c_void {
113        self.ptr
114    }
115}
116
117impl Clone for DispatchQueue {
118    fn clone(&self) -> Self {
119        unsafe {
120            Self {
121                ptr: crate::ffi::dispatch_queue_retain(self.ptr),
122            }
123        }
124    }
125}
126
127impl Drop for DispatchQueue {
128    fn drop(&mut self) {
129        unsafe {
130            crate::ffi::dispatch_queue_release(self.ptr);
131        }
132    }
133}
134
135impl fmt::Debug for DispatchQueue {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        f.debug_struct("DispatchQueue")
138            .field("ptr", &self.ptr)
139            .finish()
140    }
141}
142
143impl fmt::Display for DispatchQueue {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        write!(f, "DispatchQueue")
146    }
147}
148
149fn timeout_ms(timeout: Option<Duration>) -> i64 {
150    timeout.map_or(-1, |duration| {
151        i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
152    })
153}
154
155/// Wrapper around `DispatchGroup`.
156#[derive(PartialEq, Eq, Hash)]
157pub struct DispatchGroup {
158    ptr: *mut c_void,
159}
160
161impl DispatchGroup {
162    /// Create a new empty group.
163    #[must_use]
164    pub fn new() -> Self {
165        let ptr = unsafe { crate::ffi::acf_dispatch_group_create() };
166        assert!(!ptr.is_null(), "failed to create DispatchGroup");
167        Self { ptr }
168    }
169
170    /// Enter the group.
171    pub fn enter(&self) {
172        unsafe { crate::ffi::acf_dispatch_group_enter(self.ptr) };
173    }
174
175    /// Leave the group.
176    pub fn leave(&self) {
177        unsafe { crate::ffi::acf_dispatch_group_leave(self.ptr) };
178    }
179
180    /// Wait for the group to finish.
181    #[must_use]
182    pub fn wait(&self, timeout: Option<Duration>) -> bool {
183        unsafe { crate::ffi::acf_dispatch_group_wait(self.ptr, timeout_ms(timeout)) }
184    }
185}
186
187impl Default for DispatchGroup {
188    fn default() -> Self {
189        Self::new()
190    }
191}
192
193impl Clone for DispatchGroup {
194    fn clone(&self) -> Self {
195        Self {
196            ptr: unsafe { crate::ffi::acf_object_retain(self.ptr) },
197        }
198    }
199}
200
201impl Drop for DispatchGroup {
202    fn drop(&mut self) {
203        unsafe { crate::ffi::acf_object_release(self.ptr) };
204    }
205}
206
207impl fmt::Debug for DispatchGroup {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_struct("DispatchGroup")
210            .field("ptr", &self.ptr)
211            .finish()
212    }
213}
214
215/// Wrapper around `DispatchSemaphore`.
216#[derive(PartialEq, Eq, Hash)]
217pub struct DispatchSemaphore {
218    ptr: *mut c_void,
219}
220
221impl DispatchSemaphore {
222    /// Create a semaphore with an initial signal count.
223    #[must_use]
224    pub fn new(value: i64) -> Self {
225        let ptr = unsafe { crate::ffi::acf_dispatch_semaphore_create(value) };
226        assert!(!ptr.is_null(), "failed to create DispatchSemaphore");
227        Self { ptr }
228    }
229
230    /// Signal the semaphore.
231    #[must_use]
232    pub fn signal(&self) -> i64 {
233        unsafe { crate::ffi::acf_dispatch_semaphore_signal(self.ptr) }
234    }
235
236    /// Wait for the semaphore.
237    #[must_use]
238    pub fn wait(&self, timeout: Option<Duration>) -> bool {
239        unsafe { crate::ffi::acf_dispatch_semaphore_wait(self.ptr, timeout_ms(timeout)) }
240    }
241}
242
243impl Clone for DispatchSemaphore {
244    fn clone(&self) -> Self {
245        Self {
246            ptr: unsafe { crate::ffi::acf_object_retain(self.ptr) },
247        }
248    }
249}
250
251impl Drop for DispatchSemaphore {
252    fn drop(&mut self) {
253        unsafe { crate::ffi::acf_object_release(self.ptr) };
254    }
255}
256
257impl fmt::Debug for DispatchSemaphore {
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        f.debug_struct("DispatchSemaphore")
260            .field("ptr", &self.ptr)
261            .finish()
262    }
263}
264
265/// Minimal timer-backed `DispatchSource` wrapper.
266#[derive(PartialEq, Eq, Hash)]
267pub struct DispatchSource {
268    ptr: *mut c_void,
269}
270
271impl DispatchSource {
272    /// Create a repeating timer source.
273    #[must_use]
274    pub fn timer(interval: Duration, leeway: Duration) -> Self {
275        let interval_ms = u64::try_from(interval.as_millis()).unwrap_or(u64::MAX);
276        let leeway_ms = u64::try_from(leeway.as_millis()).unwrap_or(u64::MAX);
277        let ptr = unsafe { crate::ffi::acf_dispatch_source_timer_create(interval_ms, leeway_ms) };
278        assert!(!ptr.is_null(), "failed to create DispatchSource timer");
279        Self { ptr }
280    }
281
282    /// Resume the timer source after creation.
283    pub fn resume(&self) {
284        unsafe { crate::ffi::acf_dispatch_source_timer_resume(self.ptr) };
285    }
286
287    /// Cancel the source.
288    pub fn cancel(&self) {
289        unsafe { crate::ffi::acf_dispatch_source_timer_cancel(self.ptr) };
290    }
291
292    /// Number of timer firings observed by the bridge.
293    #[must_use]
294    pub fn fire_count(&self) -> u64 {
295        unsafe { crate::ffi::acf_dispatch_source_timer_fire_count(self.ptr) }
296    }
297}
298
299impl Clone for DispatchSource {
300    fn clone(&self) -> Self {
301        Self {
302            ptr: unsafe { crate::ffi::acf_object_retain(self.ptr) },
303        }
304    }
305}
306
307impl Drop for DispatchSource {
308    fn drop(&mut self) {
309        unsafe { crate::ffi::acf_object_release(self.ptr) };
310    }
311}
312
313impl fmt::Debug for DispatchSource {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        f.debug_struct("DispatchSource")
316            .field("ptr", &self.ptr)
317            .field("fire_count", &self.fire_count())
318            .finish()
319    }
320}