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