From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / utils / grabclient.c
index dbf0aec7858e7eff5d3254cf239208e48c99d513..fc002012d05d3c111c0cac17e6de14c9c244f201 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1992-2005 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1992-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
    same API as this program (utils/grabclient.c).
  */
 
+/* This code is a mess.  There's two decades of history in this file.
+   There are several distinct paths through this file depending on what
+   platform it's being compiled for:
+
+
+   X11 execution path:
+
+       load_image_async CB
+           load_random_image_x11
+               fork_exec_cb
+                   "xscreensaver-getimage 0xWINDOW 0xPIXMAP"
+                       "xscreensaver-getimage-file --name /DIR"
+                       draw_colorbars
+                       XPutImage
+                   XtAppAddInput xscreensaver_getimage_cb
+       ...
+       xscreensaver_getimage_cb
+           get_name_from_xprops
+           get_original_geometry_from_xprops
+           CB name, geom, closure
+
+
+   MacOS execution path:
+
+       load_image_async CB
+           load_random_image_cocoa
+               osx_grab_desktop_image (grabclient-osx.m, MacOS version)
+                   copy_framebuffer_to_ximage
+                   XPutImage
+               draw_colorbars
+               osx_load_image_file_async
+                   open_image_name_pipe
+                       "xscreensaver-getimage-file --name /DIR"
+                 XtAppAddInput xscreensaver_getimage_file_cb
+       ...
+       xscreensaver_getimage_file_cb
+           osx_load_image_file
+               CB name, geom, closure
+
+
+   iOS execution path:
+
+       load_image_async CB
+           load_random_image_cocoa
+               osx_grab_desktop_image (grabclient-osx.m, iOS version)
+                   CGWindowListCreateImage
+                   jwxyz_draw_NSImage_or_CGImage
+               draw_colorbars
+               ios_load_random_image
+                   ios_load_random_image_cb
+                       jwxyz_draw_NSImage_or_CGImage
+                       CB name, geom, closure
+
+
+   Andrid execution path:
+
+       load_image_async CB
+           load_random_image_android
+               jwxyz_load_random_image (jwxyz-android.c)
+                   XPutImage
+               draw_colorbars
+               CB name, geom, closure
+ */
+
 #include "utils.h"
 #include "grabscreen.h"
 #include "resources.h"
+#include "yarandom.h"
 
-#include "vroot.h"
-#include <X11/Xatom.h>
-
-#include <X11/Intrinsic.h>   /* for XtInputId, etc */
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+# include "colorbars.h"
+#else /* !HAVE_COCOA -- real Xlib */
+# include "vroot.h"
+# include <X11/Xatom.h>
+# include <X11/Intrinsic.h>   /* for XtInputId, etc */
+#endif /* !HAVE_COCOA */
 
+#include <sys/stat.h>
 
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 
 
 extern char *progname;
-extern XtAppContext app;
 
+static void print_loading_msg (Screen *, Window);
+
+
+/* Used for pipe callbacks in X11 or OSX mode.
+   X11: this is the xscreensaver_getimage_cb closure,
+     when waiting on the fork of "xscreensaver-getimage"
+   OSX: this is the xscreensaver_getimage_file_cb closure,
+     when waiting on the fork of "xscreensaver-getimage-file"
+ */
+typedef struct {
+  void (*callback) (Screen *, Window, Drawable,
+                    const char *name, XRectangle *geom, void *closure);
+  Screen *screen;
+  Window window;
+  Drawable drawable;
+  void *closure;
+  XtInputId pipe_id;
+  FILE *pipe;
+
+# if !defined(USE_IPHONE) && !defined(HAVE_COCOA)  /* Real X11 */
+  pid_t pid;
+# endif
+
+# if !defined(USE_IPHONE) && defined(HAVE_COCOA)   /* Desktop OSX */
+  char *directory;
+# endif
+
+} xscreensaver_getimage_data;
+
+
+#if !defined(HAVE_COCOA) && !defined(HAVE_ANDROID)   /* Real X11 */
 
 static Bool error_handler_hit_p = False;
 
