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