-
-
Save pronebird/46fdc921bc7970867283000b30420074 to your computer and use it in GitHub Desktop.
CFNotificationCenter in rust
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use core_foundation::{ | |
base::{CFTypeID, TCFType}, | |
declare_TCFType, | |
dictionary::CFDictionaryRef, | |
impl_TCFType, | |
string::{CFString, CFStringRef}, | |
}; | |
use std::{ffi::c_void, fmt}; | |
pub type CFNotificationName = CFStringRef; | |
pub type CFNotificationCallback = extern "system" fn( | |
CFNotificationCenterRef, | |
*const c_void, | |
CFNotificationName, | |
*const c_void, | |
CFDictionaryRef, | |
); | |
#[repr(isize)] // CFIndex | |
pub enum CFNotificationSuspensionBehavior { | |
Drop = 1, | |
Coalesce = 2, | |
Hold = 3, | |
DeliverImmediately = 4, | |
} | |
extern "C" { | |
pub fn CFNotificationCenterGetTypeID() -> CFTypeID; | |
pub fn CFNotificationCenterGetLocalCenter() -> CFNotificationCenterRef; | |
pub fn CFNotificationCenterAddObserver( | |
center: CFNotificationCenterRef, | |
observer: *const c_void, | |
callback: CFNotificationCallback, | |
name: CFNotificationName, | |
object: *const c_void, | |
suspension_behavior: CFNotificationSuspensionBehavior, | |
); | |
pub fn CFNotificationCenterRemoveObserver( | |
center: CFNotificationCenterRef, | |
observer: *const c_void, | |
name: CFNotificationName, | |
object: *const c_void, | |
); | |
} | |
#[repr(C)] | |
pub struct __CFNotificationCenter(c_void); | |
pub type CFNotificationCenterRef = *const __CFNotificationCenter; | |
declare_TCFType!( | |
/// Low-level access to notification center on macOS and iOS. | |
CFNotificationCenter, CFNotificationCenterRef | |
); | |
impl_TCFType!( | |
CFNotificationCenter, | |
CFNotificationCenterRef, | |
CFNotificationCenterGetTypeID | |
); | |
impl CFNotificationCenter { | |
pub fn local() -> Self { | |
unsafe { Self::wrap_under_create_rule(CFNotificationCenterGetLocalCenter()) } | |
} | |
pub unsafe fn add_observer( | |
&self, | |
observer: *const c_void, | |
callback: CFNotificationCallback, | |
name: impl Into<CFString>, | |
object: *const c_void, | |
suspension_behavior: CFNotificationSuspensionBehavior, | |
) { | |
let cf_name = name.into(); | |
CFNotificationCenterAddObserver( | |
self.0, | |
observer, | |
callback, | |
cf_name.as_concrete_TypeRef(), | |
object, | |
suspension_behavior, | |
); | |
} | |
pub unsafe fn remove_observer( | |
&self, | |
observer: *const c_void, | |
name: impl Into<CFString>, | |
object: *const c_void, | |
) { | |
let cf_name = name.into(); | |
CFNotificationCenterRemoveObserver(self.0, observer, cf_name.as_concrete_TypeRef(), object); | |
} | |
pub fn add_observer_fn<S, F>( | |
&self, | |
name: S, | |
closure: F, | |
object: *const c_void, | |
suspension_behavior: CFNotificationSuspensionBehavior, | |
) -> NotificationToken | |
where | |
S: Into<CFString>, | |
F: FnMut( | |
CFNotificationCenterRef, | |
*const c_void, | |
CFNotificationName, | |
*const c_void, | |
CFDictionaryRef, | |
) + Send | |
+ 'static, | |
{ | |
let boxed_fn: Box<F> = Box::new(closure); | |
// Important: leak the Box<F> which will be released in `NotificationToken`. | |
let raw_boxed_fn = Box::into_raw(boxed_fn); | |
let cf_name = name.into(); | |
unsafe { | |
self.add_observer( | |
raw_boxed_fn as *const _, | |
notification_callback::<F>, | |
cf_name.clone(), | |
object, | |
suspension_behavior, | |
) | |
}; | |
NotificationToken::new(self.clone(), cf_name, raw_boxed_fn as *mut _, object) | |
} | |
} | |
extern "system" fn notification_callback<F>( | |
center: CFNotificationCenterRef, | |
observer: *const c_void, | |
name: CFNotificationName, | |
object: *const c_void, | |
user_info: CFDictionaryRef, | |
) where | |
F: FnMut( | |
CFNotificationCenterRef, | |
*const c_void, | |
CFNotificationName, | |
*const c_void, | |
CFDictionaryRef, | |
) + Send | |
+ 'static, | |
{ | |
// Important: cast observer to &mut F without taking ownership. | |
let callback: &mut F = unsafe { &mut *(observer as *mut F) }; | |
callback(center, observer, name, object, user_info); | |
} | |
/// Type that automatically unregisters the notification observer on drop. | |
pub struct NotificationToken { | |
name: String, | |
release_fn: Option<Box<dyn FnOnce()>>, | |
} | |
unsafe impl Send for NotificationToken {} | |
impl fmt::Debug for NotificationToken { | |
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | |
f.debug_struct("NotificationToken") | |
.field("name", &self.name) | |
.finish() | |
} | |
} | |
impl NotificationToken { | |
fn new<F>( | |
center: CFNotificationCenter, | |
name: CFString, | |
boxed_fn: *mut F, | |
object: *const c_void, | |
) -> Self | |
where | |
F: FnMut( | |
CFNotificationCenterRef, | |
*const c_void, | |
CFNotificationName, | |
*const c_void, | |
CFDictionaryRef, | |
) + Send | |
+ 'static, | |
{ | |
Self { | |
name: name.to_string(), | |
release_fn: Some(Box::new(move || { | |
unsafe { center.remove_observer(boxed_fn as *const _, name, object) }; | |
let _ = unsafe { Box::from_raw(boxed_fn as *mut F) }; | |
})), | |
} | |
} | |
} | |
impl Drop for NotificationToken { | |
fn drop(&mut self) { | |
if let Some(release_fn) = self.release_fn.take() { | |
release_fn(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment