From http://www.jwz.org/xscreensaver/xscreensaver-5.22.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   fgets (buf, sizeof(buf)-1, clo2->pipe);
568   pclose (clo2->pipe);
569   clo2->pipe = 0;
570   XtRemoveInput (clo2->id);
571   clo2->id = 0;
572
573   /* strip trailing newline */
574   int L = strlen(buf);
575   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
576     buf[--L] = 0;
577
578   Display *dpy = DisplayOfScreen (clo2->screen);
579   XRectangle geom;
580
581   if (*buf && *buf != '/')              /* pathname is relative to dir. */
582     {
583       absfile = malloc (strlen(dir) + strlen(buf) + 10);
584       strcpy (absfile, dir);
585       if (dir[strlen(dir)-1] != '/')
586         strcat (absfile, "/");
587       strcat (absfile, buf);
588     }
589
590   if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
591                              (absfile ? absfile : buf), &geom)) {
592     /* unable to load image - draw colorbars 
593      */
594     XWindowAttributes xgwa;
595     XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
596     Window r;
597     int x, y;
598     unsigned int w, h, bbw, d;
599     struct stat st;
600
601     /* Log something to syslog so we can tell the difference between
602        corrupted images and broken symlinks. */
603     if (!*buf)
604       fprintf (stderr, "%s: no image filename found\n", progname);
605     else if (! stat (buf, &st))
606       fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
607     else
608       {
609         char buf2[2048];
610         sprintf (buf2, "%.255s: %.1024s", progname, buf);
611         perror (buf2);
612       }
613
614     XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
615     draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
616                     0, 0, w, h);
617     geom.x = geom.y = 0;
618     geom.width = w;
619     geom.height = h;
620   }
621
622   /* Take the extension off of the file name. */
623   /* Duplicated in driver/xscreensaver-getimage.c. */
624   if (buf && *buf)
625     {
626       char *slash = strrchr (buf, '/');
627       char *dot = strrchr ((slash ? slash : buf), '.');
628       if (dot) *dot = 0;
629       /* Replace slashes with newlines */
630       /* while (dot = strchr(buf, '/')) *dot = '\n'; */
631       /* Replace slashes with spaces */
632       while ((dot = strchr(buf, '/'))) *dot = ' ';
633     }
634
635   if (absfile) free (absfile);
636   clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
637                   clo2->closure);
638   clo2->callback = 0;
639   free (clo2->directory);
640   free (clo2);
641 }
642
643
644 # else  /* USE_IPHONE */
645
646 /* Callback for ios_load_random_image(), called after we have loaded an
647    image from the iOS device's Photo Library.  See iosgrabimage.m.
648  */
649 static void
650 ios_load_random_image_cb (void *uiimage, const char *filename, void *closure)
651 {
652   struct pipe_closure *clo2 = (struct pipe_closure *) closure;
653   Display *dpy = DisplayOfScreen (clo2->screen);
654   XRectangle geom;
655
656   if (uiimage)
657     {
658       jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen), 
659                                      clo2->drawable,
660                                      True, uiimage, &geom,
661                                      0);
662     }
663   else  /* Probably means no images in the gallery. */
664     {
665       XWindowAttributes xgwa;
666       Window r;
667       int x, y;
668       unsigned int w, h, bbw, d;
669       XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
670       XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
671       draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
672                       0, 0, w, h);
673       geom.x = geom.y = 0;
674       geom.width = w;
675       geom.height = h;
676       filename = 0;
677     }
678
679   clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable,
680                   filename, &geom, clo2->closure);
681   clo2->callback = 0;
682   if (clo2->directory) free (clo2->directory);
683   free (clo2);
684 }
685
686 # endif /* USE_IPHONE */
687
688
689 static void
690 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
691                            const char *dir,
692                            void (*callback) (Screen *, Window, Drawable,
693                                              const char *name,
694                                              XRectangle *geom,
695                                              void *closure),
696                        void *closure)
697 {
698 # if 0  /* do it synchronously */
699
700   FILE *pipe = open_image_name_pipe (dir);
701   char buf[10240];
702   *buf = 0;
703   fgets (buf, sizeof(buf)-1, pipe);
704   pclose (pipe);
705
706   /* strip trailing newline */
707   int L = strlen(buf);
708   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
709     buf[--L] = 0;
710
711   XRectangle geom;
712   if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
713     /* draw colorbars */
714     abort();
715   }
716   callback (screen, xwindow, drawable, buf, &geom, closure);
717
718 # else  /* do it asynchronously */
719
720   struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
721
722   clo2->screen = screen;
723   clo2->xwindow = xwindow;
724   clo2->drawable = drawable;
725   clo2->callback = callback;
726   clo2->closure = closure;
727
728 #  ifndef USE_IPHONE
729   clo2->directory = strdup (dir);
730   clo2->pipe = open_image_name_pipe (dir);
731   clo2->id = XtAppAddInput (XtDisplayToApplicationContext (
732                               DisplayOfScreen (screen)), 
733                             fileno (clo2->pipe),
734                             (XtPointer) (XtInputReadMask | XtInputExceptMask),
735                             pipe_cb, (XtPointer) clo2);
736 #  else /* USE_IPHONE */
737   ios_load_random_image (ios_load_random_image_cb, clo2);
738 #  endif /* USE_IPHONE */
739
740 # endif
741 }
742
743
744 /* Loads an image into the Drawable, returning once the image is loaded.
745  */
746 static void
747 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
748                      void (*callback) (Screen *, Window, Drawable,
749                                        const char *name, XRectangle *geom,
750                                        void *closure),
751                      void *closure,
752                      char **name_ret,
753                      XRectangle *geom_ret)
754 {
755   Display *dpy = DisplayOfScreen (screen);
756   XWindowAttributes xgwa;
757   Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
758   Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
759   const char *dir = 0;
760   Bool done = False;
761   XRectangle geom_ret_2;
762   char *name_ret_2 = 0;
763   
764   if (!drawable) abort();
765
766   if (callback) {
767     geom_ret = &geom_ret_2;
768     name_ret = &name_ret_2;
769   }
770
771   XGetWindowAttributes (dpy, window, &xgwa);
772   {
773     Window r;
774     int x, y;
775     unsigned int w, h, bbw, d;
776     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
777     xgwa.width = w;
778     xgwa.height = h;
779   }
780
781   if (name_ret)
782     *name_ret = 0;
783
784   if (geom_ret) {
785     geom_ret->x = 0;
786     geom_ret->y = 0;
787     geom_ret->width  = xgwa.width;
788     geom_ret->height = xgwa.height;
789   }
790
791 # ifndef USE_IPHONE
792   if (filep)
793     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
794
795   if (!dir || !*dir)
796     filep = False;
797 # endif /* ! USE_IPHONE */
798
799   if (deskp && filep) {
800     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
801     filep = !deskp;
802   }
803
804   if (filep && !done) {
805     osx_load_image_file_async (screen, window, drawable, dir, 
806                                callback, closure);
807     return;
808   }
809
810   if (deskp && !done) {
811     if (osx_grab_desktop_image (screen, window, drawable, &geom_ret_2)) {
812       if (name_ret)
813         *name_ret = strdup ("desktop");
814       done = True;
815     }
816   }
817
818   if (! done)
819     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
820                     0, 0, xgwa.width, xgwa.height);
821
822   if (callback) {
823     /* If we got here, we loaded synchronously even though they wanted async.
824      */
825     callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
826     if (name_ret_2) free (name_ret_2);
827   }
828 }
829
830 #endif /* HAVE_COCOA */
831
832
833 /* Writes the string "Loading..." in the middle of the screen.
834    This will presumably get blown away when the image finally loads,
835    minutes or hours later...
836
837    This is called by load_image_async_simple() but not by load_image_async(),
838    since it is assumed that hacks that are loading more than one image
839    *at one time* will be doing something more clever than just blocking
840    with a blank screen.
841  */
842 static void
843 print_loading_msg (Screen *screen, Window window)
844 {
845   Display *dpy = DisplayOfScreen (screen);
846   XWindowAttributes xgwa;
847   XGCValues gcv;
848   XFontStruct *f = 0;
849   GC gc;
850   char *fn = get_string_resource (dpy, "labelFont", "Font");
851   const char *text = "Loading...";
852   int w;
853
854   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
855   if (!fn) fn = get_string_resource (dpy, "font", "Font");
856   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
857   f = XLoadQueryFont (dpy, fn);
858   if (!f) f = XLoadQueryFont (dpy, "fixed");
859   if (!f) abort();
860   free (fn);
861   fn = 0;
862
863   XGetWindowAttributes (dpy, window, &xgwa);
864   w = XTextWidth (f, text, strlen(text));
865
866   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
867                                        "foreground", "Foreground");
868   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
869                                        "background", "Background");
870   gcv.font = f->fid;
871   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
872   XDrawImageString (dpy, window, gc,
873                     (xgwa.width - w) / 2,
874                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
875                     text, strlen(text));
876   XFreeFont (dpy, f);
877   XFreeGC (dpy, gc);
878   XSync (dpy, False);
879 }
880
881
882 /* Loads an image into the Drawable in the background;
883    when the image is fully loaded, runs the callback.
884    When grabbing desktop images, the Window will be unmapped first.
885  */
886 void
887 load_image_async (Screen *screen, Window window, Drawable drawable,
888                   void (*callback) (Screen *, Window, Drawable,
889                                     const char *name, XRectangle *geom,
890                                     void *closure),
891                   void *closure)
892 {
893   load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
894 }
895
896 struct async_load_state {
897   Bool done_p;
898   char *filename;
899   XRectangle geom;
900 };
901
902 static void
903 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
904                             const char *name, XRectangle *geom, void *closure)
905 {
906   async_load_state *state = (async_load_state *) closure;
907   state->done_p = True;
908   state->filename = (name ? strdup (name) : 0);
909   state->geom = *geom;
910 }
911
912 async_load_state *
913 load_image_async_simple (async_load_state *state,
914                          Screen *screen,
915                          Window window,
916                          Drawable drawable, 
917                          char **filename_ret,
918                          XRectangle *geometry_ret)
919 {
920   if (state && state->done_p)           /* done! */
921     {
922       if (filename_ret)
923         *filename_ret = state->filename;
924       else if (state->filename)
925         free (state->filename);
926
927       if (geometry_ret)
928         *geometry_ret = state->geom;
929
930       free (state);
931       return 0;
932     }
933   else if (! state)                     /* first time */
934     {
935       state = (async_load_state *) calloc (1, sizeof(*state));
936       state->done_p = False;
937       print_loading_msg (screen, window);
938       load_image_async (screen, window, drawable, 
939                         load_image_async_simple_cb,
940                         state);
941       return state;
942     }
943   else                                  /* still waiting */
944     return state;
945 }