diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 6b086343..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 \ @@ -77,8 +76,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..56829a92 100644 --- a/WINGs/WINGs/WINGsP.h.in +++ b/WINGs/WINGs/WINGsP.h.in @@ -369,8 +369,12 @@ Bool W_CheckIdleHandlers(void); void W_CheckTimerHandlers(void); -Bool W_HandleInputEvents(Bool waitForInput, int inputfd); - +/* + * 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 19f5449b..c3c5740c 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -297,32 +297,18 @@ 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 WMDeleteTimerWithClientData(void *cdata); - void WMDeleteTimerHandler(WMHandlerID handlerID); 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 deleted file mode 100644 index 74f4fdd1..00000000 --- a/WINGs/handlers.c +++ /dev/null @@ -1,554 +0,0 @@ - -/* - * WINGs internal handlers: timer, idle and input handlers - */ - -#include "wconfig.h" -#include "WINGsP.h" - -#include -#include - -#include - -#ifdef HAVE_SYS_SELECT_H -# include -#endif - -#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; - -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); -} - -/* 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; - } -} - -static void 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 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; - - 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); -} - -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; - 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); - } - } -} - -/* - * 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) -{ -#if defined(HAVE_POLL) && defined(HAVE_POLL_H) && !defined(HAVE_SELECT) - struct poll fd *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); -#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 */ -} diff --git a/WINGs/wevent.c b/WINGs/wevent.c index 71be3d77..0f623076 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,18 @@ 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; + + timeout = waitForInput ? W_DelayUntilNextTimerEvent_millis() : 0; + + 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(); -} 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); 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;