http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[xscreensaver] / OSX / jwxyz-timers.m
diff --git a/OSX/jwxyz-timers.m b/OSX/jwxyz-timers.m
new file mode 100644 (file)
index 0000000..ec6f439
--- /dev/null
@@ -0,0 +1,383 @@
+/* xscreensaver, Copyright (c) 2006 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 OSX implementation of Xt timers, for libjwxyz.
+ */
+
+#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)
+#else
+# define LOGT(str,arg1,arg2)
+#endif
+
+#ifdef DEBUG_SOURCES
+# define LOGI(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
+#else
+# define LOGI(str,arg1,arg2,arg3)
+#endif
+
+
+XtAppContext
+XtDisplayToApplicationContext (Display *dpy)
+{
+  return (XtAppContext) dpy;
+}
+
+#define app_to_display(APP) ((Display *) (APP))
+
+
+struct jwxyz_XtIntervalId {
+  CFRunLoopTimerRef cftimer;
+  int refcount;
+
+  XtTimerCallbackProc cb;
+  XtPointer closure;
+};
+
+struct jwxyz_XtInputId {
+# ifdef USE_COCOA_SOURCES
+  CFRunLoopSourceRef cfsource;
+  CFSocketRef socket;
+# else
+  XtAppContext app;
+# endif
+  int refcount;
+
+  XtInputCallbackProc cb;
+  XtPointer closure;
+  int fd;
+};
+
+
+static const void *
+jwxyz_timer_retain (const void *arg)
+{
+  struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
+  data->refcount++;
+  LOGT(@"timer  0x%08X: retain %d\n", (unsigned int) data, data->refcount);
+  return arg;
+}
+
+static void
+jwxyz_timer_release (const void *arg)
+{
+  struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
+  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);
+}
+
+static const void *
+jwxyz_source_retain (const void *arg)
+{
+  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);
+  return arg;
+}
+
+static void
+jwxyz_source_release (const void *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();
+  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);
+  }
+}
+
+
+static void
+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);
+  data->cb (data->closure, &data);
+
+  // Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate,
+  // which will call jwxyz_timer_release.
+}
+
+
+#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)
+{
+  struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *)
+    calloc (1, sizeof(*data));
+  data->cb = cb;
+  data->closure = closure;
+
+  LOGT(@"timer  0x%08X: alloc %d\n", (unsigned int) data, msecs);
+  
+  CFRunLoopTimerContext ctx = { 0, };
+  ctx.info    = data;
+  ctx.retain  = jwxyz_timer_retain;
+  ctx.release = jwxyz_timer_release;
+
+  CFAbsoluteTime time = CFAbsoluteTimeGetCurrent() + (msecs / 1000.0);
+
+  data->cftimer =
+    CFRunLoopTimerCreate (NULL, // allocator
+                          time, 0, 0, 0, // interval, flags, order
+                          jwxyz_timer_cb, &ctx);
+  // CFRunLoopTimerCreate called jwxyz_timer_retain.
+
+  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();
+
+  CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), id->cftimer,
+                        kCFRunLoopCommonModes);
+  CFRunLoopTimerInvalidate (id->cftimer);
+  // CFRunLoopTimerInvalidate called jwxyz_timer_release.
+}
+
+
+#ifndef USE_COCOA_SOURCES
+
+struct jwxyz_sources_data {
+  int count;
+  XtInputId ids[FD_SETSIZE];
+};
+
+jwxyz_sources_data *
+jwxyz_sources_init (XtAppContext app)
+{
+  jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
+  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();
+  td->ids[id->fd] = id;
+  td->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();
+  td->ids[id->fd] = 0;
+  td->count--;
+}
+
+void
+jwxyz_sources_run (jwxyz_sources_data *td)
+{
+  if (td->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;
+    }
+  }
+
+  if (!max) abort();
+
+  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();
+        id->cb (id->closure, &id->fd, &id);
+      }
+    }
+  }
+}
+
+#endif /* !USE_COCOA_SOURCES */
+
+
+XtInputId
+XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
+               XtInputCallbackProc cb, XtPointer closure)
+{
+  struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *)
+    calloc (1, sizeof(*data));
+  data->cb = cb;
+  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 */
+
+  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 */
+
+  jwxyz_source_deselect (id);
+  jwxyz_source_release (id);
+
+# endif /* !USE_COCOA_SOURCES */
+}
+
+void
+jwxyz_XtRemoveInput_all (Display *dpy)
+{
+# 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);
+  }
+
+# endif /* !USE_COCOA_SOURCES */
+}