@@ -100,7 +200,7 @@ xscreensaver_window_p (Display *dpy, Window window)
    explain.
  */
 Bool
-use_subwindow_mode_p(Screen *screen, Window window)
+use_subwindow_mode_p (Screen *screen, Window window)
 {
   if (window != VirtualRootWindowOfScreen(screen))
     return False;
@@ -165,11 +265,15 @@ checkerboard (Screen *screen, Drawable drawable)
         XFillRectangle (dpy, drawable, gc, x,      y,      size, size);
         XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
       }
+  XFreeGC (dpy, gc);
 }
 
 
+/* Read the image's original name off of the window's X properties.
+   Used only when running "real" X11, not jwxyz.
+ */
 static char *
-get_name (Display *dpy, Window window)
+get_name_from_xprops (Display *dpy, Window window)
 {
   Atom type;
   int format;
@@ -182,13 +286,17 @@ get_name (Display *dpy, Window window)
                           &name)
       == Success
       && type != None)
-    return strdup((char *) name);
+    return (char *) name;
   else
     return 0;
 }
 
+
+/* Read the image's original geometry off of the window's X properties.
+   Used only when running "real" X11, not jwxyz.
+ */
 static Bool
-get_geometry (Display *dpy, Window window, XRectangle *ret)
+get_original_geometry_from_xprops (Display *dpy, Window window, XRectangle *ret)
 {
   Atom type;
   int format;
@@ -205,6 +313,7 @@ get_geometry (Display *dpy, Window window, XRectangle *ret)
       && type != None)
     {
       int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
+      free (name);
       /* Require all four, and don't allow negative positions. */
       if (flags == (XValue|YValue|WidthValue|HeightValue))
         {
@@ -222,7 +331,6 @@ get_geometry (Display *dpy, Window window, XRectangle *ret)
 }
 
 
-
 static void
 hack_subproc_environment (Display *dpy)
 {
@@ -236,10 +344,15 @@ hack_subproc_environment (Display *dpy)
 
   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
      any more, right?  It's not Posix, but everyone seems to have it. */
-#ifdef HAVE_PUTENV
+# ifdef HAVE_PUTENV
   if (putenv (ndpy))
     abort ();
-#endif /* HAVE_PUTENV */
+# endif /* HAVE_PUTENV */
+
+  /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
+     glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS)
+     do not.  So we must leak it (and/or the previous setting). Yay.
+   */
 }
 
 
@@ -249,8 +362,10 @@ hack_subproc_environment (Display *dpy)
    this is due to the intermediate /bin/sh that system() uses to
    parse arguments?  I'm not sure.  But using fork() and execvp()
    here seems to close the race.
- */
 
+   Used to execute "xscreensaver-getimage".
+   Used only when running "real" X11, not jwxyz.
+ */
 static void
 exec_simple_command (const char *command)
 {
@@ -267,47 +382,14 @@ exec_simple_command (const char *command)
   execvp (av[0], av);  /* shouldn't return. */
 }
 
-static void
-fork_exec_wait (const char *command)
-{
-  char buf [255];
-  pid_t forked;
-  int status;
-
-  switch ((int) (forked = fork ()))
-    {
-    case -1:
-      sprintf (buf, "%s: couldn't fork", progname);
-      perror (buf);
-      return;
-
-    case 0:
-      exec_simple_command (command);
-      exit (1);  /* exits child fork */
-      break;
-
-    default:
-      waitpid (forked, &status, 0);
-      break;
-    }
-}
-
-
-typedef struct {
-  void (*callback) (Screen *, Window, Drawable,
-                    const char *name, XRectangle *geom, void *closure);
-  Screen *screen;
-  Window window;
-  Drawable drawable;
-  void *closure;
-  FILE *read_pipe;
-  FILE *write_pipe;
-  XtInputId pipe_id;
-} grabclient_data;
-
 
