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        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        Self::from_raw(ptr).expect("CFNotificationCenterGetDistributedCenter returned NULL")
82    }
83
84    /// Darwin notification center.
85    #[must_use]
86    pub fn darwin() -> Self {
87        let ptr = unsafe { ffi::cf_notification_center_get_darwin() };
88        Self::from_raw(ptr).expect("CFNotificationCenterGetDarwinNotifyCenter returned NULL")
89    }
90
91    /// Post a notification with an optional user-info dictionary.
92    pub fn post(
93        &self,
94        name: &CFString,
95        user_info: Option<&CFDictionary>,
96        deliver_immediately: bool,
97    ) {
98        unsafe {
99            ffi::cf_notification_center_post_notification(
100                self.as_ptr(),
101                name.as_ptr(),
102                user_info.map_or(std::ptr::null_mut(), CFDictionary::as_ptr),
103                deliver_immediately,
104            );
105        }
106    }
107}
108
109impl CFRunLoop {
110    /// Current thread's run loop.
111    #[must_use]
112    pub fn current() -> Self {
113        let ptr = unsafe { ffi::cf_run_loop_get_current() };
114        Self::from_raw(ptr).expect("CFRunLoopGetCurrent returned NULL")
115    }
116
117    /// Main thread run loop.
118    #[must_use]
119    pub fn main() -> Self {
120        let ptr = unsafe { ffi::cf_run_loop_get_main() };
121        Self::from_raw(ptr).expect("CFRunLoopGetMain returned NULL")
122    }
123
124    /// Run the current thread's run loop in the default mode for `duration`.
125    #[must_use]
126    pub fn run_in_default_mode(
127        &self,
128        duration: Duration,
129        return_after_source_handled: bool,
130    ) -> CFRunLoopRunResult {
131        let code = unsafe {
132            ffi::cf_run_loop_run_in_default_mode(
133                duration_to_seconds(duration),
134                return_after_source_handled,
135            )
136        };
137        match code {
138            1 => CFRunLoopRunResult::Finished,
139            2 => CFRunLoopRunResult::Stopped,
140            4 => CFRunLoopRunResult::HandledSource,
141            _ => CFRunLoopRunResult::TimedOut,
142        }
143    }
144
145    /// Wake the run loop.
146    pub fn wake_up(&self) {
147        unsafe { ffi::cf_run_loop_wake_up(self.as_ptr()) };
148    }
149
150    /// Stop the run loop.
151    pub fn stop(&self) {
152        unsafe { ffi::cf_run_loop_stop(self.as_ptr()) };
153    }
154
155    /// Add a timer to the run loop in the default mode.
156    pub fn add_timer(&self, timer: &CFTimer) {
157        unsafe { ffi::cf_run_loop_add_timer(self.as_ptr(), timer.as_ptr()) };
158    }
159}
160
161impl CFTimer {
162    /// Create a run-loop timer with a no-op callback.
163    #[must_use]
164    pub fn new(interval: Duration, repeats: bool) -> Self {
165        let ptr = unsafe { ffi::cf_run_loop_timer_create(duration_to_seconds(interval), repeats) };
166        Self::from_raw(ptr).expect("CFRunLoopTimerCreate returned NULL")
167    }
168
169    /// Whether the timer is still valid.
170    #[must_use]
171    pub fn is_valid(&self) -> bool {
172        unsafe { ffi::cf_run_loop_timer_is_valid(self.as_ptr()) }
173    }
174
175    /// Fire the timer immediately.
176    pub fn fire(&self) {
177        unsafe { ffi::cf_run_loop_timer_fire(self.as_ptr()) };
178    }
179
180    /// Invalidate the timer.
181    pub fn invalidate(&self) {
182        unsafe { ffi::cf_run_loop_timer_invalidate(self.as_ptr()) };
183    }
184}
185
186impl CFMessagePort {
187    /// Create a local message port that echoes request data back as the reply.
188    #[must_use]
189    pub fn create_echo_local(name: &str) -> Self {
190        let name =
191            std::ffi::CString::new(name).expect("message-port name may not contain NUL bytes");
192        let ptr = unsafe { ffi::cf_message_port_create_echo_local(name.as_ptr()) };
193        Self::from_raw(ptr).expect("CFMessagePortCreateLocal returned NULL")
194    }
195
196    /// Connect to an existing remote message port.
197    #[must_use]
198    pub fn connect_remote(name: &str) -> Option<Self> {
199        let name =
200            std::ffi::CString::new(name).expect("message-port name may not contain NUL bytes");
201        let ptr = unsafe { ffi::cf_message_port_create_remote(name.as_ptr()) };
202        Self::from_raw(ptr)
203    }
204
205    /// Send a request and copy the reply bytes.
206    pub fn send_request(&self, bytes: &[u8], timeout: Duration) -> Result<Vec<u8>, i32> {
207        let mut out_bytes = std::ptr::null_mut();
208        let mut out_len = 0_usize;
209        let status = unsafe {
210            ffi::cf_message_port_send_request(
211                self.as_ptr(),
212                bytes.as_ptr(),
213                bytes.len(),
214                duration_to_seconds(timeout),
215                &mut out_bytes,
216                &mut out_len,
217            )
218        };
219        if status != 0 {
220            return Err(status);
221        }
222        if out_bytes.is_null() {
223            return Ok(Vec::new());
224        }
225        let reply = unsafe { std::slice::from_raw_parts(out_bytes, out_len) }.to_vec();
226        unsafe { ffi::cf_message_port_free_bytes(out_bytes, out_len) };
227        Ok(reply)
228    }
229
230    /// Invalidate the message port.
231    pub fn invalidate(&self) {
232        unsafe { ffi::cf_message_port_invalidate(self.as_ptr()) };
233    }
234}
235
236/// Paired Core Foundation streams backed by a shared in-memory buffer.
237#[derive(Debug, Clone)]
238pub struct CFStreamPair {
239    pub read: CFReadStream,
240    pub write: CFWriteStream,
241}
242
243impl CFStreamPair {
244    /// Create a bound read/write stream pair.
245    #[must_use]
246    pub fn new(transfer_buffer_size: usize) -> Self {
247        let mut read = std::ptr::null_mut();
248        let mut write = std::ptr::null_mut();
249        unsafe { ffi::cf_stream_create_bound_pair(transfer_buffer_size, &mut read, &mut write) };
250        Self {
251            read: CFReadStream::from_raw(read)
252                .expect("CFStreamCreateBoundPair read stream was NULL"),
253            write: CFWriteStream::from_raw(write)
254                .expect("CFStreamCreateBoundPair write stream was NULL"),
255        }
256    }
257}
258
259impl CFReadStream {
260    /// Open the stream.
261    #[must_use]
262    pub fn open(&self) -> bool {
263        unsafe { ffi::cf_read_stream_open(self.as_ptr()) }
264    }
265
266    /// Close the stream.
267    pub fn close(&self) {
268        unsafe { ffi::cf_read_stream_close(self.as_ptr()) };
269    }
270
271    /// Read bytes into `buffer`.
272    pub fn read(&self, buffer: &mut [u8]) -> Result<usize, isize> {
273        let count =
274            unsafe { ffi::cf_read_stream_read(self.as_ptr(), buffer.as_mut_ptr(), buffer.len()) };
275        if count < 0 {
276            Err(count)
277        } else {
278            Ok(usize::try_from(count).expect("non-negative read count fits in usize"))
279        }
280    }
281}
282
283impl CFWriteStream {
284    /// Open the stream.
285    #[must_use]
286    pub fn open(&self) -> bool {
287        unsafe { ffi::cf_write_stream_open(self.as_ptr()) }
288    }
289
290    /// Close the stream.
291    pub fn close(&self) {
292        unsafe { ffi::cf_write_stream_close(self.as_ptr()) };
293    }
294
295    /// Write bytes from `buffer`.
296    pub fn write(&self, buffer: &[u8]) -> Result<usize, isize> {
297        let count =
298            unsafe { ffi::cf_write_stream_write(self.as_ptr(), buffer.as_ptr(), buffer.len()) };
299        if count < 0 {
300            Err(count)
301        } else {
302            Ok(usize::try_from(count).expect("non-negative write count fits in usize"))
303        }
304    }
305}
306
307impl CFSocket {
308    /// Create a UDP/IPv4 socket wrapper with no callbacks attached.
309    #[must_use]
310    pub fn udp_ipv4() -> Option<Self> {
311        let ptr = unsafe { ffi::cf_socket_create_udp_ipv4() };
312        Self::from_raw(ptr)
313    }
314
315    /// Native file descriptor.
316    #[must_use]
317    pub fn native(&self) -> i32 {
318        unsafe { ffi::cf_socket_get_native(self.as_ptr()) }
319    }
320
321    /// Whether the socket is valid.
322    #[must_use]
323    pub fn is_valid(&self) -> bool {
324        unsafe { ffi::cf_socket_is_valid(self.as_ptr()) }
325    }
326
327    /// Invalidate the socket.
328    pub fn invalidate(&self) {
329        unsafe { ffi::cf_socket_invalidate(self.as_ptr()) };
330    }
331}
332
333impl CFFileDescriptor {
334    /// Wrap a native file descriptor with a no-op callback.
335    #[must_use]
336    pub fn from_raw_fd(native_fd: i32, close_on_invalidate: bool) -> Option<Self> {
337        let ptr = unsafe { ffi::cf_file_descriptor_create(native_fd, close_on_invalidate) };
338        Self::from_raw(ptr)
339    }
340
341    /// Underlying native file descriptor.
342    #[must_use]
343    pub fn native_descriptor(&self) -> i32 {
344        unsafe { ffi::cf_file_descriptor_get_native(self.as_ptr()) }
345    }
346
347    /// Invalidate the descriptor.
348    pub fn invalidate(&self) {
349        unsafe { ffi::cf_file_descriptor_invalidate(self.as_ptr()) };
350    }
351}