Simplify WINGs handlers API and reimplement in Rust #17

Merged
trurl merged 4 commits from trurl/wmaker:refactor/riir.wings-handlers into refactor/riir 2026-02-19 17:03:26 -05:00
12 changed files with 360 additions and 604 deletions

View File

@@ -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@

View File

@@ -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 ]-------------------------------------------------- */

View File

@@ -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 ]----------------------------------------------- */

View File

@@ -1,554 +0,0 @@
/*
* WINGs internal handlers: timer, idle and input handlers
*/
#include "wconfig.h"
#include "WINGsP.h"
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xos.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <time.h>
#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 */
}

View File

@@ -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 <poll.h>
#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)

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

317
wutil-rs/src/handlers.rs Normal file
View File

@@ -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<Box<TimerHandler>>,
idle_handlers: Vec<Box<IdleHandler>>,
}
/// Refers to a scheduled handler.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct HandlerId(Option<NonZero<usize>>);
/// 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<TimerHandler>> for HandlerId {
fn from(handler: &'a Box<TimerHandler>) -> HandlerId {
HandlerId(NonZero::new(
(handler.as_ref() as *const TimerHandler).addr(),
))
}
}
impl<'a> From<&'a Box<IdleHandler>> for HandlerId {
fn from(handler: &'a Box<IdleHandler>) -> 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<TimerHandler>) -> 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<dyn FnMut() + Send>,
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<dyn FnMut() + Send>,
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<Instant> {
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<dyn FnMut() + Send>) -> 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
trurl marked this conversation as resolved Outdated
Outdated
Review

Nit: a couple of do comments on this struct and callback would be nice.

Nit: a couple of do comments on this struct and `callback` would be nice.
Outdated
Review

Done.

Done.
/// 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<dyn FnMut() + Send>,
/// The next time that the handler should be called.
trurl marked this conversation as resolved Outdated
Outdated
Review

(Similarly, a comment here would be nice.)

(Similarly, a comment here would be nice.)
Outdated
Review

Done.

Done.
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<dyn FnMut() + Send>,
}
static DEFAULT_HANDLERS: Mutex<HandlerQueues> = Mutex::new(HandlerQueues::new());
fn with_global_handlers<R>(f: impl FnOnce(&mut HandlerQueues) -> R) -> R {
f(&mut DEFAULT_HANDLERS.try_lock().unwrap())
trurl marked this conversation as resolved Outdated
Outdated
Review

I don't quite get this; it appears to not just check, but consume and drop, global idle handlers. Perhaps the name could be changed to reflect that? run_global_idle_handlers or something? The repeated with_global_handlers blocks are interesting; I believe that's intended for the case where an idle handler is added during processing?

I don't quite get this; it appears to not just check, but consume and drop, global idle handlers. Perhaps the name could be changed to reflect that? `run_global_idle_handlers` or something? The repeated `with_global_handlers` blocks are interesting; I believe that's intended for the case where an idle handler is added during processing?
Outdated
Review

The name comes from the original C API, but I agree that it's misleading. run_global_idle_handlers is certainly better. Change made.

The repeated with_global_handlers blocks are interesting; I believe that's intended for the case where an idle handler is added during processing?

Yes. The repeated with_global_handlers blocks are written that way in case a callback tries to touch the global queue (which would fail if the callback is invoked from within with_global_handlers because it's inside a mutex). The original C implementation does something similar (copying the global idleHandler array in W_CheckIdleHandlers and iterating over the copy).

The name comes from the original C API, but I agree that it's misleading. `run_global_idle_handlers` is certainly better. Change made. > The repeated with_global_handlers blocks are interesting; I believe that's intended for the case where an idle handler is added during processing? Yes. The repeated `with_global_handlers` blocks are written that way in case a callback tries to touch the global queue (which would fail if the callback is invoked from within `with_global_handlers` because it's inside a mutex). The original C implementation does something similar (copying the global `idleHandler` array in `W_CheckIdleHandlers` and iterating over the copy).
}
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
}
})
}
trurl marked this conversation as resolved Outdated
Outdated
Review

Is there a good reason not to change the type signature on the C side to make this explicitly unsigned? A negative number there feels relativistically impossible?

Is there a good reason not to change the type signature on the C side to make this explicitly unsigned? A negative number there feels relativistically impossible?
Outdated
Review

I've been trying to keep things compatible with the original headers, but I have definitely broken source-level compatibility. So, yeah, let's use unsigned where appropriate.

I've been trying to keep things compatible with the original headers, but I have definitely broken source-level compatibility. So, yeah, let's use `unsigned` where appropriate.
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<unsafe extern "C" fn(data: *mut c_void)>,
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<unsafe extern "C" fn(data: *mut c_void)>,
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<unsafe extern "C" fn(data: *mut c_void)>,
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,
}
})
}
}

View File

@@ -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;