-static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id);
+static void xscreensaver_getimage_cb (XtPointer closure,
+                                      int *fd, XtIntervalId *id);
 
+/* Spawn a program, and run the callback when it finishes.
+   Used to execute "xscreensaver-getimage".
+   Used only when running "real" X11, not jwxyz.
+ */
 static void
 fork_exec_cb (const char *command,
               Screen *screen, Window window, Drawable drawable,
@@ -316,9 +398,11 @@ fork_exec_cb (const char *command,
                                 void *closure),
               void *closure)
 {
-  grabclient_data *data;
+  XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen));
+  xscreensaver_getimage_data *data;
   char buf [255];
   pid_t forked;
+  FILE *wpipe;
 
   int fds [2];
 
@@ -329,16 +413,16 @@ fork_exec_cb (const char *command,
       exit (1);
     }
 
-  data = (grabclient_data *) calloc (1, sizeof(*data));
+  data = (xscreensaver_getimage_data *) calloc (1, sizeof(*data));
   data->callback   = callback;
   data->closure    = closure;
   data->screen     = screen;
   data->window     = window;
   data->drawable   = drawable;
-  data->read_pipe  = fdopen (fds[0], "r");
-  data->write_pipe = fdopen (fds[1], "w");
+  data->pipe       = fdopen (fds[0], "r");
+  wpipe            = fdopen (fds[1], "w");   /* Is this necessary? */
 
