Skip to main content

apple_cf/cf/
runtime.rs

1//! Core Foundation runtime / event-loop / stream wrappers.
2//!
3#![allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
4
5//! ```rust,no_run
6//! use apple_cf::cf::{
7//!     CFFileDescriptor, CFMessagePort, CFNotificationCenter, CFReadStream, CFRunLoop,
8//!     CFRunLoopRunResult, CFSocket, CFString, CFStreamPair, CFTimer, CFWriteStream,
9//! };
10//! use std::time::Duration;
11//!
12//! let center = CFNotificationCenter::local();
13//! center.post(&CFString::new("com.doomfish.apple-cf.example"), None, false);
14//!
15//! let timer = CFTimer::new(Duration::from_millis(10), false);
16//! let run_loop = CFRunLoop::current();
17//! run_loop.add_timer(&timer);
18//! let result = run_loop.run_in_default_mode(Duration::from_millis(20), true);
19//! assert!(matches!(result, CFRunLoopRunResult::HandledSource | CFRunLoopRunResult::TimedOut));
20//!
21//! let local = CFMessagePort::create_echo_local("com.doomfish.apple-cf.echo");
22//! let remote = CFMessagePort::connect_remote("com.doomfish.apple-cf.echo").unwrap();
23//! let reply = remote.send_request(b"ping", Duration::from_millis(100)).unwrap();
24//! assert_eq!(reply, b"ping");
25//!
26//! let pair = CFStreamPair::new(1024);
27//! assert!(pair.read.open());
28//! assert!(pair.write.open());
29//! assert_eq!(pair.write.write(b"ok").unwrap(), 2);
30//! let mut buffer = [0_u8; 2];
31//! assert_eq!(pair.read.read(&mut buffer).unwrap(), 2);
32//! assert_eq!(&buffer, b"ok");
33//!
34//! let socket = CFSocket::udp_ipv4().unwrap();
35//! assert!(socket.is_valid());
36//!
37//! let fd = CFFileDescriptor::from_raw_fd(0, false).unwrap();
38//! assert_eq!(fd.native_descriptor(), 0);
39//! ```
40
41use super::base::impl_cf_type_wrapper;
42use super::{CFDictionary, CFString};
43use crate::ffi;
44use std::time::Duration;
45
46impl_cf_type_wrapper!(CFNotificationCenter, cf_notification_center_get_type_id);
47impl_cf_type_wrapper!(CFRunLoop, cf_run_loop_get_type_id);
48impl_cf_type_wrapper!(CFTimer, cf_run_loop_timer_get_type_id);
49impl_cf_type_wrapper!(CFMessagePort, cf_message_port_get_type_id);
50impl_cf_type_wrapper!(CFReadStream, cf_read_stream_get_type_id);
51impl_cf_type_wrapper!(CFWriteStream, cf_write_stream_get_type_id);
52impl_cf_type_wrapper!(CFSocket, cf_socket_get_type_id);
53impl_cf_type_wrapper!(CFFileDescriptor, cf_file_descriptor_get_type_id);
54
55fn duration_to_seconds(duration: Duration) -> f64 {
56    duration.as_secs_f64()
57}
58
59/// Result codes from `CFRunLoopRunInMode`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61#[repr(i32)]
62pub enum CFRunLoopRunResult {
63    Finished = 1,
64    Stopped = 2,
65    TimedOut = 3,
66    HandledSource = 4,
67}
68
69impl CFNotificationCenter {
70    /// Local notification center.
71    #[must_use]
72    pub fn local() -> Self {
73        let ptr = unsafe { ffi::cf_notification_center_get_local() };
74        unsafe { Self::from_raw(ptr) }.expect("CFNotificationCenterGetLocalCenter returned NULL")
75    }
76
77    /// Distributed notification center.
78    #[must_use]
79    pub fn distributed() -> Self {
80        let ptr = unsafe { ffi::cf_notification_center_get_distributed() };
81        unsafe { Self::from_raw(ptr) }
82            .expect("CFNotificationCenterGetDistributedCenter returned NULL")
83    }
84
85    /// Darwin notification center.
86    #[must_use]
87    pub fn darwin() -> Self {
88        let ptr = unsafe { ffi::cf_notification_center_get_darwin() };
89        unsafe { Self::from_raw(ptr) }
90            .expect("CFNotificationCenterGetDarwinNotifyCenter returned NULL")
91    }
92
93    /// Post a notification with an optional user-info dictionary.
94    pub fn post(
95        &self,
96        name: &CFString,
97        user_info: Option<&CFDictionary>,
98        deliver_immediately: bool,
99    ) {
100        unsafe {
101            ffi::cf_notification_center_post_notification(
102                self.as_ptr(),
103                name.as_ptr(),
104                user_info.map_or(std::ptr::null_mut(), CFDictionary::as_ptr),
105                deliver_immediately,
106            );
107        }
108    }
109}
110
111impl CFRunLoop {
112    /// Current thread's run loop.
113    #[must_use]
114    pub fn current() -> Self {
115        let ptr = unsafe { ffi::cf_run_loop_get_current() };
116        unsafe { Self::from_raw(ptr) }.expect("CFRunLoopGetCurrent returned NULL")
117    }
118
119    /// Main thread run loop.
120    #[must_use]
121    pub fn main() -> Self {
122        let ptr = unsafe { ffi::cf_run_loop_get_main() };
123        unsafe { Self::from_raw(ptr) }.expect("CFRunLoopGetMain returned NULL")
124    }
125
126    /// Run the current thread's run loop in the default mode for `duration`.
127    #[must_use]
128    pub fn run_in_default_mode(
129        &self,
130        duration: Duration,
131        return_after_source_handled: bool,
132    ) -> CFRunLoopRunResult {
133        let code = unsafe {
134            ffi::cf_run_loop_run_in_default_mode(
135                duration_to_seconds(duration),
136                return_after_source_handled,
137            )
138        };
139        match code {
140            1 => CFRunLoopRunResult::Finished,
141            2 => CFRunLoopRunResult::Stopped,
142            4 => CFRunLoopRunResult::HandledSource,
143            _ => CFRunLoopRunResult::TimedOut,
144        }
145    }
146
147    /// Wake the run loop.
148    pub fn wake_up(&self) {
149        unsafe { ffi::cf_run_loop_wake_up(self.as_ptr()) };
150    }
151
152    /// Stop the run loop.
153    pub fn stop(&self) {
154        unsafe { ffi::cf_run_loop_stop(self.as_ptr()) };
155    }
156
157    /// Add a timer to the run loop in the default mode.
158    pub fn add_timer(&self, timer: &CFTimer) {
159        unsafe { ffi::cf_run_loop_add_timer(self.as_ptr(), timer.as_ptr()) };
160    }
161}
162
163impl CFTimer {
164    /// Create a run-loop timer with a no-op callback.
165    #[must_use]
166    pub fn new(interval: Duration, repeats: bool) -> Self {
167        let ptr = unsafe { ffi::cf_run_loop_timer_create(duration_to_seconds(interval), repeats) };
168        unsafe { Self::from_raw(ptr) }.expect("CFRunLoopTimerCreate returned NULL")
169    }
170
171    /// Whether the timer is still valid.
172    #[must_use]
173    pub fn is_valid(&self) -> bool {
174        unsafe { ffi::cf_run_loop_timer_is_valid(self.as_ptr()) }
175    }
176
177    /// Fire the timer immediately.
178    pub fn fire(&self) {
179        unsafe { ffi::cf_run_loop_timer_fire(self.as_ptr()) };
180    }
181
182    /// Invalidate the timer.
183    pub fn invalidate(&self) {
184        unsafe { ffi::cf_run_loop_timer_invalidate(self.as_ptr()) };
185    }
186}
187
188impl CFMessagePort {
189    /// Create a local message port that echoes request data back as the reply.
190    #[must_use]
191    pub fn create_echo_local(name: &str) -> Self {
192        let name =
193            std::ffi::CString::new(name).expect("message-port name may not contain NUL bytes");
194        let ptr = unsafe { ffi::cf_message_port_create_echo_local(name.as_ptr()) };
195        unsafe { Self::from_raw(ptr) }.expect("CFMessagePortCreateLocal returned NULL")
196    }
197
198    /// Connect to an existing remote message port.
199    #[must_use]
200    pub fn connect_remote(name: &str) -> Option<Self> {
201        let name =
202            std::ffi::CString::new(name).expect("message-port name may not contain NUL bytes");
203        let ptr = unsafe { ffi::cf_message_port_create_remote(name.as_ptr()) };
204        unsafe { Self::from_raw(ptr) }
205    }
206
207    /// Send a request and copy the reply bytes.
208    pub fn send_request(&self, bytes: &[u8], timeout: Duration) -> Result<Vec<u8>, i32> {
209        let mut out_bytes = std::ptr::null_mut();
210        let mut out_len = 0_usize;
211        let status = unsafe {
212            ffi::cf_message_port_send_request(
213                self.as_ptr(),
214                bytes.as_ptr(),
215                bytes.len(),
216                duration_to_seconds(timeout),
217                &mut out_bytes,
218                &mut out_len,
219            )
220        };
221        if status != 0 {
222            return Err(status);
223        }
224        if out_bytes.is_null() {
225            return Ok(Vec::new());
226        }
227        let reply = unsafe { std::slice::from_raw_parts(out_bytes, out_len) }.to_vec();
228        unsafe { ffi::cf_message_port_free_bytes(out_bytes, out_len) };
229        Ok(reply)
230    }
231
232    /// Invalidate the message port.
233    pub fn invalidate(&self) {
234        unsafe { ffi::cf_message_port_invalidate(self.as_ptr()) };
235    }
236}
237
238/// Paired Core Foundation streams backed by a shared in-memory buffer.
239#[derive(Debug, Clone)]
240pub struct CFStreamPair {
241    pub read: CFReadStream,
242    pub write: CFWriteStream,
243}
244
245impl CFStreamPair {
246    /// Create a bound read/write stream pair.
247    #[must_use]
248    pub fn new(transfer_buffer_size: usize) -> Self {
249        let mut read = std::ptr::null_mut();
250        let mut write = std::ptr::null_mut();
251        unsafe { ffi::cf_stream_create_bound_pair(transfer_buffer_size, &mut read, &mut write) };
252        Self {
253            read: unsafe { CFReadStream::from_raw(read) }
254                .expect("CFStreamCreateBoundPair read stream was NULL"),
255            write: unsafe { CFWriteStream::from_raw(write) }
256                .expect("CFStreamCreateBoundPair write stream was NULL"),
257        }
258    }
259}
260
261impl CFReadStream {
262    /// Open the stream.
263    #[must_use]
264    pub fn open(&self) -> bool {
265        unsafe { ffi::cf_read_stream_open(self.as_ptr()) }
266    }
267
268    /// Close the stream.
269    pub fn close(&self) {
270        unsafe { ffi::cf_read_stream_close(self.as_ptr()) };
271    }
272
273    /// Read bytes into `buffer`.
274    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, isize> {
275        let count =
276            unsafe { ffi::cf_read_stream_read(self.as_ptr(), buffer.as_mut_ptr(), buffer.len()) };
277        if count < 0 {
278            Err(count)
279        } else {
280            Ok(usize::try_from(count).expect("non-negative read count fits in usize"))
281        }
282    }
283}
284
285impl CFWriteStream {
286    /// Open the stream.
287    #[must_use]
288    pub fn open(&self) -> bool {
289        unsafe { ffi::cf_write_stream_open(self.as_ptr()) }
290    }
291
292    /// Close the stream.
293    pub fn close(&self) {
294        unsafe { ffi::cf_write_stream_close(self.as_ptr()) };
295    }
296
297    /// Write bytes from `buffer`.
298    pub fn write(&self, buffer: &[u8]) -> Result<usize, isize> {
299        let count =
300            unsafe { ffi::cf_write_stream_write(self.as_ptr(), buffer.as_ptr(), buffer.len()) };
301        if count < 0 {
302            Err(count)
303        } else {
304            Ok(usize::try_from(count).expect("non-negative write count fits in usize"))
305        }
306    }
307}
308
309impl CFSocket {
310    /// Create a UDP/IPv4 socket wrapper with no callbacks attached.
311    #[must_use]
312    pub fn udp_ipv4() -> Option<Self> {
313        let ptr = unsafe { ffi::cf_socket_create_udp_ipv4() };
314        unsafe { Self::from_raw(ptr) }
315    }
316
317    /// Native file descriptor.
318    #[must_use]
319    pub fn native(&self) -> i32 {
320        unsafe { ffi::cf_socket_get_native(self.as_ptr()) }
321    }
322
323    /// Whether the socket is valid.
324    #[must_use]
325    pub fn is_valid(&self) -> bool {
326        unsafe { ffi::cf_socket_is_valid(self.as_ptr()) }
327    }
328
329    /// Invalidate the socket.
330    pub fn invalidate(&self) {
331        unsafe { ffi::cf_socket_invalidate(self.as_ptr()) };
332    }
333}
334
335impl CFFileDescriptor {
336    /// Wrap a native file descriptor with a no-op callback.
337    #[must_use]
338    pub fn from_raw_fd(native_fd: i32, close_on_invalidate: bool) -> Option<Self> {
339        let ptr = unsafe { ffi::cf_file_descriptor_create(native_fd, close_on_invalidate) };
340        unsafe { Self::from_raw(ptr) }
341    }
342
343    /// Underlying native file descriptor.
344    #[must_use]
345    pub fn native_descriptor(&self) -> i32 {
346        unsafe { ffi::cf_file_descriptor_get_native(self.as_ptr()) }
347    }
348
349    /// Invalidate the descriptor.
350    pub fn invalidate(&self) {
351        unsafe { ffi::cf_file_descriptor_invalidate(self.as_ptr()) };
352    }
353}