/* xscreensaver, Copyright (c) 2006-2017 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
/* This is the portable implementation of Xt timers and inputs, for libjwxyz.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_JWXYZ /* whole file */
#undef DEBUG_TIMERS
#undef DEBUG_SOURCES
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#include "jwxyz.h"
#include "jwxyz-timers.h"
#ifdef HAVE_ANDROID
extern void Log(const char *format, ...);
#endif
#ifdef HAVE_COCOA
# define Log(S, ...) fprintf(stderr, "xscreensaver: " S "\n", __VA_ARGS__)
#endif
#ifdef DEBUG_TIMERS
# define LOGT(...) Log(__VA_ARGS__)
#else
# define LOGT(...)
#endif
#ifdef DEBUG_SOURCES
# define LOGI(...) Log(__VA_ARGS__)
#else
# define LOGI(...)
#endif
#define ASSERT_RET(C,S) do { \
if (!(C)) { \
jwxyz_abort ("jwxyz-timers: %s",(S)); \
return; \
}} while(0)
#define ASSERT_RET0(C,S) do { \
if (!(C)) { \
jwxyz_abort ("jwxyz-timers: %s",(S)); \
return 0; \
}} while(0)
XtAppContext
XtDisplayToApplicationContext (Display *dpy)
{
return (XtAppContext) dpy;
}
#define app_to_display(APP) ((Display *) (APP))
#define DISPLAY_SOURCES_DATA(APP) \
JWXYZ_VTBL(app_to_display (APP))->display_sources_data (app_to_display (APP))
struct jwxyz_sources_data {
int fd_count;
XtInputId ids[FD_SETSIZE];
XtIntervalId all_timers;
};
struct jwxyz_XtIntervalId {
XtAppContext app;
int refcount;
double run_at;
XtTimerCallbackProc cb;
XtPointer closure;
XtIntervalId next;
};
struct jwxyz_XtInputId {
XtAppContext app;
int refcount;
XtInputCallbackProc cb;
XtPointer closure;
int fd;
};
static double
double_time (void)
{
struct timeval now;
# ifdef GETTIMEOFDAY_TWO_ARGS
struct timezone tzp;
gettimeofday(&now, &tzp);
# else
gettimeofday(&now);
# endif
return (now.tv_sec + ((double) now.tv_usec * 0.000001));
}
jwxyz_sources_data *
jwxyz_sources_init (XtAppContext app)
{
jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
return td;
}
XtIntervalId
XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
XtTimerCallbackProc cb, XtPointer closure)
{
jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
XtIntervalId data = (XtIntervalId) calloc (1, sizeof(*data));
double now = double_time();
data->app = app;
data->cb = cb;
data->closure = closure;
data->refcount++;
data->run_at = now + (msecs / 1000.0);
data->next = td->all_timers;
td->all_timers = data;
LOGT("timer 0x%08lX: alloc %lu %.2f", (unsigned long) data, msecs,
data->run_at - now);
return data;
}
/* This is called both by the user to manually kill a timer,
and by the run loop after a timer has fired.
*/
void
XtRemoveTimeOut (XtIntervalId data)
{
jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (data->app);
LOGT("timer 0x%08lX: remove", (unsigned long) data);
ASSERT_RET (data->refcount > 0, "already freed");
data->refcount--;
LOGT("timer 0x%08lX: release %d", (unsigned long) data, data->refcount);
ASSERT_RET (data->refcount >= 0, "double free");
if (data->refcount == 0) {
/* Remove it from the list of live timers. */
XtIntervalId prev, timer;
int hit = 0;
for (timer = td->all_timers, prev = 0;
timer;
prev = timer, timer = timer->next) {
if (timer == data) {
ASSERT_RET (!hit, "circular timer list");
if (prev)
prev->next = timer->next;
else
td->all_timers = timer->next;
timer->next = 0;
hit = 1;
} else {
ASSERT_RET (timer->refcount > 0, "timer list corrupted");
}
}
free (data);
}
}
XtInputId
XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
XtInputCallbackProc cb, XtPointer closure)
{
jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
XtInputId data = (XtInputId) calloc (1, sizeof(*data));
data->cb = cb;
data->fd = fd;
data->closure = closure;
data->app = app;
data->refcount++;
LOGI("source 0x%08lX %2d: alloc", (unsigned long) data, data->fd);
ASSERT_RET0 (fd > 0 && fd < FD_SETSIZE, "fd out of range");
ASSERT_RET0 (td->ids[fd] == 0, "sources corrupted");
td->ids[fd] = data;
td->fd_count++;
return data;
}
void
XtRemoveInput (XtInputId id)
{
jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (id->app);
LOGI("source 0x%08lX %2d: remove", (unsigned long) id, id->fd);
ASSERT_RET (id->refcount > 0, "sources corrupted");
ASSERT_RET (td->fd_count > 0, "sources corrupted");
ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range");
ASSERT_RET (td->ids[id->fd] == id, "sources corrupted");
td->ids[id->fd] = 0;
td->fd_count--;
id->refcount--;
LOGI("source 0x%08lX %2d: release %d", (unsigned long) id, id->fd,
id->refcount);
ASSERT_RET (id->refcount >= 0, "double free");
if (id->refcount == 0) {
memset (id, 0xA1, sizeof(*id));
id->fd = -666;
free (id);
}
}
static void
jwxyz_timers_run (jwxyz_sources_data *td)
{
/* Iterate the timer list, being careful because XtRemoveTimeOut removes
the current item from that list. */
if (td->all_timers) {
XtIntervalId timer, next;
double now = double_time();
int count = 0;
for (timer = td->all_timers, next = timer->next;
timer;
timer = next, next = (timer ? timer->next : 0)) {
if (timer->run_at <= now) {
LOGT("timer 0x%08lX: fire %.02f", (unsigned long) timer,
now - timer->run_at);
timer->cb (timer->closure, &timer);
XtRemoveTimeOut (timer);
count++;
ASSERT_RET (count < 10000, "way too many timers to run");
}
}
}
}
static void
jwxyz_sources_run (jwxyz_sources_data *td)
{
if (td->fd_count == 0) return;
struct timeval tv = { 0, };
fd_set fds;
int i;
int max = 0;
FD_ZERO (&fds);
for (i = 0; i < FD_SETSIZE; i++) {
if (td->ids[i]) {
FD_SET (i, &fds);
max = i;
}
}
ASSERT_RET (max > 0, "no fds");
if (0 < select (max+1, &fds, NULL, NULL, &tv)) {
for (i = 0; i < FD_SETSIZE; i++) {
if (FD_ISSET (i, &fds)) {
XtInputId id = td->ids[i];
ASSERT_RET (id && id->cb, "sources corrupted");
ASSERT_RET (id->fd == i, "sources corrupted");
id->cb (id->closure, &id->fd, &id);
}
}
}
}
static void
jwxyz_XtRemoveInput_all (jwxyz_sources_data *td)
{
int i;
for (i = 0; i < FD_SETSIZE; i++) {
XtInputId id = td->ids[i];
if (id) XtRemoveInput (id);
}
}
static void
jwxyz_XtRemoveTimeOut_all (jwxyz_sources_data *td)
{
XtIntervalId timer, next;
int count = 0;
/* Iterate the timer list, being careful because XtRemoveTimeOut removes
the current item from that list. */
if (td->all_timers) {
for (timer = td->all_timers, next = timer->next;
timer;
timer = next, next = (timer ? timer->next : 0)) {
XtRemoveTimeOut (timer);
count++;
ASSERT_RET (count < 10000, "way too many timers to free");
}
ASSERT_RET (!td->all_timers, "timer list didn't empty");
}
}
void
jwxyz_sources_free (jwxyz_sources_data *td)
{
jwxyz_XtRemoveInput_all (td);
jwxyz_XtRemoveTimeOut_all (td);
memset (td, 0xA1, sizeof(*td));
free (td);
}
XtInputMask
XtAppPending (XtAppContext app)
{
return XtIMAlternateInput; /* just always say yes */
}
void
XtAppProcessEvent (XtAppContext app, XtInputMask mask)
{
jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
if (mask & XtIMAlternateInput)
jwxyz_sources_run (td);
if (mask & XtIMTimer)
jwxyz_timers_run (td);
}
#endif /* HAVE_JWXYZ */