Simplify WINGs handlers API and reimplement in Rust #17
@@ -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@
|
||||
|
||||
@@ -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 ]-------------------------------------------------- */
|
||||
|
||||
|
||||
@@ -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 ]----------------------------------------------- */
|
||||
|
||||
|
||||
554
WINGs/handlers.c
@@ -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 */
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
/// 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
cross
commented
(Similarly, a comment here would be nice.) (Similarly, a comment here would be nice.)
trurl
commented
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
cross
commented
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? 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?
trurl
commented
The name comes from the original C API, but I agree that it's misleading.
Yes. The repeated 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
cross
commented
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?
trurl
commented
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 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,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Nit: a couple of do comments on this struct and
callbackwould be nice.Done.