1#![allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
4
5use super::base::impl_cf_type_wrapper;
43use super::{CFDictionary, CFString};
44use crate::ffi;
45use crate::utils::panic_safe;
46use std::collections::BTreeMap;
47use std::ffi::{c_void, CString};
48use std::os::fd::{AsRawFd, BorrowedFd, IntoRawFd, OwnedFd};
49use std::sync::atomic::{AtomicUsize, Ordering};
50use std::sync::{Arc, Mutex, PoisonError};
51use std::time::Duration;
52
53impl_cf_type_wrapper!(CFNotificationCenter, cf_notification_center_get_type_id);
54impl_cf_type_wrapper!(CFRunLoop, cf_run_loop_get_type_id);
55#[allow(clippy::non_send_fields_in_send_ty)]
56unsafe impl Send for CFRunLoop {}
57unsafe impl Sync for CFRunLoop {}
58impl_cf_type_wrapper!(CFTimer, cf_run_loop_timer_get_type_id);
59impl_cf_type_wrapper!(CFMessagePort, cf_message_port_get_type_id);
60impl_cf_type_wrapper!(CFReadStream, cf_read_stream_get_type_id);
61impl_cf_type_wrapper!(CFWriteStream, cf_write_stream_get_type_id);
62impl_cf_type_wrapper!(CFSocket, cf_socket_get_type_id);
63impl_cf_type_wrapper!(CFFileDescriptor, cf_file_descriptor_get_type_id);
64
65type NotificationCallback = dyn Fn(&CFString, Option<&CFDictionary>) + Send + Sync;
66
67static NOTIFICATION_OBSERVERS: Mutex<BTreeMap<usize, Arc<NotificationCallback>>> =
68 Mutex::new(BTreeMap::new());
69static NEXT_NOTIFICATION_OBSERVER: AtomicUsize = AtomicUsize::new(1);
70const CF_NOTIFICATION_SUSPENSION_BEHAVIOR_DELIVER_IMMEDIATELY: isize = 4;
71
72extern "C" {
73 fn CFNotificationCenterAddObserver(
74 center: *mut c_void,
75 observer: *const c_void,
76 callback: extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *const c_void, *mut c_void),
77 name: *mut c_void,
78 object: *const c_void,
79 suspension_behavior: isize,
80 );
81 fn CFNotificationCenterRemoveEveryObserver(center: *mut c_void, observer: *const c_void);
82}
83
84extern "C" fn notification_observer_trampoline(
85 _center: *mut c_void,
86 observer: *mut c_void,
87 name: *mut c_void,
88 _object: *const c_void,
89 user_info: *mut c_void,
90) {
91 panic_safe::catch_user_panic("CFNotificationCenter observer", || {
92 let callback = NOTIFICATION_OBSERVERS
93 .lock()
94 .unwrap_or_else(PoisonError::into_inner)
95 .get(&(observer as usize))
96 .cloned();
97 let Some(callback) = callback else {
98 return;
99 };
100 let Some(name) = (unsafe { CFString::from_raw_borrowed(name) }) else {
101 return;
102 };
103 let user_info = unsafe { CFDictionary::from_raw_borrowed(user_info) };
104 callback(&name, user_info.as_ref());
105 });
106}
107
108pub struct CFNotificationObserver {
109 center: CFNotificationCenter,
110 token: usize,
111}
112
113impl Drop for CFNotificationObserver {
114 fn drop(&mut self) {
115 unsafe {
116 CFNotificationCenterRemoveEveryObserver(
117 self.center.as_ptr(),
118 self.token as *const c_void,
119 );
120 }
121 NOTIFICATION_OBSERVERS
122 .lock()
123 .unwrap_or_else(PoisonError::into_inner)
124 .remove(&self.token);
125 }
126}
127
128impl std::fmt::Debug for CFNotificationObserver {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 f.debug_struct("CFNotificationObserver")
131 .field("center", &self.center.as_ptr())
132 .field("token", &self.token)
133 .finish()
134 }
135}
136
137fn duration_to_seconds(duration: Duration) -> f64 {
138 duration.as_secs_f64()
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
143#[repr(i32)]
144pub enum CFRunLoopRunResult {
145 Finished = 1,
146 Stopped = 2,
147 TimedOut = 3,
148 HandledSource = 4,
149}
150
151impl CFNotificationCenter {
152 #[must_use]
154 pub fn local() -> Self {
155 let ptr = unsafe { ffi::cf_notification_center_get_local() };
156 unsafe { Self::from_raw(ptr) }.expect("CFNotificationCenterGetLocalCenter returned NULL")
157 }
158
159 #[must_use]
161 pub fn distributed() -> Self {
162 let ptr = unsafe { ffi::cf_notification_center_get_distributed() };
163 unsafe { Self::from_raw(ptr) }
164 .expect("CFNotificationCenterGetDistributedCenter returned NULL")
165 }
166
167 #[must_use]
169 pub fn darwin() -> Self {
170 let ptr = unsafe { ffi::cf_notification_center_get_darwin() };
171 unsafe { Self::from_raw(ptr) }
172 .expect("CFNotificationCenterGetDarwinNotifyCenter returned NULL")
173 }
174
175 #[must_use]
176 pub fn add_observer<F>(&self, name: &CFString, callback: F) -> CFNotificationObserver
177 where
178 F: Fn(&CFString, Option<&CFDictionary>) + Send + Sync + 'static,
179 {
180 let token = NEXT_NOTIFICATION_OBSERVER.fetch_add(1, Ordering::Relaxed);
181 NOTIFICATION_OBSERVERS
182 .lock()
183 .unwrap_or_else(PoisonError::into_inner)
184 .insert(token, Arc::new(callback));
185 unsafe {
186 CFNotificationCenterAddObserver(
187 self.as_ptr(),
188 token as *const c_void,
189 notification_observer_trampoline,
190 name.as_ptr(),
191 std::ptr::null(),
192 CF_NOTIFICATION_SUSPENSION_BEHAVIOR_DELIVER_IMMEDIATELY,
193 );
194 }
195 CFNotificationObserver {
196 center: self.clone(),
197 token,
198 }
199 }
200
201 pub fn post(
203 &self,
204 name: &CFString,
205 user_info: Option<&CFDictionary>,
206 deliver_immediately: bool,
207 ) {
208 unsafe {
209 ffi::cf_notification_center_post_notification(
210 self.as_ptr(),
211 name.as_ptr(),
212 user_info.map_or(std::ptr::null_mut(), CFDictionary::as_ptr),
213 deliver_immediately,
214 );
215 }
216 }
217}
218
219impl CFRunLoop {
220 #[must_use]
222 pub fn current() -> Self {
223 let ptr = unsafe { ffi::cf_run_loop_get_current() };
224 unsafe { Self::from_raw(ptr) }.expect("CFRunLoopGetCurrent returned NULL")
225 }
226
227 #[must_use]
229 pub fn main() -> Self {
230 let ptr = unsafe { ffi::cf_run_loop_get_main() };
231 unsafe { Self::from_raw(ptr) }.expect("CFRunLoopGetMain returned NULL")
232 }
233
234 #[must_use]
236 pub fn run_in_default_mode(
237 duration: Duration,
238 return_after_source_handled: bool,
239 ) -> CFRunLoopRunResult {
240 let code = unsafe {
241 ffi::cf_run_loop_run_in_default_mode(
242 duration_to_seconds(duration),
243 return_after_source_handled,
244 )
245 };
246 match code {
247 1 => CFRunLoopRunResult::Finished,
248 2 => CFRunLoopRunResult::Stopped,
249 4 => CFRunLoopRunResult::HandledSource,
250 _ => CFRunLoopRunResult::TimedOut,
251 }
252 }
253
254 pub fn wake_up(&self) {
256 unsafe { ffi::cf_run_loop_wake_up(self.as_ptr()) };
257 }
258
259 pub fn stop(&self) {
261 unsafe { ffi::cf_run_loop_stop(self.as_ptr()) };
262 }
263
264 pub fn add_timer(&self, timer: &CFTimer) {
266 unsafe { ffi::cf_run_loop_add_timer(self.as_ptr(), timer.as_ptr()) };
267 }
268}
269
270impl CFTimer {
271 #[must_use]
273 pub fn new(interval: Duration, repeats: bool) -> Self {
274 let ptr = unsafe { ffi::cf_run_loop_timer_create(duration_to_seconds(interval), repeats) };
275 unsafe { Self::from_raw(ptr) }.expect("CFRunLoopTimerCreate returned NULL")
276 }
277
278 #[must_use]
280 pub fn is_valid(&self) -> bool {
281 unsafe { ffi::cf_run_loop_timer_is_valid(self.as_ptr()) }
282 }
283
284 pub fn fire(&self) {
286 unsafe { ffi::cf_run_loop_timer_fire(self.as_ptr()) };
287 }
288
289 pub fn invalidate(&self) {
291 unsafe { ffi::cf_run_loop_timer_invalidate(self.as_ptr()) };
292 }
293}
294
295impl CFMessagePort {
296 #[must_use]
298 pub fn create_echo_local(name: &str) -> Option<Self> {
299 let name = CString::new(name).ok()?;
300 let ptr = unsafe { ffi::cf_message_port_create_echo_local(name.as_ptr()) };
301 unsafe { Self::from_raw(ptr) }
302 }
303
304 #[must_use]
306 pub fn connect_remote(name: &str) -> Option<Self> {
307 let name = CString::new(name).ok()?;
308 let ptr = unsafe { ffi::cf_message_port_create_remote(name.as_ptr()) };
309 unsafe { Self::from_raw(ptr) }
310 }
311
312 pub fn send_request(&self, bytes: &[u8], timeout: Duration) -> Result<Vec<u8>, i32> {
314 let mut out_bytes = std::ptr::null_mut();
315 let mut out_len = 0_usize;
316 let status = unsafe {
317 ffi::cf_message_port_send_request(
318 self.as_ptr(),
319 bytes.as_ptr(),
320 bytes.len(),
321 duration_to_seconds(timeout),
322 &raw mut out_bytes,
323 &raw mut out_len,
324 )
325 };
326 if status != 0 {
327 return Err(status);
328 }
329 if out_bytes.is_null() {
330 return Ok(Vec::new());
331 }
332 let reply = unsafe { std::slice::from_raw_parts(out_bytes, out_len) }.to_vec();
333 unsafe { ffi::cf_message_port_free_bytes(out_bytes, out_len) };
334 Ok(reply)
335 }
336
337 pub fn invalidate(&self) {
339 unsafe { ffi::cf_message_port_invalidate(self.as_ptr()) };
340 }
341}
342
343#[derive(Debug, Clone)]
345pub struct CFStreamPair {
346 pub read: CFReadStream,
347 pub write: CFWriteStream,
348}
349
350impl CFStreamPair {
351 #[must_use]
353 pub fn new(transfer_buffer_size: usize) -> Self {
354 let mut read = std::ptr::null_mut();
355 let mut write = std::ptr::null_mut();
356 unsafe { ffi::cf_stream_create_bound_pair(transfer_buffer_size, &raw mut read, &raw mut write) };
357 Self {
358 read: unsafe { CFReadStream::from_raw(read) }
359 .expect("CFStreamCreateBoundPair read stream was NULL"),
360 write: unsafe { CFWriteStream::from_raw(write) }
361 .expect("CFStreamCreateBoundPair write stream was NULL"),
362 }
363 }
364}
365
366impl CFReadStream {
367 #[must_use]
369 pub fn open(&self) -> bool {
370 unsafe { ffi::cf_read_stream_open(self.as_ptr()) }
371 }
372
373 pub fn close(&self) {
375 unsafe { ffi::cf_read_stream_close(self.as_ptr()) };
376 }
377
378 pub fn read(&self, buffer: &mut [u8]) -> Result<usize, isize> {
380 let count =
381 unsafe { ffi::cf_read_stream_read(self.as_ptr(), buffer.as_mut_ptr(), buffer.len()) };
382 if count < 0 {
383 Err(count)
384 } else {
385 Ok(usize::try_from(count).expect("non-negative read count fits in usize"))
386 }
387 }
388}
389
390impl CFWriteStream {
391 #[must_use]
393 pub fn open(&self) -> bool {
394 unsafe { ffi::cf_write_stream_open(self.as_ptr()) }
395 }
396
397 pub fn close(&self) {
399 unsafe { ffi::cf_write_stream_close(self.as_ptr()) };
400 }
401
402 pub fn write(&self, buffer: &[u8]) -> Result<usize, isize> {
404 let count =
405 unsafe { ffi::cf_write_stream_write(self.as_ptr(), buffer.as_ptr(), buffer.len()) };
406 if count < 0 {
407 Err(count)
408 } else {
409 Ok(usize::try_from(count).expect("non-negative write count fits in usize"))
410 }
411 }
412}
413
414impl CFSocket {
415 #[must_use]
417 pub fn udp_ipv4() -> Option<Self> {
418 let ptr = unsafe { ffi::cf_socket_create_udp_ipv4() };
419 unsafe { Self::from_raw(ptr) }
420 }
421
422 #[must_use]
424 pub fn native(&self) -> i32 {
425 unsafe { ffi::cf_socket_get_native(self.as_ptr()) }
426 }
427
428 #[must_use]
430 pub fn is_valid(&self) -> bool {
431 unsafe { ffi::cf_socket_is_valid(self.as_ptr()) }
432 }
433
434 pub fn invalidate(&self) {
436 unsafe { ffi::cf_socket_invalidate(self.as_ptr()) };
437 }
438}
439
440impl CFFileDescriptor {
441 #[must_use]
443 pub fn from_owned_fd(fd: OwnedFd) -> Option<Self> {
444 let ptr = unsafe { ffi::cf_file_descriptor_create(fd.as_raw_fd(), true) };
445 let descriptor = unsafe { Self::from_raw(ptr) }?;
446 let _ = fd.into_raw_fd();
447 Some(descriptor)
448 }
449
450 #[must_use]
451 pub fn from_borrowed_fd(fd: BorrowedFd<'_>) -> Option<Self> {
452 let ptr = unsafe { ffi::cf_file_descriptor_create(fd.as_raw_fd(), false) };
453 unsafe { Self::from_raw(ptr) }
454 }
455
456 #[must_use]
458 pub fn native_descriptor(&self) -> i32 {
459 unsafe { ffi::cf_file_descriptor_get_native(self.as_ptr()) }
460 }
461
462 pub fn invalidate(&self) {
464 unsafe { ffi::cf_file_descriptor_invalidate(self.as_ptr()) };
465 }
466}