-/* xscreensaver, Copyright (c) 2006 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2014 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
/* This is the OSX implementation of Xt timers, for libjwxyz.
*/
+//#define DEBUG_TIMERS
+//#define DEBUG_SOURCES
+
#import <stdlib.h>
-#import <Cocoa/Cocoa.h>
+
#import "jwxyz.h"
#import "jwxyz-timers.h"
-//#define DEBUG_TIMERS
-//#define DEBUG_SOURCES
-
-/* If this is defined, we implement timers in terms of CFRunLoopTimerCreate.
- But I couldn't get that to work right: when the left window was accepting
- input, the right window would starve. So I implemented them in terms of
- select() instead. This is more efficient anyway, since instead of the
- event loop telling us that input is ready constantly, we only check once
- just before the draw method is called.
- */
-#undef USE_COCOA_SOURCES
#ifdef DEBUG_TIMERS
-# define LOGT(str,arg1,arg2) NSLog(str,arg1,arg2)
+# define LOGT( str,arg1) NSLog(str,arg1)
+# define LOGT2(str,arg1,arg2) NSLog(str,arg1,arg2)
#else
-# define LOGT(str,arg1,arg2)
+# define LOGT( str,arg1)
+# define LOGT2(str,arg1,arg2)
#endif
#ifdef DEBUG_SOURCES
-# define LOGI(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
+# define LOGI( str,arg1,arg2) NSLog(str,arg1,arg2)
+# define LOGI2(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
#else
-# define LOGI(str,arg1,arg2,arg3)
+# define LOGI( str,arg1,arg2)
+# define LOGI2(str,arg1,arg2,arg3)
#endif
+#define ASSERT_RET(C,S) do { \
+ if (!(C)) { \
+ jwxyz_abort ("jwxyz-timers: %s",(S)); \
+ return; \
+ }} while(0)
+
XtAppContext
XtDisplayToApplicationContext (Display *dpy)
#define app_to_display(APP) ((Display *) (APP))
+struct jwxyz_sources_data {
+ int fd_count;
+ XtInputId ids[FD_SETSIZE];
+ struct jwxyz_XtIntervalId *all_timers;
+};
+
struct jwxyz_XtIntervalId {
+ XtAppContext app;
CFRunLoopTimerRef cftimer;
int refcount;
XtTimerCallbackProc cb;
XtPointer closure;
+
+ struct jwxyz_XtIntervalId *next;
};
struct jwxyz_XtInputId {
-# ifdef USE_COCOA_SOURCES
- CFRunLoopSourceRef cfsource;
- CFSocketRef socket;
-# else
XtAppContext app;
-# endif
int refcount;
XtInputCallbackProc cb;
{
struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
data->refcount++;
- LOGT(@"timer 0x%08X: retain %d\n", (unsigned int) data, data->refcount);
+ LOGT2(@"timer 0x%08X: retain %d", (unsigned int) data, data->refcount);
return arg;
}
+/* This is called both by the user to manually kill a timer (XtRemoveTimeOut)
+ and by the run loop after a timer has fired (CFRunLoopTimerInvalidate).
+ */
static void
jwxyz_timer_release (const void *arg)
{
struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
+ jwxyz_sources_data *td = display_sources_data (app_to_display (data->app));
+
data->refcount--;
- LOGT(@"timer 0x%08X: release %d\n", (unsigned int) data, data->refcount);
- if (data->refcount < 0) abort();
- if (data->refcount == 0) free (data);
+ LOGT2(@"timer 0x%08X: release %d", (unsigned int) 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);
+ }
}
static const void *
{
struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
data->refcount++;
- LOGI(@"source 0x%08X %2d: retain %d\n", (unsigned int) data, data->fd,
- data->refcount);
+ LOGI2(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd,
+ data->refcount);
return arg;
}
{
struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
data->refcount--;
- LOGI(@"source 0x%08X %2d: release %d\n", (unsigned int) data, data->fd,
- data->refcount);
- if (data->refcount < 0) abort();
+ LOGI2(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd,
+ data->refcount);
+ ASSERT_RET (data->refcount >= 0, "double free");
if (data->refcount == 0) {
-# ifdef USE_COCOA_SOURCES
- if (data->socket)
- CFRelease (data->socket);
- if (data->cfsource)
- CFRelease (data->cfsource);
-# endif /* USE_COCOA_SOURCES */
memset (data, 0xA1, sizeof(*data));
data->fd = -666;
free (data);
jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg)
{
struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
- LOGT(@"timer 0x%08X: fire\n", (unsigned int) data, 0);
+ LOGT(@"timer 0x%08X: fire", (unsigned int) data);
data->cb (data->closure, &data);
// Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate,
}
-#ifdef USE_COCOA_SOURCES
-
-/* whether there is data available to be read on the file descriptor
- */
-static int
-input_available_p (int fd)
-{
- struct timeval tv = { 0, };
- fd_set fds;
- FD_ZERO (&fds);
- FD_SET (fd, &fds);
- return select (fd+1, &fds, NULL, NULL, &tv);
-}
-
-
-static void
-jwxyz_source_cb (CFSocketRef s, CFSocketCallBackType type,
- CFDataRef address, const void *call_data, void *info)
-{
- struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) info;
-
- if (type != kCFSocketReadCallBack) abort();
- if (call_data != 0) abort(); // not used for kCFSocketRead
-
- // We are sometimes called when there is not, in fact, data available!
- // So don't call data->cb if we're being fed a pack of lies.
- //
- if (! input_available_p (data->fd)) {
- LOGI(@"source 0x%08X %2d: false alarm!\n", (unsigned int) data, data->fd, 0);
- return;
- }
-
- LOGI(@"source 0x%08X %2d: fire\n", (unsigned int) data, data->fd, 0);
-
- data->cb (data->closure, &data->fd, &data);
-}
-
-#endif /* USE_COCOA_SOURCES */
-
-
XtIntervalId
XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
XtTimerCallbackProc cb, XtPointer closure)
{
+ jwxyz_sources_data *td = display_sources_data (app_to_display (app));
struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *)
calloc (1, sizeof(*data));
+ data->app = app;
data->cb = cb;
data->closure = closure;
- LOGT(@"timer 0x%08X: alloc %d\n", (unsigned int) data, msecs);
+ LOGT2(@"timer 0x%08X: alloc %lu", (unsigned int) data, msecs);
CFRunLoopTimerContext ctx = { 0, };
ctx.info = data;
jwxyz_timer_cb, &ctx);
// CFRunLoopTimerCreate called jwxyz_timer_retain.
+ data->next = td->all_timers;
+ td->all_timers = data;
+
CFRunLoopAddTimer (CFRunLoopGetCurrent(), data->cftimer,
kCFRunLoopCommonModes);
return data;
void
XtRemoveTimeOut (XtIntervalId id)
{
- LOGT(@"timer 0x%08X: remove\n", (unsigned int) id, 0);
- if (id->refcount <= 0) abort();
- if (!id->cftimer) abort();
+ LOGT(@"timer 0x%08X: remove", (unsigned int) id);
+ ASSERT_RET (id->refcount > 0, "already freed");
+ ASSERT_RET (id->cftimer, "timers corrupted");
CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), id->cftimer,
kCFRunLoopCommonModes);
}
-#ifndef USE_COCOA_SOURCES
-
-struct jwxyz_sources_data {
- int count;
- XtInputId ids[FD_SETSIZE];
-};
-
jwxyz_sources_data *
jwxyz_sources_init (XtAppContext app)
{
return td;
}
-void
-jwxyz_sources_free (jwxyz_sources_data *td)
-{
- free (td);
-}
-
-
static void
jwxyz_source_select (XtInputId id)
{
jwxyz_sources_data *td = display_sources_data (app_to_display (id->app));
- if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort();
- if (td->ids[id->fd]) abort();
+ ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range");
+ ASSERT_RET (td->ids[id->fd] == 0, "sources corrupted");
td->ids[id->fd] = id;
- td->count++;
+ td->fd_count++;
}
static void
jwxyz_source_deselect (XtInputId id)
{
jwxyz_sources_data *td = display_sources_data (app_to_display (id->app));
- if (td->count <= 0) abort();
- if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort();
- if (td->ids[id->fd] != id) abort();
+ 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->count--;
+ td->fd_count--;
}
void
jwxyz_sources_run (jwxyz_sources_data *td)
{
- if (td->count == 0) return;
+ if (td->fd_count == 0) return;
struct timeval tv = { 0, };
fd_set fds;
}
}
- if (!max) abort();
+ 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];
- if (!id || !id->cb) abort();
- if (id->fd != i) abort();
+ ASSERT_RET (id && id->cb, "sources corrupted");
+ ASSERT_RET (id->fd == i, "sources corrupted");
id->cb (id->closure, &id->fd, &id);
}
}
}
}
-#endif /* !USE_COCOA_SOURCES */
-
XtInputId
XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
data->fd = fd;
data->closure = closure;
- LOGI(@"source 0x%08X %2d: alloc\n", (unsigned int) data, data->fd, 0);
-
-# ifdef USE_COCOA_SOURCES
-
- CFSocketContext ctx = { 0, };
- ctx.info = data;
- ctx.retain = jwxyz_source_retain;
- ctx.release = jwxyz_source_release;
-
- data->socket = CFSocketCreateWithNative (NULL, fd, kCFSocketReadCallBack,
- jwxyz_source_cb, &ctx);
- // CFSocketCreateWithNative called jwxyz_source_retain.
-
- CFSocketSetSocketFlags (data->socket,
- kCFSocketAutomaticallyReenableReadCallBack
- );
- // not kCFSocketCloseOnInvalidate.
-
- data->cfsource = CFSocketCreateRunLoopSource (NULL, data->socket, 0);
-
- CFRunLoopAddSource (CFRunLoopGetCurrent(), data->cfsource,
- kCFRunLoopCommonModes);
-
-# else /* !USE_COCOA_SOURCES */
+ LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd);
data->app = app;
jwxyz_source_retain (data);
jwxyz_source_select (data);
-# endif /* !USE_COCOA_SOURCES */
-
return data;
}
void
XtRemoveInput (XtInputId id)
{
- LOGI(@"source 0x%08X %2d: remove\n", (unsigned int) id, id->fd, 0);
- if (id->refcount <= 0) abort();
-# ifdef USE_COCOA_SOURCES
- if (! id->cfsource) abort();
- if (! id->socket) abort();
-
- CFRunLoopRemoveSource (CFRunLoopGetCurrent(), id->cfsource,
- kCFRunLoopCommonModes);
- CFSocketInvalidate (id->socket);
- // CFSocketInvalidate called jwxyz_source_release.
-
-# else /* !USE_COCOA_SOURCES */
+ LOGI(@"source 0x%08X %2d: remove", (unsigned int) id, id->fd);
+ ASSERT_RET (id->refcount > 0, "sources corrupted");
jwxyz_source_deselect (id);
jwxyz_source_release (id);
-
-# endif /* !USE_COCOA_SOURCES */
}
-void
-jwxyz_XtRemoveInput_all (Display *dpy)
+static void
+jwxyz_XtRemoveInput_all (jwxyz_sources_data *td)
{
-# ifdef USE_COCOA_SOURCES
- abort();
-# else /* !USE_COCOA_SOURCES */
-
- jwxyz_sources_data *td = display_sources_data (dpy);
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)
+{
+ struct jwxyz_XtIntervalId *timer, *next;
+ int count = 0;
+
+ // Iterate the timer list, being careful that XtRemoveTimeOut removes
+ // things 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");
+ }
+}
+
-# endif /* !USE_COCOA_SOURCES */
+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_to_display (app));
+ jwxyz_sources_run (td);
}