-  if (!data->read_pipe || !data->write_pipe)
+  if (!data->pipe || !wpipe)
     {
       sprintf (buf, "%s: fdopen", progname);
       perror (buf);
@@ -346,11 +430,12 @@ fork_exec_cb (const char *command,
     }
 
   data->pipe_id =
-    XtAppAddInput (app, fileno (data->read_pipe),
+    XtAppAddInput (app, fileno (data->pipe),
                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
-                   finalize_cb, (XtPointer) data);
+                   xscreensaver_getimage_cb, (XtPointer) data);
 
-  switch ((int) (forked = fork ()))
+  forked = fork ();
+  switch ((int) forked)
     {
     case -1:
       sprintf (buf, "%s: couldn't fork", progname);
@@ -359,8 +444,8 @@ fork_exec_cb (const char *command,
 
     case 0:                                    /* child */
 
-      fclose (data->read_pipe);
-      data->read_pipe = 0;
+      fclose (data->pipe);
+      data->pipe = 0;
 
       /* clone the write pipe onto stdout so that it gets closed
          when the fork exits.  This will shut down the pipe and
@@ -377,8 +462,8 @@ fork_exec_cb (const char *command,
       break;
 
     default:                                   /* parent */
-      fclose (data->write_pipe);
-      data->write_pipe = 0;
+      fclose (wpipe);
+      data->pid = forked;
       break;
     }
 }
@@ -386,25 +471,35 @@ fork_exec_cb (const char *command,
 
 /* Called in the parent when the forked process dies.
    Runs the caller's callback, and cleans up.
+   This runs when "xscreensaver-getimage" exits.
+   Used only when running "real" X11, not jwxyz.
  */
 static void
-finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
+xscreensaver_getimage_cb (XtPointer closure, int *fd, XtIntervalId *id)
 {
-  grabclient_data *data = (grabclient_data *) closure;
+  xscreensaver_getimage_data *data = (xscreensaver_getimage_data *) closure;
   Display *dpy = DisplayOfScreen (data->screen);
   char *name;
   XRectangle geom = { 0, 0, 0, 0 };
 
   XtRemoveInput (*id);
 
-  name = get_name (dpy, data->window);
-  get_geometry (dpy, data->window, &geom);
+  name = get_name_from_xprops (dpy, data->window);
+  get_original_geometry_from_xprops (dpy, data->window, &geom);
 
   data->callback (data->screen, data->window, data->drawable,
                   name, &geom, data->closure);
   if (name) free (name);
 
-  fclose (data->read_pipe);
+  fclose (data->pipe);
+
+  if (data->pid)       /* reap zombies */
+    {
+      int status;
+      waitpid (data->pid, &status, 0);
+      data->pid = 0;
+    }
+
   memset (data, 0, sizeof (*data));
   free (data);
 }
@@ -412,18 +507,17 @@ finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
 
 /* Loads an image into the Drawable.
    When grabbing desktop images, the Window will be unmapped first.
+   Used only when running "real" X11, not jwxyz.
  */
 static void
-load_random_image_1 (Screen *screen, Window window, Drawable drawable,
-                     void (*callback) (Screen *, Window, Drawable,
-                                       const char *name, XRectangle *geom,
-                                       void *closure),
-                     void *closure,
-                     char **name_ret,
-                     XRectangle *geom_ret)
+load_random_image_x11 (Screen *screen, Window window, Drawable drawable,
+                       void (*callback) (Screen *, Window, Drawable,
+                                         const char *name, XRectangle *geom,
+                                         void *closure),
+                       void *closure)
 {
   Display *dpy = DisplayOfScreen (screen);
-  char *grabber = get_string_resource ("desktopGrabber", "DesktopGrabber");
+  char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber");
   char *cmd;
   char id[200];
 
@@ -448,100 +542,510 @@ load_random_image_1 (Screen *screen, Window window, Drawable drawable,
      then the game is already over.
    */
   sprintf (cmd, grabber, id);
+  free (grabber);
+  grabber = 0;
 
   /* In case "cmd" fails, leave some random image on the screen, not just
      black or white, so that it's more obvious what went wrong. */
   checkerboard (screen, drawable);
+  if (window == drawable)
+    print_loading_msg (screen, window);
 
   XSync (dpy, True);
   hack_subproc_environment (dpy);
 
-  if (callback)
+  /* Start the image loading in another fork and return immediately.
+     Invoke the callback function when done. */
+  fork_exec_cb (cmd, screen, window, drawable, callback, closure);
+
+  free (cmd);
+  XSync (dpy, True);
+}
+
+#elif defined (HAVE_COCOA) /* OSX or iOS */
+
+# ifndef USE_IPHONE   /* HAVE_COCOA && !USE_IPHONE -- desktop OSX */
+
+# define BACKSLASH(c) \
+  (! ((c >= 'a' && c <= 'z') || \
+      (c >= 'A' && c <= 'Z') || \
+      (c >= '0' && c <= '9') || \
+      c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
+
+/* Gets the name of an image file to load by running xscreensaver-getimage-file
+   at the end of a pipe.  This can be very slow!
+ */
+static FILE *
+open_image_name_pipe (const char *dir)
+{
+  char *s;
+
+  /* /bin/sh on OS X 10.10 wipes out the PATH. */
+  const char *path = getenv("PATH");
+  char *cmd = s = malloc ((strlen(dir) + strlen(path)) * 2 + 100);
+  strcpy (s, "/bin/sh -c 'export PATH=");
+  s += strlen (s);
+  while (*path) {
+    char c = *path++;
+    if (BACKSLASH(c)) *s++ = '\\';
+    *s++ = c;
+  }
+  strcpy (s, "; ");
+  s += strlen (s);
+
+  strcpy (s, "xscreensaver-getimage-file --name ");
+  s += strlen (s);
+  while (*dir) {
+    char c = *dir++;
+    if (BACKSLASH(c)) *s++ = '\\';
+    *s++ = c;
+  }
+
+  strcpy (s, "'");
+  s += strlen (s);
+
+  *s = 0;
+
+  FILE *pipe = popen (cmd, "r");
+  free (cmd);
+  return pipe;
+}
+
+
+static void
+xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id)
+{
+  /* This is not called from a signal handler, so doing stuff here is fine.
+   */
+  xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
+  char buf[10240];
+  const char *dir = clo2->directory;
+  char *absfile = 0;
+  *buf = 0;
+  fgets (buf, sizeof(buf)-1, clo2->pipe);
+  pclose (clo2->pipe);
+  clo2->pipe = 0;
+  XtRemoveInput (clo2->pipe_id);
+  clo2->pipe_id = 0;
+
+  /* strip trailing newline */
+  int L = strlen(buf);
+  while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
+    buf[--L] = 0;
+
+  Display *dpy = DisplayOfScreen (clo2->screen);
+  XRectangle geom;
+
+  if (*buf && *buf != '/')             /* pathname is relative to dir. */
     {
-      /* Start the image loading in another fork and return immediately.
-         Invoke the callback function when done.
-       */
-      if (name_ret) abort();
-      fork_exec_cb (cmd, screen, window, drawable, callback, closure);
+      absfile = malloc (strlen(dir) + strlen(buf) + 10);
+      strcpy (absfile, dir);
+      if (dir[strlen(dir)-1] != '/')
+        strcat (absfile, "/");
+      strcat (absfile, buf);
     }
-  else
+
+  if (! osx_load_image_file (clo2->screen, clo2->window, clo2->drawable,
+                             (absfile ? absfile : buf), &geom)) {
+    /* unable to load image - draw colorbars 
+     */
+    XWindowAttributes xgwa;
+    XGetWindowAttributes (dpy, clo2->window, &xgwa);
+    Window r;
+    int x, y;
+    unsigned int w, h, bbw, d;
+    struct stat st;
+
+    /* Log something to syslog so we can tell the difference between
+       corrupted images and broken symlinks. */
+    if (!*buf)
+      fprintf (stderr, "%s: no image filename found\n", progname);
+    else if (! stat (buf, &st))
+      fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
+    else
+      {
+        char buf2[2048];
+        sprintf (buf2, "%.255s: %.1024s", progname, buf);
+        perror (buf2);
+      }
+
+    XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
+    draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
+                    0, 0, w, h);
+    geom.x = geom.y = 0;
+    geom.width = w;
+    geom.height = h;
+  }
+
+  /* Take the extension off of the file name. */
+  /* Duplicated in driver/xscreensaver-getimage.c. */
+  if (*buf)
     {
-      /* Wait for the image to load, and return it immediately.
-       */
-      fork_exec_wait (cmd);
-      if (name_ret)
-        *name_ret = get_name (dpy, window);
-      if (geom_ret)
-        get_geometry (dpy, window, geom_ret);
+      char *slash = strrchr (buf, '/');
+      char *dot = strrchr ((slash ? slash : buf), '.');
+      if (dot) *dot = 0;
+      /* Replace slashes with newlines */
+      /* while (dot = strchr(buf, '/')) *dot = '\n'; */
+      /* Replace slashes with spaces */
+      /* while ((dot = strchr(buf, '/'))) *dot = ' '; */
     }
 
-  free (cmd);
-  XSync (dpy, True);
+  if (absfile) free (absfile);
+  clo2->callback (clo2->screen, clo2->window, clo2->drawable, buf, &geom,
+                  clo2->closure);
+  clo2->callback = 0;
+  free (clo2->directory);
+  free (clo2);
 }
 
 
-/* Loads an image into the Drawable in the background;
-   when the image is fully loaded, runs the callback.
-   When grabbing desktop images, the Window will be unmapped first.
+# else   /* HAVE_COCOA && USE_IPHONE -- iOS */
+
+/* Callback for ios_load_random_image(), called after we have loaded an
+   image from the iOS device's Photo Library.  See grabclient-ios.m.
  */
-void
-fork_load_random_image (Screen *screen, Window window, Drawable drawable,
-                        void (*callback) (Screen *, Window, Drawable,
-                                          const char *name, XRectangle *geom,
-                                          void *closure),
-                        void *closure)
+static void
+ios_load_random_image_cb (void *uiimage, const char *filename, 
+                          int width, int height, void *closure)
 {
-  load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
+  xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
+  Display *dpy = DisplayOfScreen (clo2->screen);
+  XRectangle geom;
+  XWindowAttributes xgwa;
+  Window r;
+  int x, y;
+  unsigned int w, h, bbw, d;
+  int rot = 0;
+
+  XGetWindowAttributes (dpy, clo2->window, &xgwa);
+  XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
+
+  /* If the image is portrait and the window is landscape, or vice versa,
+     rotate the image. The idea is to fill up as many pixels as possible,
+     and assume the user will just rotate their phone until it looks right.
+     This makes "decayscreen", etc. much more easily viewable.
+   */
+  if (get_boolean_resource (dpy, "rotateImages", "RotateImages")) {
+    if ((width > height) != (w > h))
+      rot = 5;
+  }
+
+  if (uiimage)
+    {
+      jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen), 
+                                     clo2->drawable,
+                                     True, uiimage, &geom,
+                                     rot);
+    }
+  else  /* Probably means no images in the gallery. */
+    {
+      draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
+                      0, 0, w, h);
+      geom.x = geom.y = 0;
+      geom.width = w;
+      geom.height = h;
+      filename = 0;
+    }
+
+  clo2->callback (clo2->screen, clo2->window, clo2->drawable,
+                  filename, &geom, clo2->closure);
+  clo2->callback = 0;
+  free (clo2);
 }
 
