From dcd45f06775ef7a5ab2a559c81937970c185e369 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sat, 1 Nov 2025 15:50:17 -0400 Subject: [PATCH 01/10] Make some unused public WMNotification APIs private. `WMRetainNotification`, `WMGetDefaultNotificationQueue`, `WMDequeueNotificationMatching`, `WMEnqueueNotification`, and `WMEnqueueCoalesceNotification`, are only used in WINGs/notification.c. To reduce the API surface area that needs to be migrated to Rust, they are being made private. --- WINGs/WINGs/WUtil.h | 20 +------------------- WINGs/notification.c | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 060c1adb..224add25 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -596,15 +596,13 @@ WMTreeNode *WMFindInTreeWithDepthLimit(WMTreeNode * aTree, int (*match)(const WM /* Walk every node of aNode with `walk' */ void WMTreeWalk(WMTreeNode *aNode, WMTreeWalkProc * walk, void *data); -/* ---[ WINGs/data.c ]---------------------------------------------------- */ +/* ---[ WINGs/notification.c ]---------------------------------------------------- */ WMNotification* WMCreateNotification(const char *name, void *object, void *clientData); void WMReleaseNotification(WMNotification *notification); -WMNotification* WMRetainNotification(WMNotification *notification); - void* WMGetNotificationClientData(WMNotification *notification); void* WMGetNotificationObject(WMNotification *notification); @@ -624,24 +622,8 @@ void WMRemoveNotificationObserverWithName(void *observer, const char *name, void WMPostNotificationName(const char *name, void *object, void *clientData); -WMNotificationQueue* WMGetDefaultNotificationQueue(void); - WMNotificationQueue* WMCreateNotificationQueue(void); -void WMDequeueNotificationMatching(WMNotificationQueue *queue, - WMNotification *notification, - unsigned mask); - -void WMEnqueueNotification(WMNotificationQueue *queue, - WMNotification *notification, - WMPostingStyle postingStyle); - -void WMEnqueueCoalesceNotification(WMNotificationQueue *queue, - WMNotification *notification, - WMPostingStyle postingStyle, - unsigned coalesceMask); - - /* Property Lists handling */ /* ---[ WINGs/proplist.c ]------------------------------------------------ */ diff --git a/WINGs/notification.c b/WINGs/notification.c index d43fef54..7cfaac21 100644 --- a/WINGs/notification.c +++ b/WINGs/notification.c @@ -16,6 +16,19 @@ typedef struct W_Notification { } Notification; +static WMNotification* WMRetainNotification(WMNotification *notification); +static WMNotificationQueue* WMGetDefaultNotificationQueue(void); +static void WMDequeueNotificationMatching(WMNotificationQueue *queue, + WMNotification *notification, + unsigned mask); +static void WMEnqueueNotification(WMNotificationQueue *queue, + WMNotification *notification, + WMPostingStyle postingStyle); +static void WMEnqueueCoalesceNotification(WMNotificationQueue *queue, + WMNotification *notification, + WMPostingStyle postingStyle, + unsigned coalesceMask); + const char *WMGetNotificationName(WMNotification * notification) { return notification->name; @@ -53,7 +66,7 @@ void WMReleaseNotification(WMNotification * notification) } } -WMNotification *WMRetainNotification(WMNotification * notification) +static WMNotification *WMRetainNotification(WMNotification * notification) { notification->refCount++; @@ -365,7 +378,7 @@ static WMNotificationQueue *notificationQueueList = NULL; /* default queue */ static WMNotificationQueue *notificationQueue = NULL; -WMNotificationQueue *WMGetDefaultNotificationQueue(void) +static WMNotificationQueue *WMGetDefaultNotificationQueue(void) { if (!notificationQueue) notificationQueue = WMCreateNotificationQueue(); @@ -387,7 +400,7 @@ WMNotificationQueue *WMCreateNotificationQueue(void) return queue; } -void WMEnqueueNotification(WMNotificationQueue * queue, WMNotification * notification, WMPostingStyle postingStyle) +static void WMEnqueueNotification(WMNotificationQueue * queue, WMNotification * notification, WMPostingStyle postingStyle) { WMEnqueueCoalesceNotification(queue, notification, postingStyle, WNCOnName | WNCOnSender); } @@ -413,7 +426,7 @@ static int matchName(const void *item, const void *cdata) #undef NOTIF #undef ITEM -void WMDequeueNotificationMatching(WMNotificationQueue * queue, WMNotification * notification, unsigned mask) +static void WMDequeueNotificationMatching(WMNotificationQueue * queue, WMNotification * notification, unsigned mask) { WMMatchDataProc *matchFunc; @@ -430,7 +443,7 @@ void WMDequeueNotificationMatching(WMNotificationQueue * queue, WMNotification * WMRemoveFromArrayMatching(queue->idleQueue, matchFunc, notification); } -void +static void WMEnqueueCoalesceNotification(WMNotificationQueue * queue, WMNotification * notification, WMPostingStyle postingStyle, unsigned coalesceMask) { -- 2.39.5 From 0c4d78a53d2f1fcc7b4bce3d452306492af598ae Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 2 Nov 2025 14:25:49 -0500 Subject: [PATCH 02/10] Drop unused macro (dead code) that invokes WMCreateNotification. --- WINGs/wtext.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/WINGs/wtext.c b/WINGs/wtext.c index 61899ed8..c16773ff 100644 --- a/WINGs/wtext.c +++ b/WINGs/wtext.c @@ -166,16 +166,6 @@ typedef struct W_Text { WMArray *xdndDestinationTypes; } Text; -/* not used */ -#if 0 -#define NOTIFY(T,C,N,A) {\ - WMNotification *notif = WMCreateNotification(N,T,A);\ - if ((T)->delegate && (T)->delegate->C)\ - (*(T)->delegate->C)((T)->delegate,notif);\ - WMPostNotification(notif);\ - WMReleaseNotification(notif);} -#endif - #define TYPETEXT 0 #if 0 -- 2.39.5 From adb967ab1543265a91ae38090bc2171f4b83c219 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 2 Nov 2025 15:59:55 -0500 Subject: [PATCH 03/10] Drop dead notification queue code. This appears to have been used by now-defunct support for network connections (WMConnection). No live code instantiates a notification queue or pushes/dequeues notifications from a notification queue. (The global NotificationCenter in WINGs/notification.c is still in use, so it is not going anywhere in this commit.) --- WINGs/WINGs/WINGsP.h.in | 4 -- WINGs/WINGs/WUtil.h | 2 - WINGs/handlers.c | 12 ---- WINGs/notification.c | 141 ---------------------------------------- 4 files changed, 159 deletions(-) diff --git a/WINGs/WINGs/WINGsP.h.in b/WINGs/WINGs/WINGsP.h.in index 2c36b269..a0020989 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -378,10 +378,6 @@ void W_InitNotificationCenter(void); void W_ReleaseNotificationCenter(void); -void W_FlushASAPNotificationQueue(void); - -void W_FlushIdleNotificationQueue(void); - /* ---[ selection.c ]----------------------------------------------------- */ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 224add25..aec32d48 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -622,8 +622,6 @@ void WMRemoveNotificationObserverWithName(void *observer, const char *name, void WMPostNotificationName(const char *name, void *object, void *clientData); -WMNotificationQueue* WMCreateNotificationQueue(void); - /* Property Lists handling */ /* ---[ WINGs/proplist.c ]------------------------------------------------ */ diff --git a/WINGs/handlers.c b/WINGs/handlers.c index c937aaa1..74f4fdd1 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -274,7 +274,6 @@ Bool W_CheckIdleHandlers(void) WMArrayIterator iter; if (!idleHandler || WMGetArrayItemCount(idleHandler) == 0) { - W_FlushIdleNotificationQueue(); /* make sure an observer in queue didn't added an idle handler */ return (idleHandler != NULL && WMGetArrayItemCount(idleHandler) > 0); } @@ -292,8 +291,6 @@ Bool W_CheckIdleHandlers(void) WMFreeArray(handlerCopy); - W_FlushIdleNotificationQueue(); - /* this is not necesarrily False, because one handler can re-add itself */ return (WMGetArrayItemCount(idleHandler) > 0); } @@ -304,7 +301,6 @@ void W_CheckTimerHandlers(void) struct timeval now; if (!timerHandler) { - W_FlushASAPNotificationQueue(); return; } @@ -331,8 +327,6 @@ void W_CheckTimerHandlers(void) wfree(handler); } } - - W_FlushASAPNotificationQueue(); } /* @@ -384,7 +378,6 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) nfds = 0; if (!extrafd && nfds == 0) { - W_FlushASAPNotificationQueue(); return False; } @@ -461,8 +454,6 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) wfree(fds); - W_FlushASAPNotificationQueue(); - return (count > 0); #else #ifdef HAVE_SELECT @@ -479,7 +470,6 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) nfds = 0; if (inputfd < 0 && nfds == 0) { - W_FlushASAPNotificationQueue(); return False; } @@ -556,8 +546,6 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) WMFreeArray(handlerCopy); } - W_FlushASAPNotificationQueue(); - return (count > 0); #else /* not HAVE_SELECT, not HAVE_POLL */ # error Neither select nor poll. You lose. diff --git a/WINGs/notification.c b/WINGs/notification.c index 7cfaac21..38b92c1a 100644 --- a/WINGs/notification.c +++ b/WINGs/notification.c @@ -17,17 +17,6 @@ typedef struct W_Notification { static WMNotification* WMRetainNotification(WMNotification *notification); -static WMNotificationQueue* WMGetDefaultNotificationQueue(void); -static void WMDequeueNotificationMatching(WMNotificationQueue *queue, - WMNotification *notification, - unsigned mask); -static void WMEnqueueNotification(WMNotificationQueue *queue, - WMNotification *notification, - WMPostingStyle postingStyle); -static void WMEnqueueCoalesceNotification(WMNotificationQueue *queue, - WMNotification *notification, - WMPostingStyle postingStyle, - unsigned coalesceMask); const char *WMGetNotificationName(WMNotification * notification) { @@ -363,133 +352,3 @@ void WMPostNotificationName(const char *name, void *object, void *clientData) WMReleaseNotification(notification); } - -/**************** Notification Queues ****************/ - -typedef struct W_NotificationQueue { - WMArray *asapQueue; - WMArray *idleQueue; - - struct W_NotificationQueue *next; -} NotificationQueue; - -static WMNotificationQueue *notificationQueueList = NULL; - -/* default queue */ -static WMNotificationQueue *notificationQueue = NULL; - -static WMNotificationQueue *WMGetDefaultNotificationQueue(void) -{ - if (!notificationQueue) - notificationQueue = WMCreateNotificationQueue(); - - return notificationQueue; -} - -WMNotificationQueue *WMCreateNotificationQueue(void) -{ - NotificationQueue *queue; - - queue = wmalloc(sizeof(NotificationQueue)); - queue->asapQueue = WMCreateArrayWithDestructor(8, (WMFreeDataProc *) WMReleaseNotification); - queue->idleQueue = WMCreateArrayWithDestructor(8, (WMFreeDataProc *) WMReleaseNotification); - queue->next = notificationQueueList; - - notificationQueueList = queue; - - return queue; -} - -static void WMEnqueueNotification(WMNotificationQueue * queue, WMNotification * notification, WMPostingStyle postingStyle) -{ - WMEnqueueCoalesceNotification(queue, notification, postingStyle, WNCOnName | WNCOnSender); -} - -#define NOTIF ((WMNotification*)cdata) -#define ITEM ((WMNotification*)item) - -static int matchSenderAndName(const void *item, const void *cdata) -{ - return (NOTIF->object == ITEM->object && strcmp(NOTIF->name, ITEM->name) == 0); -} - -static int matchSender(const void *item, const void *cdata) -{ - return (NOTIF->object == ITEM->object); -} - -static int matchName(const void *item, const void *cdata) -{ - return (strcmp(NOTIF->name, ITEM->name) == 0); -} - -#undef NOTIF -#undef ITEM - -static void WMDequeueNotificationMatching(WMNotificationQueue * queue, WMNotification * notification, unsigned mask) -{ - WMMatchDataProc *matchFunc; - - if ((mask & WNCOnName) && (mask & WNCOnSender)) - matchFunc = matchSenderAndName; - else if (mask & WNCOnName) - matchFunc = matchName; - else if (mask & WNCOnSender) - matchFunc = matchSender; - else - return; - - WMRemoveFromArrayMatching(queue->asapQueue, matchFunc, notification); - WMRemoveFromArrayMatching(queue->idleQueue, matchFunc, notification); -} - -static void -WMEnqueueCoalesceNotification(WMNotificationQueue * queue, - WMNotification * notification, WMPostingStyle postingStyle, unsigned coalesceMask) -{ - if (coalesceMask != WNCNone) - WMDequeueNotificationMatching(queue, notification, coalesceMask); - - switch (postingStyle) { - case WMPostNow: - WMPostNotification(notification); - WMReleaseNotification(notification); - break; - - case WMPostASAP: - WMAddToArray(queue->asapQueue, notification); - break; - - case WMPostWhenIdle: - WMAddToArray(queue->idleQueue, notification); - break; - } -} - -void W_FlushASAPNotificationQueue(void) -{ - WMNotificationQueue *queue = notificationQueueList; - - while (queue) { - while (WMGetArrayItemCount(queue->asapQueue)) { - WMPostNotification(WMGetFromArray(queue->asapQueue, 0)); - WMDeleteFromArray(queue->asapQueue, 0); - } - - queue = queue->next; - } -} - -void W_FlushIdleNotificationQueue(void) -{ - WMNotificationQueue *queue = notificationQueueList; - - while (queue) { - while (WMGetArrayItemCount(queue->idleQueue)) { - WMPostNotification(WMGetFromArray(queue->idleQueue, 0)); - WMDeleteFromArray(queue->idleQueue, 0); - } - - queue = queue->next; - } -} -- 2.39.5 From 5847e9d68f87a82f979ec7ad4d5d5d2eafa8857c Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sat, 8 Nov 2025 12:26:57 -0500 Subject: [PATCH 04/10] Ditch the notification function WMRemoveNotificationObserverWithName. This reduces the notification API in a way that is helpful for rewriting in Rust. This function is only used in one place, and the object that is being deregistered is free'd immediately after WMRemoveNotificationObserverWithName is called, so it should be safe just to use WMRemoveNotificationObserver (since I hope it's an error to keep a notification to a free'd object registered). --- WINGs/WINGs/WUtil.h | 3 -- WINGs/notification.c | 76 -------------------------------------------- WINGs/wbrowser.c | 2 +- 3 files changed, 1 insertion(+), 80 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index aec32d48..45557e84 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -617,9 +617,6 @@ void WMPostNotification(WMNotification *notification); void WMRemoveNotificationObserver(void *observer); -void WMRemoveNotificationObserverWithName(void *observer, const char *name, - void *object); - void WMPostNotificationName(const char *name, void *object, void *clientData); /* Property Lists handling */ diff --git a/WINGs/notification.c b/WINGs/notification.c index 38b92c1a..335189d7 100644 --- a/WINGs/notification.c +++ b/WINGs/notification.c @@ -266,82 +266,6 @@ void WMRemoveNotificationObserver(void *observer) WMHashRemove(notificationCenter->observerTable, observer); } -void WMRemoveNotificationObserverWithName(void *observer, const char *name, void *object) -{ - NotificationObserver *orec, *tmp, *rec; - NotificationObserver *newList = NULL; - - /* get the list of actions the observer is doing */ - orec = (NotificationObserver *) WMHashGet(notificationCenter->observerTable, observer); - - WMHashRemove(notificationCenter->observerTable, observer); - - /* rebuild the list of actions for the observer */ - - while (orec) { - tmp = orec->nextAction; - if (orec->name == name && orec->object == object) { - if (!name && !object) { - if (notificationCenter->nilList == orec) - notificationCenter->nilList = orec->next; - } else if (!name) { - rec = - (NotificationObserver *) WMHashGet(notificationCenter->objectTable, - orec->object); - if (rec == orec) { - assert(rec->prev == NULL); - /* replace table entry */ - if (orec->next) { - WMHashInsert(notificationCenter->objectTable, - orec->object, orec->next); - } else { - WMHashRemove(notificationCenter->objectTable, orec->object); - } - } - } else { - rec = (NotificationObserver *) WMHashGet(notificationCenter->nameTable, - orec->name); - if (rec == orec) { - assert(rec->prev == NULL); - /* 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); - } else { - /* append this action in the new action list */ - orec->nextAction = NULL; - if (!newList) { - newList = orec; - } else { - NotificationObserver *p; - - p = newList; - while (p->nextAction) { - p = p->nextAction; - } - p->nextAction = orec; - } - } - orec = tmp; - } - - /* reinsert the list to the table */ - if (newList) { - WMHashInsert(notificationCenter->observerTable, observer, newList); - } -} - void WMPostNotificationName(const char *name, void *object, void *clientData) { WMNotification *notification; diff --git a/WINGs/wbrowser.c b/WINGs/wbrowser.c index b25bc8c1..41b43047 100644 --- a/WINGs/wbrowser.c +++ b/WINGs/wbrowser.c @@ -314,7 +314,7 @@ static void removeColumn(WMBrowser * bPtr, int column) wfree(bPtr->titles[i]); bPtr->titles[i] = NULL; } - WMRemoveNotificationObserverWithName(bPtr, WMListSelectionDidChangeNotification, bPtr->columns[i]); + WMRemoveNotificationObserver(bPtr->columns[i]); WMDestroyWidget(bPtr->columns[i]); bPtr->columns[i] = NULL; } -- 2.39.5 From d88d626fbe3bb5def76e778c731816160ade84a4 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Wed, 12 Nov 2025 12:15:13 -0500 Subject: [PATCH 05/10] Remove direct creation and posting of notifications from WINGs. We can reduce the WMNotification API surface area further by getting rid of WMCreateNotification and WMPostNotification. WMNotification remains a first-class object, but it is no longer possible for client code to create a notification object directly. Notifications must now be posted through WMPostNotificationName, which handles notification creation and destruction on its own. This will simplify the notification lifecycle and make the Rust rewrite simpler. (Notifications no longer need to be reference-counted, heap-allocated objects that might be saved somewhere after they are dispatched.) WTextField code which reused the WMNotification struct has been modified to take parameters of the correct type directly, instead of through a WMNotification's void* data field. --- WINGs/WINGs/WINGs.h | 17 +++++------ WINGs/WINGs/WUtil.h | 4 --- WINGs/wtextfield.c | 69 ++++++++++++++++++++++++------------------- WPrefs.app/editmenu.c | 5 +--- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/WINGs/WINGs/WINGs.h b/WINGs/WINGs/WINGs.h index b6192bdb..2d2b3a8e 100644 --- a/WINGs/WINGs/WINGs.h +++ b/WINGs/WINGs/WINGs.h @@ -233,7 +233,7 @@ typedef enum { /* text movement types */ -enum { +typedef enum { WMIllegalTextMovement, WMReturnTextMovement, WMEscapeTextMovement, @@ -243,13 +243,13 @@ enum { WMRightTextMovement, WMUpTextMovement, WMDownTextMovement -}; +} WMTextMovementType; /* text field special events */ -enum { +typedef enum { WMInsertTextEvent, WMDeleteTextEvent -}; +} WMTextFieldSpecialEventType; enum { @@ -533,14 +533,11 @@ typedef struct WMBrowserDelegate { typedef struct WMTextFieldDelegate { void *data; - void (*didBeginEditing)(struct WMTextFieldDelegate *self, - WMNotification *notif); + void (*didBeginEditing)(struct WMTextFieldDelegate *self, WMTextMovementType reason); - void (*didChange)(struct WMTextFieldDelegate *self, - WMNotification *notif); + void (*didChange)(struct WMTextFieldDelegate *self, WMTextFieldSpecialEventType reason); - void (*didEndEditing)(struct WMTextFieldDelegate *self, - WMNotification *notif); + void (*didEndEditing)(struct WMTextFieldDelegate *self, WMTextMovementType reason); Bool (*shouldBeginEditing)(struct WMTextFieldDelegate *self, WMTextField *tPtr); diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 45557e84..f290f187 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -599,8 +599,6 @@ void WMTreeWalk(WMTreeNode *aNode, WMTreeWalkProc * walk, void *data); /* ---[ WINGs/notification.c ]---------------------------------------------------- */ -WMNotification* WMCreateNotification(const char *name, void *object, void *clientData); - void WMReleaseNotification(WMNotification *notification); void* WMGetNotificationClientData(WMNotification *notification); @@ -613,8 +611,6 @@ const char* WMGetNotificationName(WMNotification *notification); void WMAddNotificationObserver(WMNotificationObserverAction *observerAction, void *observer, const char *name, void *object); -void WMPostNotification(WMNotification *notification); - void WMRemoveNotificationObserver(void *observer); void WMPostNotificationName(const char *name, void *object, void *clientData); diff --git a/WINGs/wtextfield.c b/WINGs/wtextfield.c index f8cf253e..b26ca5eb 100644 --- a/WINGs/wtextfield.c +++ b/WINGs/wtextfield.c @@ -68,12 +68,6 @@ typedef struct W_TextField { } flags; } TextField; -#define NOTIFY(T,C,N,A) { WMNotification *notif = WMCreateNotification(N,T,A);\ - if ((T)->delegate && (T)->delegate->C)\ - (*(T)->delegate->C)((T)->delegate,notif);\ - WMPostNotification(notif);\ - WMReleaseNotification(notif);} - #define MIN_TEXT_BUFFER 2 #define TEXT_BUFFER_INCR 8 @@ -906,7 +900,10 @@ static void handleEvents(XEvent * event, void *data) paintTextField(tPtr); - NOTIFY(tPtr, didBeginEditing, WMTextDidBeginEditingNotification, NULL); + if (tPtr->delegate && tPtr->delegate->didBeginEditing) { + (*tPtr->delegate->didBeginEditing)(tPtr->delegate, 0); + } + WMPostNotificationName(WMTextDidBeginEditingNotification, tPtr, NULL); tPtr->flags.notIllegalMovement = 0; break; @@ -921,8 +918,10 @@ static void handleEvents(XEvent * event, void *data) paintTextField(tPtr); if (!tPtr->flags.notIllegalMovement) { - NOTIFY(tPtr, didEndEditing, WMTextDidEndEditingNotification, - (void *)WMIllegalTextMovement); + if (tPtr->delegate && tPtr->delegate->didEndEditing) { + (*tPtr->delegate->didEndEditing)(tPtr->delegate, WMIllegalTextMovement); + } + WMPostNotificationName(WMTextDidEndEditingNotification, tPtr, (void *)WMIllegalTextMovement); } break; @@ -943,7 +942,8 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) char buffer[64]; KeySym ksym; const char *textEvent = NULL; - void *data = NULL; + WMTextMovementType movement_type; + WMTextFieldSpecialEventType special_field_event_type; int count, refresh = 0; int control_pressed = 0; int cancelSelection = 1; @@ -975,14 +975,14 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) tPtr->view->prevFocusChain); tPtr->flags.notIllegalMovement = 1; } - data = (void *)WMBacktabTextMovement; + movement_type = WMBacktabTextMovement; } else { if (tPtr->view->nextFocusChain) { W_SetFocusOfTopLevel(W_TopLevelOfView(tPtr->view), tPtr->view->nextFocusChain); tPtr->flags.notIllegalMovement = 1; } - data = (void *)WMTabTextMovement; + movement_type = WMTabTextMovement; } textEvent = WMTextDidEndEditingNotification; @@ -994,7 +994,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) case XK_Escape: if (!modified) { - data = (void *)WMEscapeTextMovement; + movement_type = WMEscapeTextMovement; textEvent = WMTextDidEndEditingNotification; relay = False; @@ -1008,7 +1008,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) /* FALLTHRU */ case XK_Return: if (!modified) { - data = (void *)WMReturnTextMovement; + movement_type = WMReturnTextMovement; textEvent = WMTextDidEndEditingNotification; relay = False; @@ -1165,7 +1165,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) if (!modified) { if (tPtr->selection.count) { WMDeleteTextFieldRange(tPtr, tPtr->selection); - data = (void *)WMDeleteTextEvent; + special_field_event_type = WMDeleteTextEvent; textEvent = WMTextDidChangeNotification; } else if (tPtr->cursorPosition > 0) { int i = oneUTF8CharBackward(&tPtr->text[tPtr->cursorPosition], @@ -1174,7 +1174,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) range.position = tPtr->cursorPosition + i; range.count = -i; WMDeleteTextFieldRange(tPtr, range); - data = (void *)WMDeleteTextEvent; + special_field_event_type = WMDeleteTextEvent; textEvent = WMTextDidChangeNotification; } @@ -1197,7 +1197,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) if (!modified) { if (tPtr->selection.count) { WMDeleteTextFieldRange(tPtr, tPtr->selection); - data = (void *)WMDeleteTextEvent; + special_field_event_type = WMDeleteTextEvent; textEvent = WMTextDidChangeNotification; } else if (tPtr->cursorPosition < tPtr->textLen) { WMRange range; @@ -1205,7 +1205,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) range.count = oneUTF8CharForward(&tPtr->text[tPtr->cursorPosition], tPtr->textLen - tPtr->cursorPosition); WMDeleteTextFieldRange(tPtr, range); - data = (void *)WMDeleteTextEvent; + special_field_event_type = WMDeleteTextEvent; textEvent = WMTextDidChangeNotification; } @@ -1220,7 +1220,7 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) if (tPtr->selection.count) WMDeleteTextFieldRange(tPtr, tPtr->selection); WMInsertTextFieldText(tPtr, buffer, tPtr->cursorPosition); - data = (void *)WMInsertTextEvent; + special_field_event_type = WMInsertTextEvent; textEvent = WMTextDidChangeNotification; relay = False; @@ -1255,21 +1255,22 @@ static void handleTextFieldKeyPress(TextField * tPtr, XEvent * event) /*printf("(%d,%d)\n", tPtr->selection.position, tPtr->selection.count); */ if (textEvent) { - WMNotification *notif = WMCreateNotification(textEvent, tPtr, data); - if (tPtr->delegate) { if (textEvent == WMTextDidBeginEditingNotification && tPtr->delegate->didBeginEditing) - (*tPtr->delegate->didBeginEditing) (tPtr->delegate, notif); + (*tPtr->delegate->didBeginEditing) (tPtr->delegate, movement_type); else if (textEvent == WMTextDidEndEditingNotification && tPtr->delegate->didEndEditing) - (*tPtr->delegate->didEndEditing) (tPtr->delegate, notif); + (*tPtr->delegate->didEndEditing) (tPtr->delegate, movement_type); else if (textEvent == WMTextDidChangeNotification && tPtr->delegate->didChange) - (*tPtr->delegate->didChange) (tPtr->delegate, notif); + (*tPtr->delegate->didChange) (tPtr->delegate, special_field_event_type); } - WMPostNotification(notif); - WMReleaseNotification(notif); + if (textEvent == WMTextDidBeginEditingNotification || textEvent == WMTextDidEndEditingNotification) { + WMPostNotificationName(textEvent, tPtr, (void *)movement_type); + } else if (textEvent == WMTextDidChangeNotification) { + WMPostNotificationName(textEvent, tPtr, (void *)special_field_event_type); + } } if (refresh) @@ -1345,7 +1346,10 @@ static void pasteText(WMView * view, Atom selection, Atom target, Time timestamp str = (char *)WMDataBytes(data); WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition); - NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent); + if (tPtr->delegate && tPtr->delegate->didChange) { + (*tPtr->delegate->didChange)(tPtr->delegate, WMInsertTextEvent); + } + WMPostNotificationName(WMTextDidChangeNotification, tPtr, (void *)WMInsertTextEvent); } else { int n; @@ -1355,7 +1359,10 @@ static void pasteText(WMView * view, Atom selection, Atom target, Time timestamp str[n] = 0; WMInsertTextFieldText(tPtr, str, tPtr->cursorPosition); XFree(str); - NOTIFY(tPtr, didChange, WMTextDidChangeNotification, (void *)WMInsertTextEvent); + if (tPtr->delegate && tPtr->delegate->didChange) { + (*tPtr->delegate->didChange)(tPtr->delegate, WMInsertTextEvent); + } + WMPostNotificationName(WMTextDidChangeNotification, tPtr, (void *)WMInsertTextEvent); } } } @@ -1477,8 +1484,10 @@ static void handleTextFieldActionEvents(XEvent * event, void *data) text[n] = 0; WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition); XFree(text); - NOTIFY(tPtr, didChange, WMTextDidChangeNotification, - (void *)WMInsertTextEvent); + if (tPtr->delegate && tPtr->delegate->didChange) { + (*tPtr->delegate->didChange)(tPtr->delegate, WMInsertTextEvent); + } + WMPostNotificationName(WMTextDidChangeNotification, tPtr, (void *)WMInsertTextEvent); } } else { tPtr->flags.waitingSelection = 1; diff --git a/WPrefs.app/editmenu.c b/WPrefs.app/editmenu.c index c84dc08a..4eb9e8f1 100644 --- a/WPrefs.app/editmenu.c +++ b/WPrefs.app/editmenu.c @@ -824,18 +824,15 @@ static void stopEditItem(WEditMenu * menu, Bool apply) menu->flags.isEditing = 0; } -static void textEndedEditing(struct WMTextFieldDelegate *self, WMNotification * notif) +static void textEndedEditing(struct WMTextFieldDelegate *self, WMTextMovementType reason) { WEditMenu *menu = (WEditMenu *) self->data; - uintptr_t reason; int i; WEditMenuItem *item; if (!menu->flags.isEditing) return; - reason = (uintptr_t)WMGetNotificationClientData(notif); - switch (reason) { case WMEscapeTextMovement: stopEditItem(menu, False); -- 2.39.5 From b7f765e3f6d2debd0ee71589602415fae1df762d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Wed, 12 Nov 2025 16:33:48 -0500 Subject: [PATCH 06/10] Rewrite WINGs/notification.c in Rust. --- WINGs/Makefile.am | 1 - WINGs/WINGs/WUtil.h | 2 - WINGs/notification.c | 278 -------------------------- WINGs/wapplication.c | 5 +- wutil-rs/Makefile.am | 1 + wutil-rs/src/lib.rs | 1 + wutil-rs/src/notification.rs | 368 +++++++++++++++++++++++++++++++++++ 7 files changed, 371 insertions(+), 285 deletions(-) delete mode 100644 WINGs/notification.c create mode 100644 wutil-rs/src/notification.rs diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 74b3ffea..8a1a839e 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -73,7 +73,6 @@ libWUtil_la_SOURCES = \ menuparser.h \ menuparser_macros.c \ misc.c \ - notification.c \ proplist.c \ userdefaults.c \ userdefaults.h \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index f290f187..d4c499f1 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -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); diff --git a/WINGs/notification.c b/WINGs/notification.c deleted file mode 100644 index 335189d7..00000000 --- a/WINGs/notification.c +++ /dev/null @@ -1,278 +0,0 @@ - -#include -#include -#include -#include - -#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); -} diff --git a/WINGs/wapplication.c b/WINGs/wapplication.c index 45070a22..312cbee9 100644 --- a/WINGs/wapplication.c +++ b/WINGs/wapplication.c @@ -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); diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index e2ee2d06..ab11b9c9 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -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 diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index ab94d66d..d2798268 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -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; diff --git a/wutil-rs/src/notification.rs b/wutil-rs/src/notification.rs new file mode 100644 index 00000000..dc56c51e --- /dev/null +++ b/wutil-rs/src/notification.rs @@ -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, + /// Optional side-channel data provided to notification listeners. + client_data: Option, +} + +/// 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, +} + +impl Sendable { + /// Creates a `Sendable` wrapping `ptr`. + /// + /// ## Safety + /// + /// `ptr` must be safe to send across threads. + pub unsafe fn new(ptr: NonNull) -> Self { + Sendable { ptr } + } +} + +impl hash::Hash for Sendable { + fn hash(&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, Action)>>, + /// Notification subscriptions that match on name. + by_name: HashMap<&'static CStr, Vec<(Option, Action)>>, + /// Notification subscriptions that match on source. + by_source: HashMap, Action)>>, + /// Notification subscriptions that match all notifications. + universal: Vec<(Option, 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(f: impl FnOnce(&mut Self) -> R) -> R { + static INSTANCE: OnceLock> = 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, + 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, + 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, + 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, 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, ¬ification); + } + } + } + + 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, ¬ification); + } + } + } + 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, ¬ification); + } + } + } + } + + for (observer, action) in &mut self.universal { + let observer = observer.map(|x| x.ptr.as_ptr()).unwrap_or(ptr::null_mut()); + unsafe { + (action)(observer, ¬ification); + } + } + } + + /// 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, + 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()); + } +} -- 2.39.5 From d8912c58e6d932e642cf92b9231fef231dfbdfc8 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 8 Dec 2025 13:06:58 -0500 Subject: [PATCH 07/10] Fix typo in comment. --- wutil-rs/src/notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wutil-rs/src/notification.rs b/wutil-rs/src/notification.rs index dc56c51e..1903ce03 100644 --- a/wutil-rs/src/notification.rs +++ b/wutil-rs/src/notification.rs @@ -49,7 +49,7 @@ pub type Action = unsafe extern "C" fn(observer: *mut c_void, notification: *con /// 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 +/// The `Send`-ability of the wrapped pointer must be guaranteed by code that /// instantiates a `Sendable`. #[derive(Clone, Copy, Eq, PartialEq)] pub struct Sendable { -- 2.39.5 From d8057575ce35ee4b937b7bb58437e0171dfd959b Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 8 Dec 2025 13:14:12 -0500 Subject: [PATCH 08/10] Use a helper method for adding notification listeners. --- wutil-rs/src/notification.rs | 46 ++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/wutil-rs/src/notification.rs b/wutil-rs/src/notification.rs index 1903ce03..d43d800d 100644 --- a/wutil-rs/src/notification.rs +++ b/wutil-rs/src/notification.rs @@ -1,11 +1,28 @@ use std::{ collections::{hash_map::Entry, HashMap}, ffi::{c_void, CStr}, - hash, + hash::{self, Hash}, ptr::{self, NonNull}, sync::{Mutex, OnceLock}, }; +// Helper function for adding the entry `(key, (observer, action))` to `map`. +fn register( + map: &mut HashMap, Action)>>, + key: K, + observer: Option, + action: Action, +) { + match map.entry(key) { + Entry::Occupied(mut o) => { + o.get_mut().push((observer, action)); + } + Entry::Vacant(v) => { + v.insert(vec![(observer, action)]); + } + } +} + /// Lightweight message from object to another. When a notification is sent, /// registered [`Action`]s are called. /// @@ -119,14 +136,7 @@ impl NotificationCenter { observer: Option, 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)]); - } - } + register(&mut self.exact, (name, source), observer, action); } /// Registers `action` to be invoked on `observer` when notifications are @@ -137,14 +147,7 @@ impl NotificationCenter { observer: Option, 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)]); - } - } + register(&mut self.by_source, source, observer, action); } /// Registers `action` to be invoked on `observer` for all notifications @@ -155,14 +158,7 @@ impl NotificationCenter { observer: Option, 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)]); - } - } + register(&mut self.by_name, name, observer, action); } /// Registers `action` to be invoked on `observer` for all notifications, -- 2.39.5 From 0893be1cea44386ab2148a584f5bc13d52115514 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 8 Dec 2025 13:24:44 -0500 Subject: [PATCH 09/10] Use BTreeMap instead of HashMap for notification subscriptions. This allows us to initialize the global NotificationCenter singleton in a const context, which eliminates the need for OnceLock. --- wutil-rs/src/notification.rs | 38 ++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/wutil-rs/src/notification.rs b/wutil-rs/src/notification.rs index d43d800d..45088a79 100644 --- a/wutil-rs/src/notification.rs +++ b/wutil-rs/src/notification.rs @@ -1,14 +1,13 @@ use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{btree_map::Entry, BTreeMap}, ffi::{c_void, CStr}, - hash::{self, Hash}, ptr::{self, NonNull}, - sync::{Mutex, OnceLock}, + sync::Mutex, }; // Helper function for adding the entry `(key, (observer, action))` to `map`. -fn register( - map: &mut HashMap, Action)>>, +fn register( + map: &mut BTreeMap, Action)>>, key: K, observer: Option, action: Action, @@ -68,7 +67,7 @@ pub type Action = unsafe extern "C" fn(observer: *mut c_void, notification: *con /// /// The `Send`-ability of the wrapped pointer must be guaranteed by code that /// instantiates a `Sendable`. -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] pub struct Sendable { ptr: NonNull, } @@ -84,23 +83,16 @@ impl Sendable { } } -impl hash::Hash for Sendable { - fn hash(&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, Action)>>, + exact: BTreeMap<(&'static CStr, Sendable), Vec<(Option, Action)>>, /// Notification subscriptions that match on name. - by_name: HashMap<&'static CStr, Vec<(Option, Action)>>, + by_name: BTreeMap<&'static CStr, Vec<(Option, Action)>>, /// Notification subscriptions that match on source. - by_source: HashMap, Action)>>, + by_source: BTreeMap, Action)>>, /// Notification subscriptions that match all notifications. universal: Vec<(Option, Action)>, } @@ -111,8 +103,13 @@ unsafe impl Send for NotificationCenter {} impl NotificationCenter { /// Creates a new `NotificationCenter`. - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + NotificationCenter { + exact: BTreeMap::new(), + by_name: BTreeMap::new(), + by_source: BTreeMap::new(), + universal: Vec::new(), + } } /// Provides access to the default, process-wide notification center. The @@ -120,9 +117,8 @@ impl NotificationCenter { /// mutex that is held while `f` is run, so panicking inside of `f` should /// be avoided. pub fn with_global_default(f: impl FnOnce(&mut Self) -> R) -> R { - static INSTANCE: OnceLock> = OnceLock::new(); + static INSTANCE: Mutex = Mutex::new(NotificationCenter::new()); f(&mut INSTANCE - .get_or_init(|| Mutex::new(NotificationCenter::new())) .try_lock() .unwrap()) } @@ -225,7 +221,7 @@ impl NotificationCenter { /// Clears all registered notification listeners and resets `self` to its /// default state. pub fn clear(&mut self) { - *self = Self::default(); + *self = Self::new(); } } -- 2.39.5 From d3dac752cc7193ebe041606c681e6fd8510225d6 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 8 Dec 2025 13:31:05 -0500 Subject: [PATCH 10/10] Indent with tabs, as is standard for this file. --- WINGs/wtextfield.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WINGs/wtextfield.c b/WINGs/wtextfield.c index b26ca5eb..5b7d1d1a 100644 --- a/WINGs/wtextfield.c +++ b/WINGs/wtextfield.c @@ -900,9 +900,9 @@ static void handleEvents(XEvent * event, void *data) paintTextField(tPtr); - if (tPtr->delegate && tPtr->delegate->didBeginEditing) { + if (tPtr->delegate && tPtr->delegate->didBeginEditing) { (*tPtr->delegate->didBeginEditing)(tPtr->delegate, 0); - } + } WMPostNotificationName(WMTextDidBeginEditingNotification, tPtr, NULL); tPtr->flags.notIllegalMovement = 0; @@ -1484,7 +1484,7 @@ static void handleTextFieldActionEvents(XEvent * event, void *data) text[n] = 0; WMInsertTextFieldText(tPtr, text, tPtr->cursorPosition); XFree(text); - if (tPtr->delegate && tPtr->delegate->didChange) { + if (tPtr->delegate && tPtr->delegate->didChange) { (*tPtr->delegate->didChange)(tPtr->delegate, WMInsertTextEvent); } WMPostNotificationName(WMTextDidChangeNotification, tPtr, (void *)WMInsertTextEvent); -- 2.39.5