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