/* xscreensaver, Copyright (c) 2006-2017 Jamie Zawinski * * 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 #include #include #include #include #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 */