+# endif /* HAVE_COCOA && USE_IPHONE */
+
+
+static void
+osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
+                           const char *dir,
+                           void (*callback) (Screen *, Window, Drawable,
+                                             const char *name,
+                                             XRectangle *geom,
+                                             void *closure),
+                       void *closure)
+{
+  xscreensaver_getimage_data *clo2 =
+    (xscreensaver_getimage_data *) calloc (1, sizeof(*clo2));
+
+  clo2->screen = screen;
+  clo2->window = xwindow;
+  clo2->drawable = drawable;
+  clo2->callback = callback;
+  clo2->closure = closure;
+
+# ifndef USE_IPHONE   /* Desktop OSX */
+  clo2->directory = strdup (dir);
+  clo2->pipe = open_image_name_pipe (dir);
+  clo2->pipe_id = XtAppAddInput (XtDisplayToApplicationContext (
+                            DisplayOfScreen (screen)), 
+                            fileno (clo2->pipe),
+                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
+                            xscreensaver_getimage_file_cb, (XtPointer) clo2);
+# else /* USE_IPHONE */
+  {
+    XWindowAttributes xgwa;
+    XGetWindowAttributes (DisplayOfScreen (screen), xwindow, &xgwa);
+    ios_load_random_image (ios_load_random_image_cb, clo2,
+                           xgwa.width, xgwa.height);
+  }
+# endif /* USE_IPHONE */
+}
 
