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/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/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 060c1adb..d4c499f1 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -596,15 +596,9 @@ 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); @@ -615,33 +609,10 @@ 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 WMRemoveNotificationObserverWithName(void *observer, const char *name, - void *object); - 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/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 deleted file mode 100644 index d43fef54..00000000 --- a/WINGs/notification.c +++ /dev/null @@ -1,482 +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; - - -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); - } -} - -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 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; - - notification = WMCreateNotification(name, object, clientData); - - WMPostNotification(notification); - - 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; - -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; -} - -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 - -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); -} - -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; - } -} 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/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; } 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 diff --git a/WINGs/wtextfield.c b/WINGs/wtextfield.c index f8cf253e..5b7d1d1a 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); 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..45088a79 --- /dev/null +++ b/wutil-rs/src/notification.rs @@ -0,0 +1,360 @@ +use std::{ + collections::{btree_map::Entry, BTreeMap}, + ffi::{c_void, CStr}, + ptr::{self, NonNull}, + sync::Mutex, +}; + +// Helper function for adding the entry `(key, (observer, action))` to `map`. +fn register( + map: &mut BTreeMap, 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. +/// +/// 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 by code that +/// instantiates a `Sendable`. +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] +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 } + } +} + +// Guaranteed by `Sendable::new`. +unsafe impl Send for Sendable {} + +pub struct NotificationCenter { + /// Notification subscriptions that match on name and source. + exact: BTreeMap<(&'static CStr, Sendable), Vec<(Option, Action)>>, + /// Notification subscriptions that match on name. + by_name: BTreeMap<&'static CStr, Vec<(Option, Action)>>, + /// Notification subscriptions that match on source. + by_source: BTreeMap, 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 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 + /// 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: Mutex = Mutex::new(NotificationCenter::new()); + f(&mut INSTANCE + .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, + ) { + register(&mut self.exact, (name, source), 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, + ) { + register(&mut self.by_source, source, 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, + ) { + register(&mut self.by_name, name, 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::new(); + } +} + +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()); + } +}