From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992-2013 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, void *closure)
652 {
653   struct pipe_closure *clo2 = (struct pipe_closure *) closure;
654   Display *dpy = DisplayOfScreen (clo2->screen);
655   XRectangle geom;
656
657   if (uiimage)
658     {
659       jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen), 
660                                      clo2->drawable,
661                                      True, uiimage, &geom,
662                                      0);
663     }
664   else  /* Probably means no images in the gallery. */
665     {
666       XWindowAttributes xgwa;
667       Window r;
668       int x, y;
669       unsigned int w, h, bbw, d;
670       XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
671       XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
672       draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
673                       0, 0, w, h);
674       geom.x = geom.y = 0;
675       geom.width = w;
676       geom.height = h;
677       filename = 0;
678     }
679
680   clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable,
681                   filename, &geom, clo2->closure);
682   clo2->callback = 0;
683   if (clo2->directory) free (clo2->directory);
684   free (clo2);
685 }
686
687 # endif /* USE_IPHONE */
688
689
690 static void
691 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
692                            const char *dir,
693                            void (*callback) (Screen *, Window, Drawable,
694                                              const char *name,
695                                              XRectangle *geom,
696                                              void *closure),
697                        void *closure)
698 {
699 # if 0  /* do it synchronously */
700
701   FILE *pipe = open_image_name_pipe (dir);
702   char buf[10240];
703   *buf = 0;
704   fgets (buf, sizeof(buf)-1, pipe);
705   pclose (pipe);
706
707   /* strip trailing newline */
708   int L = strlen(buf);
709   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
710     buf[--L] = 0;
711
712   XRectangle geom;
713   if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
714     /* draw colorbars */
715     abort();
716   }
717   callback (screen, xwindow, drawable, buf, &geom, closure);
718
719 # else  /* do it asynchronously */
720
721   struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
722
723   clo2->screen = screen;
724   clo2->xwindow = xwindow;
725   clo2->drawable = drawable;
726   clo2->callback = callback;
727   clo2->closure = closure;
728
729 #  ifndef USE_IPHONE
730   clo2->directory = strdup (dir);
731   clo2->pipe = open_image_name_pipe (dir);
732   clo2->id = XtAppAddInput (XtDisplayToApplicationContext (
733                               DisplayOfScreen (screen)), 
734                             fileno (clo2->pipe),
735                             (XtPointer) (XtInputReadMask | XtInputExceptMask),
736                             pipe_cb, (XtPointer) clo2);
737 #  else /* USE_IPHONE */
738   ios_load_random_image (ios_load_random_image_cb, clo2);
739 #  endif /* USE_IPHONE */
740
741 # endif
742 }
743
744
745 /* Loads an image into the Drawable, returning once the image is loaded.
746  */
747 static void
748 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
749                      void (*callback) (Screen *, Window, Drawable,
750                                        const char *name, XRectangle *geom,
751                                        void *closure),
752                      void *closure,
753                      char **name_ret,
754                      XRectangle *geom_ret)
755 {
756   Display *dpy = DisplayOfScreen (screen);
757   XWindowAttributes xgwa;
758   Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
759   Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
760   const char *dir = 0;
761   Bool done = False;
762   XRectangle geom_ret_2;
763   char *name_ret_2 = 0;
764   
765   if (!drawable) abort();
766
767   if (callback) {
768     geom_ret = &geom_ret_2;
769     name_ret = &name_ret_2;
770   }
771
772   XGetWindowAttributes (dpy, window, &xgwa);
773   {
774     Window r;
775     int x, y;
776     unsigned int w, h, bbw, d;
777     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
778     xgwa.width = w;
779     xgwa.height = h;
780   }
781
782   if (name_ret)
783     *name_ret = 0;
784
785   if (geom_ret) {
786     geom_ret->x = 0;
787     geom_ret->y = 0;
788     geom_ret->width  = xgwa.width;
789     geom_ret->height = xgwa.height;
790   }
791
792 # ifndef USE_IPHONE
793   if (filep)
794     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
795
796   if (!dir || !*dir)
797     filep = False;
798 # endif /* ! USE_IPHONE */
799
800   if (deskp && filep) {
801     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
802     filep = !deskp;
803   }
804
805   if (filep && !done) {
806     osx_load_image_file_async (screen, window, drawable, dir, 
807                                callback, closure);
808     return;
809   }
810
811   if (deskp && !done) {
812     if (osx_grab_desktop_image (screen, window, drawable, &geom_ret_2)) {
813       if (name_ret)
814         *name_ret = strdup ("desktop");
815       done = True;
816     }
817   }
818
819   if (! done)
820     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
821                     0, 0, xgwa.width, xgwa.height);
822
823   if (callback) {
824     /* If we got here, we loaded synchronously even though they wanted async.
825      */
826     callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
827     if (name_ret_2) free (name_ret_2);
828   }
829 }
830
831 #endif /* HAVE_COCOA */
832
833
834 /* Writes the string "Loading..." in the middle of the screen.
835    This will presumably get blown away when the image finally loads,
836    minutes or hours later...
837
838    This is called by load_image_async_simple() but not by load_image_async(),
839    since it is assumed that hacks that are loading more than one image
840    *at one time* will be doing something more clever than just blocking
841    with a blank screen.
842  */
843 static void
844 print_loading_msg (Screen *screen, Window window)
845 {
846   Display *dpy = DisplayOfScreen (screen);
847   XWindowAttributes xgwa;
848   XGCValues gcv;
849   XFontStruct *f = 0;
850   GC gc;
851   char *fn = get_string_resource (dpy, "labelFont", "Font");
852   const char *text = "Loading...";
853   int w;
854
855   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
856   if (!fn) fn = get_string_resource (dpy, "font", "Font");
857   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
858   f = XLoadQueryFont (dpy, fn);
859   if (!f) f = XLoadQueryFont (dpy, "fixed");
860   if (!f) abort();
861   free (fn);
862   fn = 0;
863
864   XGetWindowAttributes (dpy, window, &xgwa);
865   w = XTextWidth (f, text, (int) strlen(text));
866
867   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
868                                        "foreground", "Foreground");
869   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
870                                        "background", "Background");
871   gcv.font = f->fid;
872   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
873   XDrawImageString (dpy, window, gc,
874                     (xgwa.width - w) / 2,
875                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
876                     text, (int) strlen(text));
877   XFreeFont (dpy, f);
878   XFreeGC (dpy, gc);
879   XSync (dpy, False);
880 }
881
882
883 /* Loads an image into the Drawable in the background;
884    when the image is fully loaded, runs the callback.
885    When grabbing desktop images, the Window will be unmapped first.
886  */
887 void
888 load_image_async (Screen *screen, Window window, Drawable drawable,
889                   void (*callback) (Screen *, Window, Drawable,
890                                     const char *name, XRectangle *geom,
891                                     void *closure),
892                   void *closure)
893 {
894   load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
895 }
896
897 struct async_load_state {
898   Bool done_p;
899   char *filename;
900   XRectangle geom;
901 };
902
903 static void
904 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
905                             const char *name, XRectangle *geom, void *closure)
906 {
907   async_load_state *state = (async_load_state *) closure;
908   state->done_p = True;
909   state->filename = (name ? strdup (name) : 0);
910   state->geom = *geom;
911 }
912
913 async_load_state *
914 load_image_async_simple (async_load_state *state,
915                          Screen *screen,
916                          Window window,
917                          Drawable drawable, 
918                          char **filename_ret,
919                          XRectangle *geometry_ret)
920 {
921   if (state && state->done_p)           /* done! */
922     {
923       if (filename_ret)
924         *filename_ret = state->filename;
925       else if (state->filename)
926         free (state->filename);
927
928       if (geometry_ret)
929         *geometry_ret = state->geom;
930
931       free (state);
932       return 0;
933     }
934   else if (! state)                     /* first time */
935     {
936       state = (async_load_state *) calloc (1, sizeof(*state));
937       state->done_p = False;
938       print_loading_msg (screen, window);
939       load_image_async (screen, window, drawable, 
940                         load_image_async_simple_cb,
941                         state);
942       return state;
943     }
944   else                                  /* still waiting */
945     return state;
946 }