-#ifndef DEBUG
 
 /* Loads an image into the Drawable, returning once the image is loaded.
-   When grabbing desktop images, the Window will be unmapped first.
  */
-void
-load_random_image (Screen *screen, Window window, Drawable drawable,
-                   char **name_ret, XRectangle *geom_ret)
+static void
+load_random_image_cocoa (Screen *screen, Window window, Drawable drawable,
+                         void (*callback) (Screen *, Window, Drawable,
+                                           const char *name, XRectangle *geom,
+                                           void *closure),
+                         void *closure)
 {
-  load_random_image_1 (screen, window, drawable, 0, 0, name_ret, geom_ret);
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
+  Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
+  const char *dir = 0;
+  Bool done = False;
+  XRectangle geom;
+  char *name = 0;
+  
+  if (!drawable) abort();
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  {
+    Window r;
+    int x, y;
+    unsigned int w, h, bbw, d;
+    XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
+    xgwa.width = w;
+    xgwa.height = h;
+  }
+
+  geom.x = 0;
+  geom.y = 0;
+  geom.width  = xgwa.width;
+  geom.height = xgwa.height;
+
+# ifndef USE_IPHONE
+  if (filep)
+    dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
+
+  if (!dir || !*dir)
+    filep = False;
+# endif /* ! USE_IPHONE */
+
+  if (deskp && filep) {
+    deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
+    filep = !deskp;
+  }
+
+  if (filep && !done) {
+    osx_load_image_file_async (screen, window, drawable, dir, 
+                               callback, closure);
+    return;
+  }
+
+  if (deskp && !done) {
+    if (osx_grab_desktop_image (screen, window, drawable, &geom)) {
+      name = strdup ("desktop");
+      done = True;
+    }
+  }
+
+  if (! done)
+    draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
+                    0, 0, xgwa.width, xgwa.height);
+
+  /* If we got here, we loaded synchronously, so we're done. */
+  callback (screen, window, drawable, name, &geom, closure);
+  if (name) free (name);
 }
 
