From 5075c877fa00c2b10fe5a02462c742f6f808cb70 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 1/4] Remove WMDeleteTimerWithClientData. This function requires tracking an explict client data pointer, which it would be preferable not to have in the Rust rewrite. Removing this function entails tracking timer handlers explicitly, but this is not an undue burden. --- WINGs/WINGs/WUtil.h | 2 -- WINGs/handlers.c | 30 ------------------------------ src/menu.c | 4 ++-- src/menu.h | 1 + src/window.c | 4 ++-- 5 files changed, 5 insertions(+), 36 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 19f5449b..ee1c8b3c 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -303,8 +303,6 @@ WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback *callback, WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback *callback, void *cdata); -void WMDeleteTimerWithClientData(void *cdata); - void WMDeleteTimerHandler(WMHandlerID handlerID); WMHandlerID WMAddIdleHandler(WMCallback *callback, void *cdata); diff --git a/WINGs/handlers.c b/WINGs/handlers.c index 74f4fdd1..495b33ec 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -151,36 +151,6 @@ WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback * callback, return handler; } -void WMDeleteTimerWithClientData(void *cdata) -{ - TimerHandler *handler, *tmp; - - if (!cdata || !timerHandler) - return; - - tmp = timerHandler; - if (tmp->clientData == cdata) { - tmp->nextDelay = 0; - if (!IS_ZERO(tmp->when)) { - timerHandler = tmp->next; - wfree(tmp); - } - } else { - while (tmp->next) { - if (tmp->next->clientData == cdata) { - handler = tmp->next; - handler->nextDelay = 0; - if (IS_ZERO(handler->when)) - break; - tmp->next = handler->next; - wfree(handler); - break; - } - tmp = tmp->next; - } - } -} - void WMDeleteTimerHandler(WMHandlerID handlerID) { TimerHandler *tmp, *handler = (TimerHandler *) handlerID; diff --git a/src/menu.c b/src/menu.c index 2a83633b..3a6548f9 100644 --- a/src/menu.c +++ b/src/menu.c @@ -1608,7 +1608,7 @@ void wMenuScroll(WMenu *menu) XEvent ev; if (omenu->jump_back) - WMDeleteTimerWithClientData(omenu->jump_back); + WMDeleteTimerHandler(omenu->jump_back_timer); if (( /*omenu->flags.buttoned && */ !wPreferences.wrap_menus) || omenu->flags.app_menu) { @@ -1709,7 +1709,7 @@ void wMenuScroll(WMenu *menu) scr->flags.jump_back_pending = 1; } else delayer = omenu->jump_back; - WMAddTimerHandler(MENU_JUMP_BACK_DELAY, callback_leaving, delayer); + omenu->jump_back_timer = WMAddTimerHandler(MENU_JUMP_BACK_DELAY, callback_leaving, delayer); } } diff --git a/src/menu.h b/src/menu.h index 60ff9555..3fe8a4bc 100644 --- a/src/menu.h +++ b/src/menu.h @@ -91,6 +91,7 @@ typedef struct WMenu { WMHandlerID timer; /* timer for the autoscroll */ void *jump_back; /* jump back data */ + WMHandlerID jump_back_timer; /* set when jump_back is being used */ /* to be called when some entry is edited */ void (*on_edit)(struct WMenu *menu, struct WMenuEntry *entry); diff --git a/src/window.c b/src/window.c index 6c2a01be..8f686786 100644 --- a/src/window.c +++ b/src/window.c @@ -1648,9 +1648,9 @@ void wUnmanageWindow(WWindow *wwin, Bool restore, Bool destroyed) wSelectWindow(wwin, False); /* remove all pending events on window */ - /* I think this only matters for autoraise */ + /* I think this only matters for autoraise. see event.c:1197. */ if (wPreferences.raise_delay) - WMDeleteTimerWithClientData(wwin->frame->core); + WMDeleteTimerHandler(wwin->frame->core->screen_ptr->autoRaiseTimer); XFlush(dpy); -- 2.39.5 From ce97a3b39f46767d266b74d87197f452c924de41 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 2/4] Drop select(2)-based input FD polling. This is an intermediate step in the process of dropping input FD polling entirely (except for pumping the X11 event queue). There is code to use poll(2) instead of select(2) to wait for events on file descriptors (which includes waiting for events from the X11 event queue). It appears not to have been used in quite some time, or perhaps never, as it contains a typo (`poll fd` instead of `pollfd` on its first line). To reduce code bloat and make source code easier to navigate, it makes sense to prefer only one of these codepaths. After correcting this typo, waiting for X11 events appears to work, so the poll(2)-based code seems to be good enough. Keeping the poll-based code is vaguely preferable because poll(2) has a slightly better interface than select(2), and poll(2) has been a standard Unix API for 30 years. Since this code is slated to be replaced, the decision to use the poll(2)-based code here is probably not very important. But future programmers may want to use this as a branching-off point for switching to epoll(7) or some other, less-select(2)-like interface. --- WINGs/handlers.c | 107 ++++------------------------------------------- 1 file changed, 7 insertions(+), 100 deletions(-) diff --git a/WINGs/handlers.c b/WINGs/handlers.c index 495b33ec..c81ce97c 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -11,8 +11,12 @@ #include -#ifdef HAVE_SYS_SELECT_H -# include +#if !HAVE_POLL +# error poll(2) is not present on this system +#endif + +#if HAVE_POLL_H +# include #endif #include @@ -335,8 +339,7 @@ void W_CheckTimerHandlers(void) */ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) { -#if defined(HAVE_POLL) && defined(HAVE_POLL_H) && !defined(HAVE_SELECT) - struct poll fd *fds; + struct pollfd *fds; InputHandler *handler; int count, timeout, nfds, i, extrafd; @@ -425,100 +428,4 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) wfree(fds); return (count > 0); -#else -#ifdef HAVE_SELECT - struct timeval timeout; - struct timeval *timeoutPtr; - fd_set rset, wset, eset; - int maxfd, nfds, i; - int count; - InputHandler *handler; - - if (inputHandler) - nfds = WMGetArrayItemCount(inputHandler); - else - nfds = 0; - - if (inputfd < 0 && nfds == 0) { - return False; - } - - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&eset); - - if (inputfd < 0) { - maxfd = 0; - } else { - FD_SET(inputfd, &rset); - maxfd = inputfd; - } - - /* use WM_ITERATE_ARRAY() here */ - for (i = 0; i < nfds; i++) { - handler = WMGetFromArray(inputHandler, i); - if (handler->mask & WIReadMask) - FD_SET(handler->fd, &rset); - - if (handler->mask & WIWriteMask) - FD_SET(handler->fd, &wset); - - if (handler->mask & WIExceptMask) - FD_SET(handler->fd, &eset); - - if (maxfd < handler->fd) - maxfd = handler->fd; - } - - /* - * Setup the timeout to the estimated time until the - * next timer expires. - */ - if (!waitForInput) { - SET_ZERO(timeout); - timeoutPtr = &timeout; - } else if (timerPending()) { - delayUntilNextTimerEvent(&timeout); - timeoutPtr = &timeout; - } else { - timeoutPtr = (struct timeval *)0; - } - - count = select(1 + maxfd, &rset, &wset, &eset, timeoutPtr); - - if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); - int mask; - - /* use WM_ITERATE_ARRAY() here */ - for (i = 0; i < nfds; i++) { - handler = WMGetFromArray(handlerCopy, i); - /* check if the handler still exist or was removed by a callback */ - if (WMGetFirstInArray(inputHandler, handler) == WANotFound) - continue; - - mask = 0; - - if ((handler->mask & WIReadMask) && FD_ISSET(handler->fd, &rset)) - mask |= WIReadMask; - - if ((handler->mask & WIWriteMask) && FD_ISSET(handler->fd, &wset)) - mask |= WIWriteMask; - - if ((handler->mask & WIExceptMask) && FD_ISSET(handler->fd, &eset)) - mask |= WIExceptMask; - - if (mask != 0 && handler->callback) { - (*handler->callback) (handler->fd, mask, handler->clientData); - } - } - - WMFreeArray(handlerCopy); - } - - return (count > 0); -#else /* not HAVE_SELECT, not HAVE_POLL */ -# error Neither select nor poll. You lose. -#endif /* HAVE_SELECT */ -#endif /* HAVE_POLL */ } -- 2.39.5 From 223a64286febc186790336b3968e4692d0ed4303 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 3/4] Remove WINGs input handlers. Input handlers date back to early versions of WINGs. They provide a way to listen for state changes on file descriptors (including networking sockets) as part of the WINGs event loop. This functionality is not actually used in the Window Maker source tree. It may have been more useful in the past, when WINGs provided a networking socket API. The status quo appears to be that this is largely dead code. Pumping the X11 event queue is special-cased in the input handling code, so it can stay in for now. --- WINGs/Makefile.am | 3 +- WINGs/WINGs/WINGsP.h.in | 3 +- WINGs/WINGs/WUtil.h | 12 --- WINGs/handlers.c | 188 +++------------------------------------- WINGs/wevent.c | 34 +++++++- WINGs/wutil.c | 24 ----- 6 files changed, 49 insertions(+), 215 deletions(-) delete mode 100644 WINGs/wutil.c diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 6b086343..9cd8999d 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -77,8 +77,7 @@ libWUtil_la_SOURCES = \ userdefaults.h \ usleep.c \ wapplication.c \ - wconfig.h \ - wutil.c + wconfig.h AM_CFLAGS = @PANGO_CFLAGS@ diff --git a/WINGs/WINGs/WINGsP.h.in b/WINGs/WINGs/WINGsP.h.in index 82644fee..7dd5ed4d 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -369,8 +369,9 @@ Bool W_CheckIdleHandlers(void); void W_CheckTimerHandlers(void); -Bool W_HandleInputEvents(Bool waitForInput, int inputfd); +int W_TimerPending(void); +void W_DelayUntilNextTimerEvent(struct timeval *delay); /* ---[ notification.c ]-------------------------------------------------- */ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index ee1c8b3c..6485674e 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -309,18 +309,6 @@ WMHandlerID WMAddIdleHandler(WMCallback *callback, void *cdata); void WMDeleteIdleHandler(WMHandlerID handlerID); -WMHandlerID WMAddInputHandler(int fd, int condition, WMInputProc *proc, - void *clientData); - -void WMDeleteInputHandler(WMHandlerID handlerID); - - -/* This function is used _only_ if you create a non-GUI program. - * For GUI based programs use WMNextEvent()/WMHandleEvent() instead. - * This function will handle all input/timer/idle events, then return. - */ - -void WHandleEvents(void); /* ---[ wutil-rs/src/hash_table.rs ]----------------------------------------------- */ diff --git a/WINGs/handlers.c b/WINGs/handlers.c index c81ce97c..de202bcf 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -11,14 +11,6 @@ #include -#if !HAVE_POLL -# error poll(2) is not present on this system -#endif - -#if HAVE_POLL_H -# include -#endif - #include #ifndef X_GETTIMEOFDAY @@ -38,22 +30,11 @@ typedef struct IdleHandler { void *clientData; } IdleHandler; -typedef struct InputHandler { - WMInputProc *callback; - void *clientData; - int fd; - int mask; -} InputHandler; - /* queue of timer event handlers */ static TimerHandler *timerHandler = NULL; static WMArray *idleHandler = NULL; -static WMArray *inputHandler = NULL; - -#define timerPending() (timerHandler) - static void rightNow(struct timeval *tv) { X_GETTIMEOFDAY(tv); @@ -95,7 +76,19 @@ static void enqueueTimerHandler(TimerHandler * handler) } } -static void delayUntilNextTimerEvent(struct timeval *delay) +/* + * Returns non-0 iff a timer event is scheduled. + */ +int W_TimerPending(void) +{ + return !!timerHandler; +} + +/* + * Finds the next timer event and sets delay to the time when it should run. + * Call only when W_TimerPending() is true. + */ +void W_DelayUntilNextTimerEvent(struct timeval *delay) { struct timeval now; TimerHandler *handler; @@ -213,34 +206,6 @@ void WMDeleteIdleHandler(WMHandlerID handlerID) WMRemoveFromArray(idleHandler, handler); } -WMHandlerID WMAddInputHandler(int fd, int condition, WMInputProc * proc, void *clientData) -{ - InputHandler *handler; - - handler = wmalloc(sizeof(InputHandler)); - - handler->fd = fd; - handler->mask = condition; - handler->callback = proc; - handler->clientData = clientData; - - if (!inputHandler) - inputHandler = WMCreateArrayWithDestructor(16, wfree); - WMAddToArray(inputHandler, handler); - - return handler; -} - -void WMDeleteInputHandler(WMHandlerID handlerID) -{ - InputHandler *handler = (InputHandler *) handlerID; - - if (!handler || !inputHandler) - return; - - WMRemoveFromArray(inputHandler, handler); -} - Bool W_CheckIdleHandlers(void) { IdleHandler *handler; @@ -302,130 +267,3 @@ void W_CheckTimerHandlers(void) } } } - -/* - * This functions will handle input events on all registered file descriptors. - * Input: - * - waitForInput - True if we want the function to wait until an event - * appears on a file descriptor we watch, False if we - * want the function to immediately return if there is - * no data available on the file descriptors we watch. - * - inputfd - Extra input file descriptor to watch for input. - * This is only used when called from wevent.c to watch - * on ConnectionNumber(dpy) to avoid blocking of X events - * if we wait for input from other file handlers. - * Output: - * if waitForInput is False, the function will return False if there are no - * input handlers registered, or if there is no data - * available on the registered ones, and will return True - * if there is at least one input handler that has data - * available. - * if waitForInput is True, the function will return False if there are no - * input handlers registered, else it will block until an - * event appears on one of the file descriptors it watches - * and then it will return True. - * - * If the retured value is True, the input handlers for the corresponding file - * descriptors are also called. - * - * Parametersshould be passed like this: - * - from wevent.c: - * waitForInput - apropriate value passed by the function who called us - * inputfd = ConnectionNumber(dpy) - * - from wutil.c: - * waitForInput - apropriate value passed by the function who called us - * inputfd = -1 - * - */ -Bool W_HandleInputEvents(Bool waitForInput, int inputfd) -{ - struct pollfd *fds; - InputHandler *handler; - int count, timeout, nfds, i, extrafd; - - extrafd = (inputfd < 0) ? 0 : 1; - - if (inputHandler) - nfds = WMGetArrayItemCount(inputHandler); - else - nfds = 0; - - if (!extrafd && nfds == 0) { - return False; - } - - fds = wmalloc((nfds + extrafd) * sizeof(struct pollfd)); - if (extrafd) { - /* put this to the end of array to avoid using ranges from 1 to nfds+1 */ - fds[nfds].fd = inputfd; - fds[nfds].events = POLLIN; - } - - /* use WM_ITERATE_ARRAY() here */ - for (i = 0; i < nfds; i++) { - handler = WMGetFromArray(inputHandler, i); - fds[i].fd = handler->fd; - fds[i].events = 0; - if (handler->mask & WIReadMask) - fds[i].events |= POLLIN; - - if (handler->mask & WIWriteMask) - fds[i].events |= POLLOUT; - -#if 0 /* FIXME */ - if (handler->mask & WIExceptMask) - FD_SET(handler->fd, &eset); -#endif - } - - /* - * Setup the timeout to the estimated time until the - * next timer expires. - */ - if (!waitForInput) { - timeout = 0; - } else if (timerPending()) { - struct timeval tv; - delayUntilNextTimerEvent(&tv); - timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; - } else { - timeout = -1; - } - - count = poll(fds, nfds + extrafd, timeout); - - if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); - int mask; - - /* use WM_ITERATE_ARRAY() here */ - for (i = 0; i < nfds; i++) { - handler = WMGetFromArray(handlerCopy, i); - /* check if the handler still exist or was removed by a callback */ - if (WMGetFirstInArray(inputHandler, handler) == WANotFound) - continue; - - mask = 0; - - if ((handler->mask & WIReadMask) && - (fds[i].revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI))) - mask |= WIReadMask; - - if ((handler->mask & WIWriteMask) && (fds[i].revents & (POLLOUT | POLLWRBAND))) - mask |= WIWriteMask; - - if ((handler->mask & WIExceptMask) && (fds[i].revents & (POLLHUP | POLLNVAL | POLLERR))) - mask |= WIExceptMask; - - if (mask != 0 && handler->callback) { - (*handler->callback) (handler->fd, mask, handler->clientData); - } - } - - WMFreeArray(handlerCopy); - } - - wfree(fds); - - return (count > 0); -} diff --git a/WINGs/wevent.c b/WINGs/wevent.c index 71be3d77..2f94718a 100644 --- a/WINGs/wevent.c +++ b/WINGs/wevent.c @@ -3,8 +3,17 @@ * This event handling stuff was inspired on Tk. */ +#include "wconfig.h" #include "WINGsP.h" +#if !HAVE_POLL +# error poll(2) is not present on this system +#endif + +#if HAVE_POLL_H +# include +#endif + /* table to map event types to event masks */ static const unsigned long eventMasks[] = { 0, @@ -349,6 +358,9 @@ int WMIsDoubleClick(XEvent * event) */ static Bool waitForEvent(Display * dpy, unsigned long xeventmask, Bool waitForInput) { + struct pollfd pfd; + int timeout, inputfd; + XSync(dpy, False); if (xeventmask == 0) { if (XPending(dpy)) @@ -361,7 +373,27 @@ static Bool waitForEvent(Display * dpy, unsigned long xeventmask, Bool waitForIn } } - return W_HandleInputEvents(waitForInput, ConnectionNumber(dpy)); + inputfd = ConnectionNumber(dpy); + + if (inputfd < 0) { + return False; + } + + pfd.fd = inputfd; + pfd.events = POLLIN; + + if (!waitForInput) { + timeout = 0; + } else if (W_TimerPending()) { + /* Wait until the around when the next timer will fire. */ + struct timeval tv; + W_DelayUntilNextTimerEvent(&tv); + timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; + } else { + timeout = -1; + } + + return poll(&pfd, 1, timeout) > 0; } void WMNextEvent(Display * dpy, XEvent * event) diff --git a/WINGs/wutil.c b/WINGs/wutil.c deleted file mode 100644 index 6125a384..00000000 --- a/WINGs/wutil.c +++ /dev/null @@ -1,24 +0,0 @@ - -/* - * Handle events for non-GUI based applications - */ - -#include "WINGsP.h" - -void WHandleEvents(void) -{ - /* Check any expired timers */ - W_CheckTimerHandlers(); - - /* Do idle and timer stuff while there are no input events */ - /* Do not wait for input here. just peek to see if input is available */ - while (!W_HandleInputEvents(False, -1) && W_CheckIdleHandlers()) { - /* dispatch timer events */ - W_CheckTimerHandlers(); - } - - W_HandleInputEvents(True, -1); - - /* Check any expired timers */ - W_CheckTimerHandlers(); -} -- 2.39.5 From 7c7b9aa97c7e4bd4c6c12f8bed38250614be85b7 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Mon, 15 Dec 2025 13:52:37 -0500 Subject: [PATCH 4/4] Rewrite WINGs handlers.c in Rust. --- WINGs/Makefile.am | 1 - WINGs/WINGs/WINGsP.h.in | 9 +- WINGs/WINGs/WUtil.h | 4 +- WINGs/handlers.c | 269 --------------------------------- WINGs/wevent.c | 11 +- wutil-rs/Makefile.am | 4 + wutil-rs/src/handlers.rs | 317 +++++++++++++++++++++++++++++++++++++++ wutil-rs/src/lib.rs | 1 + 8 files changed, 331 insertions(+), 285 deletions(-) delete mode 100644 WINGs/handlers.c create mode 100644 wutil-rs/src/handlers.rs diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 9cd8999d..22c4b25c 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -67,7 +67,6 @@ libWUtil_la_SOURCES = \ error.c \ error.h \ findfile.c \ - handlers.c \ menuparser.c \ menuparser.h \ menuparser_macros.c \ diff --git a/WINGs/WINGs/WINGsP.h.in b/WINGs/WINGs/WINGsP.h.in index 7dd5ed4d..56829a92 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -369,9 +369,12 @@ Bool W_CheckIdleHandlers(void); void W_CheckTimerHandlers(void); -int W_TimerPending(void); - -void W_DelayUntilNextTimerEvent(struct timeval *delay); +/* + * Returns the duration in milliseconds until the next timer event should go off + * (saturating at INT_MAX). If there is no such timer event, returns a negative + * value. + */ +int W_DelayUntilNextTimerEvent_millis(); /* ---[ notification.c ]-------------------------------------------------- */ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 6485674e..c3c5740c 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -297,10 +297,10 @@ void wusleep(unsigned int usec); /* Event handlers: timer, idle, input */ -WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback *callback, +WMHandlerID WMAddTimerHandler(unsigned milliseconds, WMCallback *callback, void *cdata); -WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback *callback, +WMHandlerID WMAddPersistentTimerHandler(unsigned milliseconds, WMCallback *callback, void *cdata); void WMDeleteTimerHandler(WMHandlerID handlerID); diff --git a/WINGs/handlers.c b/WINGs/handlers.c deleted file mode 100644 index de202bcf..00000000 --- a/WINGs/handlers.c +++ /dev/null @@ -1,269 +0,0 @@ - -/* - * WINGs internal handlers: timer, idle and input handlers - */ - -#include "wconfig.h" -#include "WINGsP.h" - -#include -#include - -#include - -#include - -#ifndef X_GETTIMEOFDAY -#define X_GETTIMEOFDAY(t) gettimeofday(t, (struct timezone*)0) -#endif - -typedef struct TimerHandler { - WMCallback *callback; /* procedure to call */ - struct timeval when; /* when to call the callback */ - void *clientData; - struct TimerHandler *next; - int nextDelay; /* 0 if it's one-shot */ -} TimerHandler; - -typedef struct IdleHandler { - WMCallback *callback; - void *clientData; -} IdleHandler; - -/* queue of timer event handlers */ -static TimerHandler *timerHandler = NULL; - -static WMArray *idleHandler = NULL; - -static void rightNow(struct timeval *tv) -{ - X_GETTIMEOFDAY(tv); -} - -/* is t1 after t2 ? */ -#define IS_AFTER(t1, t2) (((t1).tv_sec > (t2).tv_sec) || \ - (((t1).tv_sec == (t2).tv_sec) \ - && ((t1).tv_usec > (t2).tv_usec))) - -#define IS_ZERO(tv) (tv.tv_sec == 0 && tv.tv_usec == 0) - -#define SET_ZERO(tv) tv.tv_sec = 0, tv.tv_usec = 0 - -static void addmillisecs(struct timeval *tv, int milliseconds) -{ - tv->tv_usec += milliseconds * 1000; - - tv->tv_sec += tv->tv_usec / 1000000; - tv->tv_usec = tv->tv_usec % 1000000; -} - -static void enqueueTimerHandler(TimerHandler * handler) -{ - TimerHandler *tmp; - - /* insert callback in queue, sorted by time left */ - if (!timerHandler || !IS_AFTER(handler->when, timerHandler->when)) { - /* first in the queue */ - handler->next = timerHandler; - timerHandler = handler; - } else { - tmp = timerHandler; - while (tmp->next && IS_AFTER(handler->when, tmp->next->when)) { - tmp = tmp->next; - } - handler->next = tmp->next; - tmp->next = handler; - } -} - -/* - * Returns non-0 iff a timer event is scheduled. - */ -int W_TimerPending(void) -{ - return !!timerHandler; -} - -/* - * Finds the next timer event and sets delay to the time when it should run. - * Call only when W_TimerPending() is true. - */ -void W_DelayUntilNextTimerEvent(struct timeval *delay) -{ - struct timeval now; - TimerHandler *handler; - - handler = timerHandler; - while (handler && IS_ZERO(handler->when)) - handler = handler->next; - - if (!handler) { - /* The return value of this function is only valid if there _are_ - timers active. */ - delay->tv_sec = 0; - delay->tv_usec = 0; - return; - } - - rightNow(&now); - if (IS_AFTER(now, handler->when)) { - delay->tv_sec = 0; - delay->tv_usec = 0; - } else { - delay->tv_sec = handler->when.tv_sec - now.tv_sec; - delay->tv_usec = handler->when.tv_usec - now.tv_usec; - if (delay->tv_usec < 0) { - delay->tv_usec += 1000000; - delay->tv_sec--; - } - } -} - -WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback * callback, void *cdata) -{ - TimerHandler *handler; - - handler = wmalloc(sizeof(TimerHandler)); - if (!handler) - return NULL; - - rightNow(&handler->when); - addmillisecs(&handler->when, milliseconds); - handler->callback = callback; - handler->clientData = cdata; - handler->nextDelay = 0; - - enqueueTimerHandler(handler); - - return handler; -} - -WMHandlerID WMAddPersistentTimerHandler(int milliseconds, WMCallback * callback, void *cdata) -{ - TimerHandler *handler = WMAddTimerHandler(milliseconds, callback, cdata); - - if (handler != NULL) - handler->nextDelay = milliseconds; - - return handler; -} - -void WMDeleteTimerHandler(WMHandlerID handlerID) -{ - TimerHandler *tmp, *handler = (TimerHandler *) handlerID; - - if (!handler || !timerHandler) - return; - - tmp = timerHandler; - - handler->nextDelay = 0; - - if (IS_ZERO(handler->when)) - return; - - if (tmp == handler) { - timerHandler = handler->next; - wfree(handler); - } else { - while (tmp->next) { - if (tmp->next == handler) { - tmp->next = handler->next; - wfree(handler); - break; - } - tmp = tmp->next; - } - } -} - -WMHandlerID WMAddIdleHandler(WMCallback * callback, void *cdata) -{ - IdleHandler *handler; - - handler = wmalloc(sizeof(IdleHandler)); - if (!handler) - return NULL; - - handler->callback = callback; - handler->clientData = cdata; - /* add handler at end of queue */ - if (!idleHandler) { - idleHandler = WMCreateArrayWithDestructor(16, wfree); - } - WMAddToArray(idleHandler, handler); - - return handler; -} - -void WMDeleteIdleHandler(WMHandlerID handlerID) -{ - IdleHandler *handler = (IdleHandler *) handlerID; - - if (!handler || !idleHandler) - return; - - WMRemoveFromArray(idleHandler, handler); -} - -Bool W_CheckIdleHandlers(void) -{ - IdleHandler *handler; - WMArray *handlerCopy; - WMArrayIterator iter; - - if (!idleHandler || WMGetArrayItemCount(idleHandler) == 0) { - /* make sure an observer in queue didn't added an idle handler */ - return (idleHandler != NULL && WMGetArrayItemCount(idleHandler) > 0); - } - - handlerCopy = WMCreateArrayWithArray(idleHandler); - - WM_ITERATE_ARRAY(handlerCopy, handler, iter) { - /* check if the handler still exist or was removed by a callback */ - if (WMGetFirstInArray(idleHandler, handler) == WANotFound) - continue; - - (*handler->callback) (handler->clientData); - WMDeleteIdleHandler(handler); - } - - WMFreeArray(handlerCopy); - - /* this is not necesarrily False, because one handler can re-add itself */ - return (WMGetArrayItemCount(idleHandler) > 0); -} - -void W_CheckTimerHandlers(void) -{ - TimerHandler *handler; - struct timeval now; - - if (!timerHandler) { - return; - } - - rightNow(&now); - - handler = timerHandler; - while (handler && IS_AFTER(now, handler->when)) { - if (!IS_ZERO(handler->when)) { - SET_ZERO(handler->when); - (*handler->callback) (handler->clientData); - } - handler = handler->next; - } - - while (timerHandler && IS_ZERO(timerHandler->when)) { - handler = timerHandler; - timerHandler = timerHandler->next; - - if (handler->nextDelay > 0) { - handler->when = now; - addmillisecs(&handler->when, handler->nextDelay); - enqueueTimerHandler(handler); - } else { - wfree(handler); - } - } -} diff --git a/WINGs/wevent.c b/WINGs/wevent.c index 2f94718a..0f623076 100644 --- a/WINGs/wevent.c +++ b/WINGs/wevent.c @@ -382,16 +382,7 @@ static Bool waitForEvent(Display * dpy, unsigned long xeventmask, Bool waitForIn pfd.fd = inputfd; pfd.events = POLLIN; - if (!waitForInput) { - timeout = 0; - } else if (W_TimerPending()) { - /* Wait until the around when the next timer will fire. */ - struct timeval tv; - W_DelayUntilNextTimerEvent(&tv); - timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; - } else { - timeout = -1; - } + timeout = waitForInput ? W_DelayUntilNextTimerEvent_millis() : 0; return poll(&pfd, 1, timeout) > 0; } diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index bb9617e4..fa126788 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -8,6 +8,7 @@ RUST_SOURCES = \ src/defines.rs \ src/find_file.rs \ src/hash_table.rs \ + src/handlers.rs \ src/lib.rs \ src/memory.rs \ src/notification.rs \ @@ -20,6 +21,9 @@ RUST_EXTRA = \ Cargo.lock \ Cargo.toml +Cargo.lock: + $(CARGO) build + target/debug/libwutil_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) $(CARGO) build diff --git a/wutil-rs/src/handlers.rs b/wutil-rs/src/handlers.rs new file mode 100644 index 00000000..0c456155 --- /dev/null +++ b/wutil-rs/src/handlers.rs @@ -0,0 +1,317 @@ +use std::{ + mem, + num::NonZero, + sync::Mutex, + time::{Duration, Instant}, +}; + +/// Tracks callbacks that are registered to run. +/// +/// Callbacks may be scheduled to run after a duration of time or when there is +/// spare time to run them. +pub struct HandlerQueues { + timer_handlers: Vec>, + idle_handlers: Vec>, +} + +/// Refers to a scheduled handler. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct HandlerId(Option>); + +/// Returned by handler queue predicate(s) to describe if there are pending +/// callbacks. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum QueueStatus { + Empty, + Pending, +} + +impl<'a> From<&'a Box> for HandlerId { + fn from(handler: &'a Box) -> HandlerId { + HandlerId(NonZero::new( + (handler.as_ref() as *const TimerHandler).addr(), + )) + } +} + +impl<'a> From<&'a Box> for HandlerId { + fn from(handler: &'a Box) -> HandlerId { + HandlerId(NonZero::new( + (handler.as_ref() as *const IdleHandler).addr(), + )) + } +} + +impl HandlerQueues { + /// Creates a new `HandlerQueues` with no pending tasks. + pub const fn new() -> Self { + HandlerQueues { + timer_handlers: Vec::new(), + idle_handlers: Vec::new(), + } + } + + /// Enqueues `handler` and returns its id. + fn enqueue_timer(&mut self, handler: Box) -> HandlerId { + let id = HandlerId::from(&handler); + let insert_at = match self + .timer_handlers + .binary_search_by(|t| t.when.cmp(&handler.when)) + { + Ok(i) => i, + Err(i) => i, + }; + self.timer_handlers.insert(insert_at, handler); + id + } + + /// Enqueues `callback` to be called once, at `when`, and + /// returns an id to refer to the scheduled task. + pub fn enqueue_once_timer( + &mut self, + callback: Box, + when: Instant, + ) -> HandlerId { + self.enqueue_timer(Box::new(TimerHandler { + callback, + when, + delay: Duration::ZERO, + })) + } + + /// Enqueues `callback` to be called repeatedly, first at `when` and + /// subsequently every `delay` time units. Returns an id to refer to the + /// schedule task. + pub fn enqueue_persistent_timer( + &mut self, + callback: Box, + when: Instant, + delay: Duration, + ) -> HandlerId { + self.enqueue_timer(Box::new(TimerHandler { + callback, + when, + delay, + })) + } + + /// Runs all queued timer handlers which should have run before `now`. + /// + /// If a timer handler that is run should repeat, it will be scheduled + /// again. + pub fn check_timer_handlers(&mut self, now: Instant) { + let mut reschedule = Vec::new(); + loop { + let Some(mut t) = self.timer_handlers.pop() else { + break; + }; + if t.when < now { + (t.callback)(); + if !t.delay.is_zero() { + t.when = now + t.delay; + reschedule.push(t); + } + } else { + reschedule.push(t); + break; + } + } + + for t in reschedule.into_iter() { + self.enqueue_timer(t); + } + } + + /// Drops `handler` if it is scheduled. Passing a `handler` that is not + /// scheduled is safe and will no-op. + pub fn delete_timer_handler(&mut self, handler: HandlerId) { + self.timer_handlers + .retain(|t| HandlerId::from(t) != handler); + } + + /// Returns the time when the next timer handler would be run, or `None` if + /// no timer handlers are scheduled. + pub fn next_timer_handler_time(&self) -> Option { + self.timer_handlers.first().map(|t| t.when) + } + + /// Schedules `callback` to be called once, when the event loop is idle, and + /// returns an id for the scheduled handler. + pub fn add_idle_handler(&mut self, callback: Box) -> HandlerId { + let handler = Box::new(IdleHandler { callback }); + let id = HandlerId::from(&handler); + self.idle_handlers.push(handler); + id + } + + /// Deletes `handler` if it is scheduled. Passing a `handler` that is not + /// scheduled is safe and will no-op. + pub fn delete_idle_handler(&mut self, handler: HandlerId) { + self.idle_handlers.retain(|h| HandlerId::from(h) != handler); + } + + /// Runs any idle handlers that are pending when this function is called and + /// returns the status of the idle handler queue. + /// + /// (Idle handlers may schedule new idle handlers, so the queue may be + /// non-empty after pending handlers are run.) + pub fn check_idle_handlers(&mut self) -> QueueStatus { + if self.idle_handlers.is_empty() { + return QueueStatus::Empty; + } + + let mut pending = Vec::new(); + mem::swap(&mut pending, &mut self.idle_handlers); + + for mut h in pending.into_iter() { + (h.callback)(); + } + + if self.idle_handlers.is_empty() { + QueueStatus::Empty + } else { + QueueStatus::Pending + } + } +} + +/// Tracks a callback, a time when it should be called, and an interval for +/// optionally repeating the callback. +/// +/// See [`HandlerQueues::enqueue_timer`] and +/// [`HandlerQueues::enqueue_persistent_timer`] for creation of `TimerHandler`s. +struct TimerHandler { + /// Called after `when` has been reached. + callback: Box, + /// The next time that the handler should be called. + when: Instant, + /// If non-zero indicates that this handler should repeat every `delay` time + /// units. This delay is best-effort only. + delay: Duration, +} + +/// Tracks a callback which should be called when the Window Maker event loop is +/// otherwise idle. +struct IdleHandler { + callback: Box, +} + +static DEFAULT_HANDLERS: Mutex = Mutex::new(HandlerQueues::new()); + +fn with_global_handlers(f: impl FnOnce(&mut HandlerQueues) -> R) -> R { + f(&mut DEFAULT_HANDLERS.try_lock().unwrap()) +} + +fn run_global_idle_handlers() -> QueueStatus { + // Move handlers out of the global queue because it is locked while + // with_global_handlers is running, and a callback may try to schedule + // another callback or perform some other operation on the global queue. + let handlers = with_global_handlers(|handlers| { + let mut idle_handlers = Vec::new(); + mem::swap(&mut idle_handlers, &mut handlers.idle_handlers); + idle_handlers + }); + for mut h in handlers.into_iter() { + (h.callback)(); + } + with_global_handlers(|handlers| { + if handlers.idle_handlers.is_empty() { + QueueStatus::Empty + } else { + QueueStatus::Pending + } + }) +} + +pub mod ffi { + use super::*; + use crate::sendable::Sendable; + use std::{ + ffi::{c_int, c_uint, c_void}, + time::Instant, + }; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddTimerHandler( + milliseconds: c_uint, + callback: Option, + cdata: *mut c_void, + ) -> HandlerId { + let cdata = unsafe { Sendable::from_nullable(cdata) }; + let callback = match callback { + None => return HandlerId(None), + Some(f) => Box::new(move || unsafe { f(Sendable::as_ptr(cdata)) }), + }; + with_global_handlers(|handlers| { + handlers.enqueue_once_timer( + callback, + Instant::now() + Duration::from_millis(milliseconds as u64), + ) + }) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddPersistentTimerHandler( + milliseconds: c_uint, + callback: Option, + cdata: *mut c_void, + ) -> HandlerId { + let cdata = unsafe { Sendable::from_nullable(cdata) }; + let callback = match callback { + None => return HandlerId(None), + Some(f) => Box::new(move || unsafe { f(Sendable::as_ptr(cdata)) }), + }; + let delay = Duration::from_millis(milliseconds as u64); + with_global_handlers(move |handlers| { + handlers.enqueue_persistent_timer(callback, Instant::now() + delay, delay) + }) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeleteTimerHandler(handler_id: HandlerId) { + with_global_handlers(|handlers| handlers.delete_timer_handler(handler_id)); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddIdleHandler( + callback: Option, + cdata: *mut c_void, + ) -> HandlerId { + let cdata = unsafe { Sendable::from_nullable(cdata) }; + let callback = match callback { + None => return HandlerId(None), + Some(f) => Box::new(move || unsafe { (f)(Sendable::as_ptr(cdata)) }), + }; + with_global_handlers(|handlers| handlers.add_idle_handler(callback)) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeleteIdleHandler(handler_id: HandlerId) { + with_global_handlers(|handlers| handlers.delete_idle_handler(handler_id)); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn W_CheckIdleHandlers() -> c_int { + c_int::from(run_global_idle_handlers() == QueueStatus::Pending) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn W_CheckTimerHandlers() { + with_global_handlers(|handlers| handlers.check_timer_handlers(Instant::now())); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn W_DelayUntilNextTimerEvent_millis() -> c_int { + with_global_handlers(|handlers| { + let now = Instant::now(); + match handlers.next_timer_handler_time() { + Some(when) if when > now => { + c_int::try_from((when - now).as_millis()).unwrap_or(c_int::MAX) + } + Some(_) => 0, + None => -1, + } + }) + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 33e09154..02681092 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -3,6 +3,7 @@ pub mod bag; pub mod data; pub mod defines; pub mod find_file; +pub mod handlers; pub mod hash_table; pub mod memory; pub mod notification; -- 2.39.5