Rewrite WINGs/notification.c in Rust.

This commit is contained in:
2025-12-08 13:06:04 -05:00
parent d88d626fbe
commit b7f765e3f6
7 changed files with 371 additions and 285 deletions
-1
View File
@@ -73,7 +73,6 @@ libWUtil_la_SOURCES = \
menuparser.h \
menuparser_macros.c \
misc.c \
notification.c \
proplist.c \
userdefaults.c \
userdefaults.h \
-2
View File
@@ -599,8 +599,6 @@ void WMTreeWalk(WMTreeNode *aNode, WMTreeWalkProc * walk, void *data);
/* ---[ WINGs/notification.c ]---------------------------------------------------- */
void WMReleaseNotification(WMNotification *notification);
void* WMGetNotificationClientData(WMNotification *notification);
void* WMGetNotificationObject(WMNotification *notification);
-278
View File
@@ -1,278 +0,0 @@
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "WUtil.h"
#include "WINGsP.h"
typedef struct W_Notification {
const char *name;
void *object;
void *clientData;
int refCount;
} Notification;
static WMNotification* WMRetainNotification(WMNotification *notification);
const char *WMGetNotificationName(WMNotification * notification)
{
return notification->name;
}
void *WMGetNotificationObject(WMNotification * notification)
{
return notification->object;
}
void *WMGetNotificationClientData(WMNotification * notification)
{
return notification->clientData;
}
WMNotification *WMCreateNotification(const char *name, void *object, void *clientData)
{
Notification *nPtr;
nPtr = wmalloc(sizeof(Notification));
nPtr->name = name;
nPtr->object = object;
nPtr->clientData = clientData;
nPtr->refCount = 1;
return nPtr;
}
void WMReleaseNotification(WMNotification * notification)
{
notification->refCount--;
if (notification->refCount < 1) {
wfree(notification);
}
}
static WMNotification *WMRetainNotification(WMNotification * notification)
{
notification->refCount++;
return notification;
}
/***************** Notification Center *****************/
typedef struct NotificationObserver {
WMNotificationObserverAction *observerAction;
void *observer;
const char *name;
void *object;
struct NotificationObserver *prev; /* for tables */
struct NotificationObserver *next;
struct NotificationObserver *nextAction; /* for observerTable */
} NotificationObserver;
typedef struct W_NotificationCenter {
WMHashTable *nameTable; /* names -> observer lists */
WMHashTable *objectTable; /* object -> observer lists */
NotificationObserver *nilList; /* obervers that catch everything */
WMHashTable *observerTable; /* observer -> NotificationObserver */
} NotificationCenter;
/* default (and only) center */
static NotificationCenter *notificationCenter = NULL;
void W_InitNotificationCenter(void)
{
notificationCenter = wmalloc(sizeof(NotificationCenter));
notificationCenter->nameTable = WMCreateStringHashTable();
notificationCenter->objectTable = WMCreateIdentityHashTable();
notificationCenter->nilList = NULL;
notificationCenter->observerTable = WMCreateIdentityHashTable();
}
void W_ReleaseNotificationCenter(void)
{
if (notificationCenter) {
if (notificationCenter->nameTable)
WMFreeHashTable(notificationCenter->nameTable);
if (notificationCenter->objectTable)
WMFreeHashTable(notificationCenter->objectTable);
if (notificationCenter->observerTable)
WMFreeHashTable(notificationCenter->observerTable);
wfree(notificationCenter);
notificationCenter = NULL;
}
}
void
WMAddNotificationObserver(WMNotificationObserverAction * observerAction,
void *observer, const char *name, void *object)
{
NotificationObserver *oRec, *rec;
oRec = wmalloc(sizeof(NotificationObserver));
oRec->observerAction = observerAction;
oRec->observer = observer;
oRec->name = name;
oRec->object = object;
oRec->next = NULL;
oRec->prev = NULL;
/* put this action in the list of actions for this observer */
rec = (NotificationObserver *) WMHashInsert(notificationCenter->observerTable, observer, oRec);
if (rec) {
/* if this is not the first action for the observer */
oRec->nextAction = rec;
} else {
oRec->nextAction = NULL;
}
if (!name && !object) {
/* catch-all */
oRec->next = notificationCenter->nilList;
if (notificationCenter->nilList) {
notificationCenter->nilList->prev = oRec;
}
notificationCenter->nilList = oRec;
} else if (!name) {
/* any message coming from object */
rec = (NotificationObserver *) WMHashInsert(notificationCenter->objectTable, object, oRec);
oRec->next = rec;
if (rec) {
rec->prev = oRec;
}
} else {
/* name && (object || !object) */
rec = (NotificationObserver *) WMHashInsert(notificationCenter->nameTable, name, oRec);
oRec->next = rec;
if (rec) {
rec->prev = oRec;
}
}
}
void WMPostNotification(WMNotification * notification)
{
NotificationObserver *orec, *tmp;
WMRetainNotification(notification);
/* tell the observers that want to know about a particular message */
orec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable, notification->name);
while (orec) {
tmp = orec->next;
if (!orec->object || !notification->object || orec->object == notification->object) {
/* tell the observer */
if (orec->observerAction) {
(*orec->observerAction) (orec->observer, notification);
}
}
orec = tmp;
}
/* tell the observers that want to know about an object */
orec = (NotificationObserver *) WMHashGet(notificationCenter->objectTable, notification->object);
while (orec) {
tmp = orec->next;
/* tell the observer */
if (orec->observerAction) {
(*orec->observerAction) (orec->observer, notification);
}
orec = tmp;
}
/* tell the catch all observers */
orec = notificationCenter->nilList;
while (orec) {
tmp = orec->next;
/* tell the observer */
if (orec->observerAction) {
(*orec->observerAction) (orec->observer, notification);
}
orec = tmp;
}
WMReleaseNotification(notification);
}
void WMRemoveNotificationObserver(void *observer)
{
NotificationObserver *orec, *tmp, *rec;
/* get the list of actions the observer is doing */
orec = (NotificationObserver *) WMHashGet(notificationCenter->observerTable, observer);
/*
* FOREACH orec IN actionlist for observer
* DO
* remove from respective lists/tables
* free
* END
*/
while (orec) {
tmp = orec->nextAction;
if (!orec->name && !orec->object) {
/* catch-all */
if (notificationCenter->nilList == orec)
notificationCenter->nilList = orec->next;
} else if (!orec->name) {
/* any message coming from object */
rec = (NotificationObserver *) WMHashGet(notificationCenter->objectTable, orec->object);
if (rec == orec) {
/* replace table entry */
if (orec->next) {
WMHashInsert(notificationCenter->objectTable, orec->object, orec->next);
} else {
WMHashRemove(notificationCenter->objectTable, orec->object);
}
}
} else {
/* name && (object || !object) */
rec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable, orec->name);
if (rec == orec) {
/* replace table entry */
if (orec->next) {
WMHashInsert(notificationCenter->nameTable, orec->name, orec->next);
} else {
WMHashRemove(notificationCenter->nameTable, orec->name);
}
}
}
if (orec->prev)
orec->prev->next = orec->next;
if (orec->next)
orec->next->prev = orec->prev;
wfree(orec);
orec = tmp;
}
WMHashRemove(notificationCenter->observerTable, observer);
}
void WMPostNotificationName(const char *name, void *object, void *clientData)
{
WMNotification *notification;
notification = WMCreateNotification(name, object, clientData);
WMPostNotification(notification);
WMReleaseNotification(notification);
}
+1 -4
View File
@@ -44,9 +44,6 @@ void WMInitializeApplication(const char *applicationName, int *argc, char **argv
WMApplication.argv[i] = wstrdup(argv[i]);
}
WMApplication.argv[i] = NULL;
/* initialize notification center */
W_InitNotificationCenter();
}
void WMReleaseApplication(void) {
@@ -60,7 +57,7 @@ void WMReleaseApplication(void) {
*/
w_save_defaults_changes();
W_ReleaseNotificationCenter();
W_ClearNotificationCenter();
if (WMApplication.applicationName) {
wfree(WMApplication.applicationName);
+1
View File
@@ -10,6 +10,7 @@ RUST_SOURCES = \
src/hash_table.rs \
src/lib.rs \
src/memory.rs \
src/notification.rs \
src/prop_list.rs \
src/string.rs
src/tree.rs
+1
View File
@@ -5,6 +5,7 @@ pub mod defines;
pub mod find_file;
pub mod hash_table;
pub mod memory;
pub mod notification;
pub mod prop_list;
pub mod string;
pub mod tree;
+368
View File
@@ -0,0 +1,368 @@
use std::{
collections::{hash_map::Entry, HashMap},
ffi::{c_void, CStr},
hash,
ptr::{self, NonNull},
sync::{Mutex, OnceLock},
};
/// Lightweight message from object to another. When a notification is sent,
/// registered [`Action`]s are called.
///
/// Use [`ffi::WMAddNotificationObserver`] or [`NotificationCenter::register`]
/// to request notifications.
///
/// ## Safety
///
/// `Notification` encapsulates two data pointers. The Rust implementation
/// explicitly supports notifications across threads. To uphold Rust's safety
/// rules, `Notification`s must only be created with pointers that can be sent
/// across threads. The [`Sendable`] struct is provided to guarantee this.
///
/// ## Rust rewrite notes
///
/// This was originally a reference-counted structure, but it is so lightweight
/// (consisting of three pointers) that the Rust version is `Copy`. This
/// simplifies things --- each Rust recipient of a `Notification` owns it, and
/// there is no need to coordinate how it is cleaned up.
///
/// In unported C code, a notificaton's `name` may be compared against a string
/// constant using pointer equality rather than string equality. This has
/// negative implications for sending notifications across the Rust/C
/// boundary. For the time being, notifications are generated and received by
/// code written in C, but care should be taken that notification names are
/// checked properly when porting notification registration in the future. (We
/// will ideally move to a different data type for identifying notifications,
/// too.)
#[derive(Clone, Copy)]
pub struct Notification {
name: &'static CStr,
/// The object that generated the notification. This may be `None` for
/// notifications that are about global state.
source: Option<Sendable>,
/// Optional side-channel data provided to notification listeners.
client_data: Option<Sendable>,
}
/// Callback that notifies `observer` (which may be null) of `notification` (which won't be).
pub type Action = unsafe extern "C" fn(observer: *mut c_void, notification: *const Notification);
/// Wraps a type-erased pointer (which it does not own) and marks it as `Send`.
///
/// The `Send`-ability of the wrapped pointer must be guaranteed code that
/// instantiates a `Sendable`.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Sendable {
ptr: NonNull<c_void>,
}
impl Sendable {
/// Creates a `Sendable` wrapping `ptr`.
///
/// ## Safety
///
/// `ptr` must be safe to send across threads.
pub unsafe fn new(ptr: NonNull<c_void>) -> Self {
Sendable { ptr }
}
}
impl hash::Hash for Sendable {
fn hash<H: hash::Hasher>(&self, h: &mut H) {
h.write_usize(self.ptr.as_ptr().addr());
}
}
// Guaranteed by `Sendable::new`.
unsafe impl Send for Sendable {}
#[derive(Default)]
pub struct NotificationCenter {
/// Notification subscriptions that match on name and source.
exact: HashMap<(&'static CStr, Sendable), Vec<(Option<Sendable>, Action)>>,
/// Notification subscriptions that match on name.
by_name: HashMap<&'static CStr, Vec<(Option<Sendable>, Action)>>,
/// Notification subscriptions that match on source.
by_source: HashMap<Sendable, Vec<(Option<Sendable>, Action)>>,
/// Notification subscriptions that match all notifications.
universal: Vec<(Option<Sendable>, Action)>,
}
// It is safe to send NotificationCenter across threads as long as the contract
// on Sendable is respected.
unsafe impl Send for NotificationCenter {}
impl NotificationCenter {
/// Creates a new `NotificationCenter`.
pub fn new() -> Self {
Self::default()
}
/// Provides access to the default, process-wide notification center. The
/// FFI C API uses this notification center. This is protected behind a
/// mutex that is held while `f` is run, so panicking inside of `f` should
/// be avoided.
pub fn with_global_default<R>(f: impl FnOnce(&mut Self) -> R) -> R {
static INSTANCE: OnceLock<Mutex<NotificationCenter>> = OnceLock::new();
f(&mut INSTANCE
.get_or_init(|| Mutex::new(NotificationCenter::new()))
.try_lock()
.unwrap())
}
/// Registers `action` to be invoked and invoked on `observer` when
/// notifications named `name` are fired from `source`.
pub fn register_exact(
&mut self,
name: &'static CStr,
source: Sendable,
observer: Option<Sendable>,
action: Action,
) {
match self.exact.entry((name, source)) {
Entry::Occupied(mut o) => {
o.get_mut().push((observer, action));
}
Entry::Vacant(v) => {
v.insert(vec![(observer, action)]);
}
}
}
/// Registers `action` to be invoked on `observer` when notifications are
/// fired by `source` (regardless of the notification name).
pub fn register_by_source(
&mut self,
source: Sendable,
observer: Option<Sendable>,
action: Action,
) {
match self.by_source.entry(source) {
Entry::Occupied(mut o) => {
o.get_mut().push((observer, action));
}
Entry::Vacant(v) => {
v.insert(vec![(observer, action)]);
}
}
}
/// Registers `action` to be invoked on `observer` for all notifications
/// named `name`.
pub fn register_by_name(
&mut self,
name: &'static CStr,
observer: Option<Sendable>,
action: Action,
) {
match self.by_name.entry(name) {
Entry::Occupied(mut o) => {
o.get_mut().push((observer, action));
}
Entry::Vacant(v) => {
v.insert(vec![(observer, action)]);
}
}
}
/// Registers `action` to be invoked on `observer` for all notifications,
/// regardless of the notification's name or source.
pub fn register_universal(&mut self, observer: Option<Sendable>, action: Action) {
self.universal.push((observer, action));
}
/// Dispatches `notification` with registered actions.
pub fn dispatch(&mut self, notification: Notification) {
if let Some(observers) = self.by_name.get_mut(notification.name) {
for (observer, action) in observers {
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
unsafe {
(action)(observer, &notification);
}
}
}
if let Some(source) = notification.source {
if let Some(observers) = self.exact.get_mut(&(notification.name, source)) {
for (observer, action) in observers {
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
unsafe {
(action)(observer, &notification);
}
}
}
if let Some(observers) = self.by_source.get_mut(&source) {
for (observer, action) in observers {
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
unsafe {
(action)(observer, &notification);
}
}
}
}
for (observer, action) in &mut self.universal {
let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut());
unsafe {
(action)(observer, &notification);
}
}
}
/// Removes all notification subscriptions that would notify `observer` if they fired.
pub fn remove_observer(&mut self, observer: Sendable) {
self.exact.retain(|_, values| {
values.retain(|(o, _)| *o != Some(observer));
!values.is_empty()
});
self.by_name.retain(|_, values| {
values.retain(|(o, _)| *o != Some(observer));
!values.is_empty()
});
self.by_source.retain(|_, values| {
values.retain(|(o, _)| *o != Some(observer));
!values.is_empty()
});
self.universal.retain(|(o, _)| *o != Some(observer));
}
/// Clears all registered notification listeners and resets `self` to its
/// default state.
pub fn clear(&mut self) {
*self = Self::default();
}
}
pub mod ffi {
use super::{Action, Notification, NotificationCenter, Sendable};
use std::{
ffi::{c_char, c_void, CStr},
ptr::{self, NonNull},
};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetNotificationClientData(
notification: *mut Notification,
) -> *mut c_void {
if notification.is_null() {
return ptr::null_mut();
}
unsafe {
(*notification)
.client_data
.map(|x| x.ptr.as_ptr())
.unwrap_or(ptr::null_mut())
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetNotificationObject(
notification: *mut Notification,
) -> *mut c_void {
if notification.is_null() {
return ptr::null_mut();
}
unsafe {
(*notification)
.source
.map(|x| x.ptr.as_ptr())
.unwrap_or(ptr::null_mut())
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMGetNotificationName(
notification: *mut Notification,
) -> *const c_char {
if notification.is_null() {
return ptr::null_mut();
}
unsafe { (*notification).name.as_ptr() }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMAddNotificationObserver(
action: Option<Action>,
observer: *mut c_void,
name: *const c_char,
object: *mut c_void,
) {
let Some(action) = action else {
return;
};
let observer = NonNull::new(observer).map(|x| unsafe { Sendable::new(x) });
let source = NonNull::new(object);
NotificationCenter::with_global_default(|c| {
if name.is_null() {
match source {
Some(source) => {
c.register_by_source(unsafe { Sendable::new(source) }, observer, action);
}
None => c.register_universal(observer, action),
}
} else {
let name = unsafe { CStr::from_ptr(name) };
match source {
Some(source) => {
c.register_exact(name, unsafe { Sendable::new(source) }, observer, action);
}
None => c.register_by_name(name, observer, action),
}
}
});
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMRemoveNotificationObserver(observer: *mut c_void) {
let Some(observer) = NonNull::new(observer) else {
return;
};
NotificationCenter::with_global_default(|c| {
c.remove_observer(unsafe { Sendable::new(observer) })
});
}
/// Posts a notification from `object` with the given `name` and `client_data`.
///
/// ## Safety
///
/// `name` must be a non-null string constant or some other pointer with a
/// static lifetime.
///
/// `object` and `client_data` must be safe to send across threads (per the
/// contract of [`Sendable`]).
///
/// ## Rust rewrite notes
///
/// This originally took a heap-allocated `*mut Notification`, but now the
/// constructed `Notification` parameters are passed directly.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn WMPostNotificationName(
name: *const c_char,
object: *mut c_void,
client_data: *mut c_void,
) {
if name.is_null() {
return;
}
let name = unsafe { CStr::from_ptr(name) };
let source = NonNull::new(object).map(|x| unsafe { Sendable::new(x) });
let client_data = NonNull::new(client_data).map(|x| unsafe { Sendable::new(x) });
NotificationCenter::with_global_default(|c| {
c.dispatch(Notification {
name,
source,
client_data,
})
});
}
/// Resets all notifications for the global notification center. Used by
/// `WApplication` teardown code. This is a private, WINGs-internal API.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn W_ClearNotificationCenter() {
NotificationCenter::with_global_default(|c| c.clear());
}
}