-#else  /* DEBUG */
 
-typedef struct {
-  char **name_ret;
-  Bool done;
-} debug_closure;
+#elif defined(HAVE_ANDROID)
 
+/* Loads an image into the Drawable, returning once the image is loaded.
+ */
 static void
-debug_cb (Screen *screen, Window window, Drawable drawable,
-          const char *name, void *closure)
+load_random_image_android (Screen *screen, Window window, Drawable drawable,
+                           void (*callback) (Screen *, Window, Drawable,
+                                             const char *name,
+                                             XRectangle *geom, void *closure),
+                           void *closure)
 {
-  debug_closure *data = (debug_closure *) closure;
-  fprintf (stderr, "%s: GRAB DEBUG: callback\n", progname);
-  if (data->name_ret)
-    *data->name_ret = (name ? strdup (name) : 0);
-  data->done = True;
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  XRectangle geom;
+  char *name = 0;
+  char *data = 0;
+  int width  = 0;
+  int height = 0;
+  
+  if (!drawable) abort();
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  {
+    Window r;
+    int x, y;
+    unsigned int w, h, bbw, d;
+    XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
+    xgwa.width = w;
+    xgwa.height = h;
+  }
+
+  geom.x = 0;
+  geom.y = 0;
+  geom.width  = xgwa.width;
+  geom.height = xgwa.height;
+
+  data = jwxyz_load_random_image (dpy, &width, &height, &name);
+  if (! data)
+    draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
+                    0, 0, xgwa.width, xgwa.height);
+  else
+    {
+      XImage *img = XCreateImage (dpy, xgwa.visual, 32,
+                                  ZPixmap, 0, data, width, height, 0, 0);
+      XGCValues gcv;
+      GC gc;
+      gcv.foreground = BlackPixelOfScreen (screen);
+      gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+      XFillRectangle (dpy, drawable, gc, 0, 0, xgwa.width, xgwa.height);
+      XPutImage (dpy, drawable, gc, img, 0, 0, 
+                 (xgwa.width  - width) / 2,
+                 (xgwa.height - height) / 2,
+                 width, height);
+      XDestroyImage (img);
+      XFreeGC (dpy, gc);
+    }
+
+  callback (screen, window, drawable, name, &geom, closure);
+  if (name) free (name);
 }
 
