From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992-2017 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* This file contains code for running an external program to grab an image
13    onto the given window.  The external program in question must take a
14    window ID as its argument, e.g., the "xscreensaver-getimage" program
15    in the hacks/ directory.
16
17    That program links against utils/grabimage.c, which happens to export the
18    same API as this program (utils/grabclient.c).
19  */
20
21 /* This code is a mess.  There's two decades of history in this file.
22    There are several distinct paths through this file depending on what
23    platform it's being compiled for:
24
25
26    X11 execution path:
27
28        load_image_async CB
29            load_random_image_x11
30                fork_exec_cb
31                    "xscreensaver-getimage 0xWINDOW 0xPIXMAP"
32                        "xscreensaver-getimage-file --name /DIR"
33                        draw_colorbars
34                        XPutImage
35                    XtAppAddInput xscreensaver_getimage_cb
36        ...
37        xscreensaver_getimage_cb
38            get_name_from_xprops
39            get_original_geometry_from_xprops
40            CB name, geom, closure
41
42
43    MacOS execution path:
44
45        load_image_async CB
46            load_random_image_cocoa
47                osx_grab_desktop_image (grabclient-osx.m, MacOS version)
48                    copy_framebuffer_to_ximage
49                    XPutImage
50                draw_colorbars
51                osx_load_image_file_async
52                    open_image_name_pipe
53                        "xscreensaver-getimage-file --name /DIR"
54                  XtAppAddInput xscreensaver_getimage_file_cb
55        ...
56        xscreensaver_getimage_file_cb
57            osx_load_image_file
58                CB name, geom, closure
59
60
61    iOS execution path:
62
63        load_image_async CB
64            load_random_image_cocoa
65                osx_grab_desktop_image (grabclient-osx.m, iOS version)
66                    CGWindowListCreateImage
67                    jwxyz_draw_NSImage_or_CGImage
68                draw_colorbars
69                ios_load_random_image
70                    ios_load_random_image_cb
71                        jwxyz_draw_NSImage_or_CGImage
72                        CB name, geom, closure
73
74
75    Andrid execution path:
76
77        load_image_async CB
78            load_random_image_android
79                jwxyz_load_random_image (jwxyz-android.c)
80                    XPutImage
81                draw_colorbars
82                CB name, geom, closure
83  */
84
85 #include "utils.h"
86 #include "grabscreen.h"
87 #include "resources.h"
88 #include "yarandom.h"
89
90 #ifdef HAVE_JWXYZ
91 # include "jwxyz.h"
92 # include "colorbars.h"
93 #else /* !HAVE_COCOA -- real Xlib */
94 # include "vroot.h"
95 # include <X11/Xatom.h>
96 # include <X11/Intrinsic.h>   /* for XtInputId, etc */
97 #endif /* !HAVE_COCOA */
98
99 #include <sys/stat.h>
100
101 #ifdef HAVE_UNISTD_H
102 # include <unistd.h>
103 #endif
104 #ifdef HAVE_SYS_WAIT_H
105 # include <sys/wait.h>          /* for waitpid() and associated macros */
106 #endif
107
108
109 extern char *progname;
110
111 static void print_loading_msg (Screen *, Window);
112
113
114 /* Used for pipe callbacks in X11 or OSX mode.
115    X11: this is the xscreensaver_getimage_cb closure,
116      when waiting on the fork of "xscreensaver-getimage"
117    OSX: this is the xscreensaver_getimage_file_cb closure,
118      when waiting on the fork of "xscreensaver-getimage-file"
119  */
120 typedef struct {
121   void (*callback) (Screen *, Window, Drawable,
122                     const char *name, XRectangle *geom, void *closure);
123   Screen *screen;
124   Window window;
125   Drawable drawable;
126   void *closure;
127   XtInputId pipe_id;
128   FILE *pipe;
129
130 # if !defined(USE_IPHONE) && !defined(HAVE_COCOA)  /* Real X11 */
131   pid_t pid;
132 # endif
133
134 # if !defined(USE_IPHONE) && defined(HAVE_COCOA)   /* Desktop OSX */
135   char *directory;
136 # endif
137
138 } xscreensaver_getimage_data;
139
140
141 #if !defined(HAVE_COCOA) && !defined(HAVE_ANDROID)   /* Real X11 */
142
143 static Bool error_handler_hit_p = False;
144
145 static int
146 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
147 {
148   error_handler_hit_p = True;
149   return 0;
150 }
151
152
153 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
154  */
155 static Bool
156 drawable_window_p (Display *dpy, Drawable d)
157 {
158   XErrorHandler old_handler;
159   XWindowAttributes xgwa;
160
161   XSync (dpy, False);
162   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
163   error_handler_hit_p = False;
164   XGetWindowAttributes (dpy, d, &xgwa);
165   XSync (dpy, False);
166   XSetErrorHandler (old_handler);
167   XSync (dpy, False);
168
169   if (!error_handler_hit_p)
170     return True;   /* It's a Window. */
171   else
172     return False;  /* It's a Pixmap, or an invalid ID. */
173 }
174
175
176 static Bool
177 xscreensaver_window_p (Display *dpy, Window window)
178 {
179   Atom type;
180   int format;
181   unsigned long nitems, bytesafter;
182   unsigned char *version;
183   if (XGetWindowProperty (dpy, window,
184                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
185                           0, 1, False, XA_STRING,
186                           &type, &format, &nitems, &bytesafter,
187                           &version)
188       == Success
189       && type != None)
190     return True;
191   return False;
192 }
193
194
195 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
196    on a window whose depth is not the maximal depth of the screen?  Or
197    something.  Anyway, things don't work unless we: use SubwindowMode for
198    the real root window (or a legitimate virtual root window); but do not
199    use SubwindowMode for the xscreensaver window.  I make no attempt to
200    explain.
201  */
202 Bool
203 use_subwindow_mode_p (Screen *screen, Window window)
204 {
205   if (window != VirtualRootWindowOfScreen(screen))
206     return False;
207   else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
208     return False;
209   else
210     return True;
211 }
212
213
214 static void
215 checkerboard (Screen *screen, Drawable drawable)
216 {
217   Display *dpy = DisplayOfScreen (screen);
218   unsigned int x, y;
219   int size = 24;
220   XColor fg, bg;
221   XGCValues gcv;
222   GC gc = XCreateGC (dpy, drawable, 0, &gcv);
223   Colormap cmap;
224   unsigned int win_width, win_height;
225
226   fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
227   fg.red = fg.green = fg.blue = 0x0000;
228   bg.red = bg.green = bg.blue = 0x4444;
229   fg.pixel = 0;
230   bg.pixel = 1;
231
232   if (drawable_window_p (dpy, drawable))
233     {
234       XWindowAttributes xgwa;
235       XGetWindowAttributes (dpy, drawable, &xgwa);
236       win_width = xgwa.width;
237       win_height = xgwa.height;
238       cmap = xgwa.colormap;
239       screen = xgwa.screen;
240     }
241   else  /* it's a pixmap */
242     {
243       XWindowAttributes xgwa;
244       Window root;
245       int x, y;
246       unsigned int bw, d;
247       XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
248       cmap = xgwa.colormap;
249       XGetGeometry (dpy, drawable,
250                     &root, &x, &y, &win_width, &win_height, &bw, &d);
251     }
252
253   /* Allocate black and gray, but don't hold them locked. */
254   if (XAllocColor (dpy, cmap, &fg))
255     XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
256   if (XAllocColor (dpy, cmap, &bg))
257     XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
258
259   XSetForeground (dpy, gc, bg.pixel);
260   XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
261   XSetForeground (dpy, gc, fg.pixel);
262   for (y = 0; y < win_height; y += size+size)
263     for (x = 0; x < win_width; x += size+size)
264       {
265         XFillRectangle (dpy, drawable, gc, x,      y,      size, size);
266         XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
267       }
268   XFreeGC (dpy, gc);
269 }
270
271
272 /* Read the image's original name off of the window's X properties.
273    Used only when running "real" X11, not jwxyz.
274  */
275 static char *
276 get_name_from_xprops (Display *dpy, Window window)
277 {
278   Atom type;
279   int format;
280   unsigned long nitems, bytesafter;
281   unsigned char *name = 0;
282   Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
283   if (XGetWindowProperty (dpy, window, atom,
284                           0, 1024, False, XA_STRING,
285                           &type, &format, &nitems, &bytesafter,
286                           &name)
287       == Success
288       && type != None)
289     return (char *) name;
290   else
291     return 0;
292 }
293
294
295 /* Read the image's original geometry off of the window's X properties.
296    Used only when running "real" X11, not jwxyz.
297  */
298 static Bool
299 get_original_geometry_from_xprops (Display *dpy, Window window, XRectangle *ret)
300 {
301   Atom type;
302   int format;
303   unsigned long nitems, bytesafter;
304   unsigned char *name = 0;
305   Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
306   int x, y;
307   unsigned int w, h;
308   if (XGetWindowProperty (dpy, window, atom,
309                           0, 1024, False, XA_STRING,
310                           &type, &format, &nitems, &bytesafter,
311                           &name)
312       == Success
313       && type != None)
314     {
315       int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
316       free (name);
317       /* Require all four, and don't allow negative positions. */
318       if (flags == (XValue|YValue|WidthValue|HeightValue))
319         {
320           ret->x = x;
321           ret->y = y;
322           ret->width  = w;
323           ret->height = h;
324           return True;
325         }
326       else
327         return False;
328     }
329   else
330     return False;
331 }
332
333
334 static void
335 hack_subproc_environment (Display *dpy)
336 {
337   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
338      the spawned processes inherit is what we are actually using.
339    */
340   const char *odpy = DisplayString (dpy);
341   char *ndpy = (char *) malloc(strlen(odpy) + 20);
342   strcpy (ndpy, "DISPLAY=");
343   strcat (ndpy, odpy);
344
345   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
346      any more, right?  It's not Posix, but everyone seems to have it. */
347 # ifdef HAVE_PUTENV
348   if (putenv (ndpy))
349     abort ();
350 # endif /* HAVE_PUTENV */
351
352   /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
353      glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS)
354      do not.  So we must leak it (and/or the previous setting). Yay.
355    */
356 }
357
358
359 /* Spawn a program, and wait for it to finish.
360    If we just use system() for this, then sometimes the subprocess
361    doesn't die when *this* process is sent a TERM signal.  Perhaps
362    this is due to the intermediate /bin/sh that system() uses to
363    parse arguments?  I'm not sure.  But using fork() and execvp()
364    here seems to close the race.
365
366    Used to execute "xscreensaver-getimage".
367    Used only when running "real" X11, not jwxyz.
368  */
369 static void
370 exec_simple_command (const char *command)
371 {
372   char *av[1024];
373   int ac = 0;
374   char *token = strtok (strdup(command), " \t");
375   while (token)
376     {
377       av[ac++] = token;
378       token = strtok(0, " \t");
379     }
380   av[ac] = 0;
381
382   execvp (av[0], av);   /* shouldn't return. */
383 }
384
385
386 static void xscreensaver_getimage_cb (XtPointer closure,
387                                       int *fd, XtIntervalId *id);
388
389 /* Spawn a program, and run the callback when it finishes.
390    Used to execute "xscreensaver-getimage".
391    Used only when running "real" X11, not jwxyz.
392  */
393 static void
394 fork_exec_cb (const char *command,
395               Screen *screen, Window window, Drawable drawable,
396               void (*callback) (Screen *, Window, Drawable,
397                                 const char *name, XRectangle *geom,
398                                 void *closure),
399               void *closure)
400 {
401   XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen));
402   xscreensaver_getimage_data *data;
403   char buf [255];
404   pid_t forked;
405   FILE *wpipe;
406
407   int fds [2];
408
409   if (pipe (fds))
410     {
411       sprintf (buf, "%s: creating pipe", progname);
412       perror (buf);
413       exit (1);
414     }
415
416   data = (xscreensaver_getimage_data *) calloc (1, sizeof(*data));
417   data->callback   = callback;
418   data->closure    = closure;
419   data->screen     = screen;
420   data->window     = window;
421   data->drawable   = drawable;
422   data->pipe       = fdopen (fds[0], "r");
423   wpipe            = fdopen (fds[1], "w");   /* Is this necessary? */
424
425   if (!data->pipe || !wpipe)
426     {
427       sprintf (buf, "%s: fdopen", progname);
428       perror (buf);
429       exit (1);
430     }
431
432   data->pipe_id =
433     XtAppAddInput (app, fileno (data->pipe),
434                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
435                    xscreensaver_getimage_cb, (XtPointer) data);
436
437   forked = fork ();
438   switch ((int) forked)
439     {
440     case -1:
441       sprintf (buf, "%s: couldn't fork", progname);
442       perror (buf);
443       return;
444
445     case 0:                                     /* child */
446
447       fclose (data->pipe);
448       data->pipe = 0;
449
450       /* clone the write pipe onto stdout so that it gets closed
451          when the fork exits.  This will shut down the pipe and
452          signal the parent.
453        */
454       close (fileno (stdout));
455       dup2 (fds[1], fileno (stdout));
456       close (fds[1]);
457
458       close (fileno (stdin)); /* for kicks */
459
460       exec_simple_command (command);
461       exit (1);  /* exits child fork */
462       break;
463
464     default:                                    /* parent */
465       fclose (wpipe);
466       data->pid = forked;
467       break;
468     }
469 }
470
471
472 /* Called in the parent when the forked process dies.
473    Runs the caller's callback, and cleans up.
474    This runs when "xscreensaver-getimage" exits.
475    Used only when running "real" X11, not jwxyz.
476  */
477 static void
478 xscreensaver_getimage_cb (XtPointer closure, int *fd, XtIntervalId *id)
479 {
480   xscreensaver_getimage_data *data = (xscreensaver_getimage_data *) closure;
481   Display *dpy = DisplayOfScreen (data->screen);
482   char *name;
483   XRectangle geom = { 0, 0, 0, 0 };
484
485   XtRemoveInput (*id);
486
487   name = get_name_from_xprops (dpy, data->window);
488   get_original_geometry_from_xprops (dpy, data->window, &geom);
489
490   data->callback (data->screen, data->window, data->drawable,
491                   name, &geom, data->closure);
492   if (name) free (name);
493
494   fclose (data->pipe);
495
496   if (data->pid)        /* reap zombies */
497     {
498       int status;
499       waitpid (data->pid, &status, 0);
500       data->pid = 0;
501     }
502
503   memset (data, 0, sizeof (*data));
504   free (data);
505 }
506
507
508 /* Loads an image into the Drawable.
509    When grabbing desktop images, the Window will be unmapped first.
510    Used only when running "real" X11, not jwxyz.
511  */
512 static void
513 load_random_image_x11 (Screen *screen, Window window, Drawable drawable,
514                        void (*callback) (Screen *, Window, Drawable,
515                                          const char *name, XRectangle *geom,
516                                          void *closure),
517                        void *closure)
518 {
519   Display *dpy = DisplayOfScreen (screen);
520   char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber");
521   char *cmd;
522   char id[200];
523
524   if (!grabber || !*grabber)
525     {
526       fprintf (stderr,
527          "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
528                progname);
529       exit (1);
530     }
531
532   sprintf (id, "0x%lx 0x%lx",
533            (unsigned long) window,
534            (unsigned long) drawable);
535   cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
536
537   /* Needn't worry about buffer overflows here, because the buffer is
538      longer than the length of the format string, and the length of what
539      we're putting into it.  So the only way to crash would be if the
540      format string itself was corrupted, but that comes from the
541      resource database, and if hostile forces have access to that,
542      then the game is already over.
543    */
544   sprintf (cmd, grabber, id);
545   free (grabber);
546   grabber = 0;
547
548   /* In case "cmd" fails, leave some random image on the screen, not just
549      black or white, so that it's more obvious what went wrong. */
550   checkerboard (screen, drawable);
551   if (window == drawable)
552     print_loading_msg (screen, window);
553
554   XSync (dpy, True);
555   hack_subproc_environment (dpy);
556
557   /* Start the image loading in another fork and return immediately.
558      Invoke the callback function when done. */
559   fork_exec_cb (cmd, screen, window, drawable, callback, closure);
560
561   free (cmd);
562   XSync (dpy, True);
563 }
564
565 #elif defined (HAVE_COCOA) /* OSX or iOS */
566
567 # ifndef USE_IPHONE   /* HAVE_COCOA && !USE_IPHONE -- desktop OSX */
568
569 # define BACKSLASH(c) \
570   (! ((c >= 'a' && c <= 'z') || \
571       (c >= 'A' && c <= 'Z') || \
572       (c >= '0' && c <= '9') || \
573       c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
574
575 /* Gets the name of an image file to load by running xscreensaver-getimage-file
576    at the end of a pipe.  This can be very slow!
577  */
578 static FILE *
579 open_image_name_pipe (const char *dir)
580 {
581   char *s;
582
583   /* /bin/sh on OS X 10.10 wipes out the PATH. */
584   const char *path = getenv("PATH");
585   char *cmd = s = malloc ((strlen(dir) + strlen(path)) * 2 + 100);
586   strcpy (s, "/bin/sh -c 'export PATH=");
587   s += strlen (s);
588   while (*path) {
589     char c = *path++;
590     if (BACKSLASH(c)) *s++ = '\\';
591     *s++ = c;
592   }
593   strcpy (s, "; ");
594   s += strlen (s);
595
596   strcpy (s, "xscreensaver-getimage-file --name ");
597   s += strlen (s);
598   while (*dir) {
599     char c = *dir++;
600     if (BACKSLASH(c)) *s++ = '\\';
601     *s++ = c;
602   }
603
604   strcpy (s, "'");
605   s += strlen (s);
606
607   *s = 0;
608
609   FILE *pipe = popen (cmd, "r");
610   free (cmd);
611   return pipe;
612 }
613
614
615 static void
616 xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id)
617 {
618   /* This is not called from a signal handler, so doing stuff here is fine.
619    */
620   xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
621   char buf[10240];
622   const char *dir = clo2->directory;
623   char *absfile = 0;
624   *buf = 0;
625   fgets (buf, sizeof(buf)-1, clo2->pipe);
626   pclose (clo2->pipe);
627   clo2->pipe = 0;
628   XtRemoveInput (clo2->pipe_id);
629   clo2->pipe_id = 0;
630
631   /* strip trailing newline */
632   int L = strlen(buf);
633   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
634     buf[--L] = 0;
635
636   Display *dpy = DisplayOfScreen (clo2->screen);
637   XRectangle geom;
638
639   if (*buf && *buf != '/')              /* pathname is relative to dir. */
640     {
641       absfile = malloc (strlen(dir) + strlen(buf) + 10);
642       strcpy (absfile, dir);
643       if (dir[strlen(dir)-1] != '/')
644         strcat (absfile, "/");
645       strcat (absfile, buf);
646     }
647
648   if (! osx_load_image_file (clo2->screen, clo2->window, clo2->drawable,
649                              (absfile ? absfile : buf), &geom)) {
650     /* unable to load image - draw colorbars 
651      */
652     XWindowAttributes xgwa;
653     XGetWindowAttributes (dpy, clo2->window, &xgwa);
654     Window r;
655     int x, y;
656     unsigned int w, h, bbw, d;
657     struct stat st;
658
659     /* Log something to syslog so we can tell the difference between
660        corrupted images and broken symlinks. */
661     if (!*buf)
662       fprintf (stderr, "%s: no image filename found\n", progname);
663     else if (! stat (buf, &st))
664       fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
665     else
666       {
667         char buf2[2048];
668         sprintf (buf2, "%.255s: %.1024s", progname, buf);
669         perror (buf2);
670       }
671
672     XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
673     draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
674                     0, 0, w, h);
675     geom.x = geom.y = 0;
676     geom.width = w;
677     geom.height = h;
678   }
679
680   /* Take the extension off of the file name. */
681   /* Duplicated in driver/xscreensaver-getimage.c. */
682   if (*buf)
683     {
684       char *slash = strrchr (buf, '/');
685       char *dot = strrchr ((slash ? slash : buf), '.');
686       if (dot) *dot = 0;
687       /* Replace slashes with newlines */
688       /* while (dot = strchr(buf, '/')) *dot = '\n'; */
689       /* Replace slashes with spaces */
690       /* while ((dot = strchr(buf, '/'))) *dot = ' '; */
691     }
692
693   if (absfile) free (absfile);
694   clo2->callback (clo2->screen, clo2->window, clo2->drawable, buf, &geom,
695                   clo2->closure);
696   clo2->callback = 0;
697   free (clo2->directory);
698   free (clo2);
699 }
700
701
702 # else   /* HAVE_COCOA && USE_IPHONE -- iOS */
703
704 /* Callback for ios_load_random_image(), called after we have loaded an
705    image from the iOS device's Photo Library.  See grabclient-ios.m.
706  */
707 static void
708 ios_load_random_image_cb (void *uiimage, const char *filename, 
709                           int width, int height, void *closure)
710 {
711   xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure;
712   Display *dpy = DisplayOfScreen (clo2->screen);
713   XRectangle geom;
714   XWindowAttributes xgwa;
715   Window r;
716   int x, y;
717   unsigned int w, h, bbw, d;
718   int rot = 0;
719
720   XGetWindowAttributes (dpy, clo2->window, &xgwa);
721   XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
722
723   /* If the image is portrait and the window is landscape, or vice versa,
724      rotate the image. The idea is to fill up as many pixels as possible,
725      and assume the user will just rotate their phone until it looks right.
726      This makes "decayscreen", etc. much more easily viewable.
727    */
728   if (get_boolean_resource (dpy, "rotateImages", "RotateImages")) {
729     if ((width > height) != (w > h))
730       rot = 5;
731   }
732
733   if (uiimage)
734     {
735       jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen), 
736                                      clo2->drawable,
737                                      True, uiimage, &geom,
738                                      rot);
739     }
740   else  /* Probably means no images in the gallery. */
741     {
742       draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
743                       0, 0, w, h);
744       geom.x = geom.y = 0;
745       geom.width = w;
746       geom.height = h;
747       filename = 0;
748     }
749
750   clo2->callback (clo2->screen, clo2->window, clo2->drawable,
751                   filename, &geom, clo2->closure);
752   clo2->callback = 0;
753   free (clo2);
754 }
755
756 # endif /* HAVE_COCOA && USE_IPHONE */
757
758
759 static void
760 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
761                            const char *dir,
762                            void (*callback) (Screen *, Window, Drawable,
763                                              const char *name,
764                                              XRectangle *geom,
765                                              void *closure),
766                        void *closure)
767 {
768   xscreensaver_getimage_data *clo2 =
769     (xscreensaver_getimage_data *) calloc (1, sizeof(*clo2));
770
771   clo2->screen = screen;
772   clo2->window = xwindow;
773   clo2->drawable = drawable;
774   clo2->callback = callback;
775   clo2->closure = closure;
776
777 # ifndef USE_IPHONE   /* Desktop OSX */
778   clo2->directory = strdup (dir);
779   clo2->pipe = open_image_name_pipe (dir);
780   clo2->pipe_id = XtAppAddInput (XtDisplayToApplicationContext (
781                             DisplayOfScreen (screen)), 
782                             fileno (clo2->pipe),
783                             (XtPointer) (XtInputReadMask | XtInputExceptMask),
784                             xscreensaver_getimage_file_cb, (XtPointer) clo2);
785 # else /* USE_IPHONE */
786   {
787     XWindowAttributes xgwa;
788     XGetWindowAttributes (DisplayOfScreen (screen), xwindow, &xgwa);
789     ios_load_random_image (ios_load_random_image_cb, clo2,
790                            xgwa.width, xgwa.height);
791   }
792 # endif /* USE_IPHONE */
793 }
794
795
796 /* Loads an image into the Drawable, returning once the image is loaded.
797  */
798 static void
799 load_random_image_cocoa (Screen *screen, Window window, Drawable drawable,
800                          void (*callback) (Screen *, Window, Drawable,
801                                            const char *name, XRectangle *geom,
802                                            void *closure),
803                          void *closure)
804 {
805   Display *dpy = DisplayOfScreen (screen);
806   XWindowAttributes xgwa;
807   Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
808   Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
809   const char *dir = 0;
810   Bool done = False;
811   XRectangle geom;
812   char *name = 0;
813   
814   if (!drawable) abort();
815
816   XGetWindowAttributes (dpy, window, &xgwa);
817   {
818     Window r;
819     int x, y;
820     unsigned int w, h, bbw, d;
821     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
822     xgwa.width = w;
823     xgwa.height = h;
824   }
825
826   geom.x = 0;
827   geom.y = 0;
828   geom.width  = xgwa.width;
829   geom.height = xgwa.height;
830
831 # ifndef USE_IPHONE
832   if (filep)
833     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
834
835   if (!dir || !*dir)
836     filep = False;
837 # endif /* ! USE_IPHONE */
838
839   if (deskp && filep) {
840     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
841     filep = !deskp;
842   }
843
844   if (filep && !done) {
845     osx_load_image_file_async (screen, window, drawable, dir, 
846                                callback, closure);
847     return;
848   }
849
850   if (deskp && !done) {
851     if (osx_grab_desktop_image (screen, window, drawable, &geom)) {
852       name = strdup ("desktop");
853       done = True;
854     }
855   }
856
857   if (! done)
858     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
859                     0, 0, xgwa.width, xgwa.height);
860
861   /* If we got here, we loaded synchronously, so we're done. */
862   callback (screen, window, drawable, name, &geom, closure);
863   if (name) free (name);
864 }
865
866
867 #elif defined(HAVE_ANDROID)
868
869 /* Loads an image into the Drawable, returning once the image is loaded.
870  */
871 static void
872 load_random_image_android (Screen *screen, Window window, Drawable drawable,
873                            void (*callback) (Screen *, Window, Drawable,
874                                              const char *name,
875                                              XRectangle *geom, void *closure),
876                            void *closure)
877 {
878   Display *dpy = DisplayOfScreen (screen);
879   XWindowAttributes xgwa;
880   XRectangle geom;
881   char *name = 0;
882   char *data = 0;
883   int width  = 0;
884   int height = 0;
885   
886   if (!drawable) abort();
887
888   XGetWindowAttributes (dpy, window, &xgwa);
889   {
890     Window r;
891     int x, y;
892     unsigned int w, h, bbw, d;
893     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
894     xgwa.width = w;
895     xgwa.height = h;
896   }
897
898   geom.x = 0;
899   geom.y = 0;
900   geom.width  = xgwa.width;
901   geom.height = xgwa.height;
902
903   data = jwxyz_load_random_image (dpy, &width, &height, &name);
904   if (! data)
905     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
906                     0, 0, xgwa.width, xgwa.height);
907   else
908     {
909       XImage *img = XCreateImage (dpy, xgwa.visual, 32,
910                                   ZPixmap, 0, data, width, height, 0, 0);
911       XGCValues gcv;
912       GC gc;
913       gcv.foreground = BlackPixelOfScreen (screen);
914       gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
915       XFillRectangle (dpy, drawable, gc, 0, 0, xgwa.width, xgwa.height);
916       XPutImage (dpy, drawable, gc, img, 0, 0, 
917                  (xgwa.width  - width) / 2,
918                  (xgwa.height - height) / 2,
919                  width, height);
920       XDestroyImage (img);
921       XFreeGC (dpy, gc);
922     }
923
924   callback (screen, window, drawable, name, &geom, closure);
925   if (name) free (name);
926 }
927
928 #endif /* HAVE_ANDROID */
929
930
931
932 /* Writes the string "Loading..." in the middle of the screen.
933    This will presumably get blown away when the image finally loads,
934    minutes or hours later...
935
936    This is called by load_image_async_simple() but not by load_image_async(),
937    since it is assumed that hacks that are loading more than one image
938    *at one time* will be doing something more clever than just blocking
939    with a blank screen.
940  */
941 static void
942 print_loading_msg (Screen *screen, Window window)
943 {
944   Display *dpy = DisplayOfScreen (screen);
945   XWindowAttributes xgwa;
946   XGCValues gcv;
947   XFontStruct *f = 0;
948   GC gc;
949   char *fn = get_string_resource (dpy, "labelFont", "Font");
950   const char *text = "Loading...";
951   int w;
952
953   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
954   if (!fn) fn = get_string_resource (dpy, "font", "Font");
955   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
956   f = XLoadQueryFont (dpy, fn);
957   if (!f) f = XLoadQueryFont (dpy, "fixed");
958   if (!f) abort();
959   free (fn);
960   fn = 0;
961
962   XGetWindowAttributes (dpy, window, &xgwa);
963   w = XTextWidth (f, text, (int) strlen(text));
964
965   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
966                                        "foreground", "Foreground");
967   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
968                                        "background", "Background");
969   gcv.font = f->fid;
970   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
971   XDrawImageString (dpy, window, gc,
972                     (xgwa.width - w) / 2,
973                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
974                     text, (int) strlen(text));
975   XFreeFont (dpy, f);
976   XFreeGC (dpy, gc);
977   XSync (dpy, False);
978 }
979
980
981 /* Loads an image into the Drawable in the background;
982    when the image is fully loaded, runs the callback.
983    When grabbing desktop images, the Window will be unmapped first.
984  */
985 void
986 load_image_async (Screen *screen, Window window, Drawable drawable,
987                   void (*callback) (Screen *, Window, Drawable,
988                                     const char *name, XRectangle *geom,
989                                     void *closure),
990                   void *closure)
991 {
992   if (!callback) abort();
993 # if defined(HAVE_COCOA)
994   load_random_image_cocoa   (screen, window, drawable, callback, closure);
995 # elif defined(HAVE_ANDROID)
996   load_random_image_android (screen, window, drawable, callback, closure);
997 # else /* real X11 */
998   load_random_image_x11     (screen, window, drawable, callback, closure);
999 # endif
1000 }
1001
1002 struct async_load_state {
1003   Bool done_p;
1004   char *filename;
1005   XRectangle geom;
1006 };
1007
1008 static void
1009 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
1010                             const char *name, XRectangle *geom, void *closure)
1011 {
1012   async_load_state *state = (async_load_state *) closure;
1013   state->done_p = True;
1014   state->filename = (name ? strdup (name) : 0);
1015   state->geom = *geom;
1016 }
1017
1018 async_load_state *
1019 load_image_async_simple (async_load_state *state,
1020                          Screen *screen,
1021                          Window window,
1022                          Drawable drawable, 
1023                          char **filename_ret,
1024                          XRectangle *geometry_ret)
1025 {
1026   if (state && state->done_p)           /* done! */
1027     {
1028       if (filename_ret)
1029         *filename_ret = state->filename;
1030       else if (state->filename)
1031         free (state->filename);
1032
1033       if (geometry_ret)
1034         *geometry_ret = state->geom;
1035
1036       free (state);
1037       return 0;
1038     }
1039   else if (! state)                     /* first time */
1040     {
1041       state = (async_load_state *) calloc (1, sizeof(*state));
1042       state->done_p = False;
1043       print_loading_msg (screen, window);
1044       load_image_async (screen, window, drawable, 
1045                         load_image_async_simple_cb,
1046                         state);
1047       return state;
1048     }
1049   else                                  /* still waiting */
1050     return state;
1051 }