Skip to content

Instantly share code, notes, and snippets.

@pronebird
Created May 1, 2024 15:06
Show Gist options
  • Save pronebird/46fdc921bc7970867283000b30420074 to your computer and use it in GitHub Desktop.
Save pronebird/46fdc921bc7970867283000b30420074 to your computer and use it in GitHub Desktop.
CFNotificationCenter in rust
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