+#endif /* HAVE_ANDROID */
+
+
+
+/* Writes the string "Loading..." in the middle of the screen.
+   This will presumably get blown away when the image finally loads,
+   minutes or hours later...
+
+   This is called by load_image_async_simple() but not by load_image_async(),
+   since it is assumed that hacks that are loading more than one image
+   *at one time* will be doing something more clever than just blocking
+   with a blank screen.
+ */
+static void
+print_loading_msg (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  XWindowAttributes xgwa;
+  XGCValues gcv;
+  XFontStruct *f = 0;
+  GC gc;
+  char *fn = get_string_resource (dpy, "labelFont", "Font");
+  const char *text = "Loading...";
+  int w;
+
+  if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
+  if (!fn) fn = get_string_resource (dpy, "font", "Font");
+  if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
+  f = XLoadQueryFont (dpy, fn);
+  if (!f) f = XLoadQueryFont (dpy, "fixed");
+  if (!f) abort();
+  free (fn);
+  fn = 0;
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  w = XTextWidth (f, text, (int) strlen(text));
+
+  gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
+                                       "foreground", "Foreground");
+  gcv.background = get_pixel_resource (dpy, xgwa.colormap,
+                                       "background", "Background");
+  gcv.font = f->fid;
+  gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
+  XDrawImageString (dpy, window, gc,
+                    (xgwa.width - w) / 2,
+                    (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
+                    text, (int) strlen(text));
+  XFreeFont (dpy, f);
+  XFreeGC (dpy, gc);
+  XSync (dpy, False);
+}
+
+
+/* Loads an image into the Drawable in the background;
+   when the image is fully loaded, runs the callback.
+   When grabbing desktop images, the Window will be unmapped first.
+ */
 void
-load_random_image (Screen *screen, Window window, Drawable drawable,
-                   char **name_ret)
+load_image_async (Screen *screen, Window window, Drawable drawable,
+                  void (*callback) (Screen *, Window, Drawable,
+                                    const char *name, XRectangle *geom,
+                                    void *closure),
+                  void *closure)
 {
-  debug_closure data;
-  data.name_ret = name_ret;
-  data.done = False;
-  fprintf (stderr, "%s: GRAB DEBUG: forking\n", progname);
-  fork_load_random_image (screen, window, drawable, debug_cb, &data);
-  while (! data.done)
+  if (!callback) abort();
+# if defined(HAVE_COCOA)
+  load_random_image_cocoa   (screen, window, drawable, callback, closure);
+# elif defined(HAVE_ANDROID)
+  load_random_image_android (screen, window, drawable, callback, closure);
+# else /* real X11 */
+  load_random_image_x11     (screen, window, drawable, callback, closure);
+# endif
+}
+
+struct async_load_state {
+  Bool done_p;
+  char *filename;
+  XRectangle geom;
+};
+
+static void
+load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
+                            const char *name, XRectangle *geom, void *closure)
+{
+  async_load_state *state = (async_load_state *) closure;
+  state->done_p = True;
+  state->filename = (name ? strdup (name) : 0);
+  state->geom = *geom;
+}
+
+async_load_state *
+load_image_async_simple (async_load_state *state,
+                         Screen *screen,
+                         Window window,
+                         Drawable drawable, 
+                         char **filename_ret,
+                         XRectangle *geometry_ret)
+{
+  if (state && state->done_p)          /* done! */
     {
-      fprintf (stderr, "%s: GRAB DEBUG: waiting\n", progname);
-      if (XtAppPending (app) & XtIMAlternateInput)
-        XtAppProcessEvent (app, XtIMAlternateInput);
-      usleep (50000);
+      if (filename_ret)
+        *filename_ret = state->filename;
+      else if (state->filename)
+        free (state->filename);
+
+      if (geometry_ret)
+        *geometry_ret = state->geom;
+
+      free (state);
+      return 0;
+    }
+  else if (! state)                    /* first time */
+    {
+      state = (async_load_state *) calloc (1, sizeof(*state));
+      state->done_p = False;
+      print_loading_msg (screen, window);
+      load_image_async (screen, window, drawable, 
+                        load_image_async_simple_cb,
+                        state);
+      return state;
     }
-  fprintf (stderr, "%s: GRAB DEBUG: done\n", progname);
+  else                                 /* still waiting */
+    return state;
 }
-
-#endif /* DEBUG */