438197e80da1ff1eb2b4db6dcbb9d8e5a47c7cae
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992-2010 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   void (*callback) (Screen *, Window, Drawable,
549                     const char *name, XRectangle *geom,
550                     void *closure);
551   void *closure;
552 };
553
554
555 static void
556 pipe_cb (XtPointer closure, int *source, XtInputId *id)
557 {
558   /* This is not called from a signal handler, so doing stuff here is fine.
559    */
560   struct pipe_closure *clo2 = (struct pipe_closure *) closure;
561   char buf[10240];
562   fgets (buf, sizeof(buf)-1, clo2->pipe);
563   pclose (clo2->pipe);
564   clo2->pipe = 0;
565   XtRemoveInput (clo2->id);
566   clo2->id = 0;
567
568   /* strip trailing newline */
569   int L = strlen(buf);
570   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
571     buf[--L] = 0;
572
573   Display *dpy = DisplayOfScreen (clo2->screen);
574   XRectangle geom;
575
576   if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
577                              buf, &geom)) {
578     /* unable to load image - draw colorbars 
579      */
580     XWindowAttributes xgwa;
581     XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
582     Window r;
583     int x, y;
584     unsigned int w, h, bbw, d;
585     struct stat st;
586
587     /* Log something to syslog so we can tell the difference between
588        corrupted images and broken symlinks. */
589     if (!*buf)
590       fprintf (stderr, "%s: no image filename found\n", progname);
591     else if (! stat (buf, &st))
592       fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
593     else
594       {
595         char buf2[2048];
596         sprintf (buf2, "%.255s: %.1024s", progname, buf);
597         perror (buf2);
598       }
599
600     XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
601     draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
602                     0, 0, w, h);
603     geom.x = geom.y = 0;
604     geom.width = w;
605     geom.height = h;
606   }
607
608   clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
609                   clo2->closure);
610   clo2->callback = 0;
611   free (clo2);
612 }
613
614
615 static void
616 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
617                            const char *dir,
618                            void (*callback) (Screen *, Window, Drawable,
619                                              const char *name,
620                                              XRectangle *geom,
621                                              void *closure),
622                        void *closure)
623 {
624 #if 0   /* do it synchronously */
625
626   FILE *pipe = open_image_name_pipe (dir);
627   char buf[10240];
628   *buf = 0;
629   fgets (buf, sizeof(buf)-1, pipe);
630   pclose (pipe);
631
632   /* strip trailing newline */
633   int L = strlen(buf);
634   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
635     buf[--L] = 0;
636
637   XRectangle geom;
638   if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
639     /* draw colorbars */
640     abort();
641   }
642   callback (screen, xwindow, drawable, buf, &geom, closure);
643
644 #else   /* do it asynchronously */
645
646   Display *dpy = DisplayOfScreen (screen);
647   struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
648   clo2->pipe = open_image_name_pipe (dir);
649   clo2->id = XtAppAddInput (XtDisplayToApplicationContext (dpy), 
650                             fileno (clo2->pipe),
651                             (XtPointer) (XtInputReadMask | XtInputExceptMask),
652                             pipe_cb, (XtPointer) clo2);
653   clo2->screen = screen;
654   clo2->xwindow = xwindow;
655   clo2->drawable = drawable;
656   clo2->callback = callback;
657   clo2->closure = closure;
658 #endif
659 }
660
661
662 /* Loads an image into the Drawable, returning once the image is loaded.
663  */
664 static void
665 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
666                      void (*callback) (Screen *, Window, Drawable,
667                                        const char *name, XRectangle *geom,
668                                        void *closure),
669                      void *closure,
670                      char **name_ret,
671                      XRectangle *geom_ret)
672 {
673   Display *dpy = DisplayOfScreen (screen);
674   XWindowAttributes xgwa;
675   Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
676   Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
677   const char *dir = 0;
678   Bool done = False;
679   XRectangle geom_ret_2;
680   char *name_ret_2 = 0;
681
682   if (!drawable) abort();
683
684   if (callback) {
685     geom_ret = &geom_ret_2;
686     name_ret = &name_ret_2;
687   }
688
689   XGetWindowAttributes (dpy, window, &xgwa);
690   {
691     Window r;
692     int x, y;
693     unsigned int w, h, bbw, d;
694     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
695     xgwa.width = w;
696     xgwa.height = h;
697   }
698
699   if (name_ret)
700     *name_ret = 0;
701
702   if (geom_ret) {
703     geom_ret->x = 0;
704     geom_ret->y = 0;
705     geom_ret->width  = xgwa.width;
706     geom_ret->height = xgwa.height;
707   }
708
709   if (filep)
710     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
711
712   if (!dir || !*dir)
713     filep = False;
714
715   if (deskp && filep) {
716     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
717     filep = !deskp;
718   }
719
720   if (filep && !done) {
721     osx_load_image_file_async (screen, window, drawable, dir, 
722                                callback, closure);
723     return;
724   }
725
726   if (deskp && !done) {
727     osx_grab_desktop_image (screen, window, drawable);
728     if (name_ret)
729       *name_ret = strdup ("desktop");
730     done = True;
731   }
732
733   if (! done)
734     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
735                     0, 0, xgwa.width, xgwa.height);
736
737   if (callback) {
738     /* If we got here, we loaded synchronously even though they wanted async.
739      */
740     callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
741   }
742 }
743
744 #endif /* HAVE_COCOA */
745
746
747 /* Writes the string "Loading..." in the middle of the screen.
748    This will presumably get blown away when the image finally loads,
749    minutes or hours later...
750
751    This is called by load_image_async_simple() but not by load_image_async(),
752    since it is assumed that hacks that are loading more than one image
753    *at one time* will be doing something more clever than just blocking
754    with a blank screen.
755  */
756 static void
757 print_loading_msg (Screen *screen, Window window)
758 {
759   Display *dpy = DisplayOfScreen (screen);
760   XWindowAttributes xgwa;
761   XGCValues gcv;
762   XFontStruct *f = 0;
763   GC gc;
764   char *fn = get_string_resource (dpy, "labelFont", "Font");
765   const char *text = "Loading...";
766   int w;
767
768   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
769   if (!fn) fn = get_string_resource (dpy, "font", "Font");
770   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
771   f = XLoadQueryFont (dpy, fn);
772   if (!f) f = XLoadQueryFont (dpy, "fixed");
773   if (!f) abort();
774   free (fn);
775   fn = 0;
776
777   XGetWindowAttributes (dpy, window, &xgwa);
778   w = XTextWidth (f, text, strlen(text));
779
780   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
781                                        "foreground", "Foreground");
782   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
783                                        "background", "Background");
784   gcv.font = f->fid;
785   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
786   XDrawImageString (dpy, window, gc,
787                     (xgwa.width - w) / 2,
788                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
789                     text, strlen(text));
790   XFreeFont (dpy, f);
791   XFreeGC (dpy, gc);
792   XSync (dpy, False);
793 }
794
795
796 /* Loads an image into the Drawable in the background;
797    when the image is fully loaded, runs the callback.
798    When grabbing desktop images, the Window will be unmapped first.
799  */
800 void
801 load_image_async (Screen *screen, Window window, Drawable drawable,
802                   void (*callback) (Screen *, Window, Drawable,
803                                     const char *name, XRectangle *geom,
804                                     void *closure),
805                   void *closure)
806 {
807   load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
808 }
809
810 struct async_load_state {
811   Bool done_p;
812   char *filename;
813   XRectangle geom;
814 };
815
816 static void
817 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
818                             const char *name, XRectangle *geom, void *closure)
819 {
820   async_load_state *state = (async_load_state *) closure;
821   state->done_p = True;
822   state->filename = (name ? strdup (name) : 0);
823   state->geom = *geom;
824 }
825
826 async_load_state *
827 load_image_async_simple (async_load_state *state,
828                          Screen *screen,
829                          Window window,
830                          Drawable drawable, 
831                          char **filename_ret,
832                          XRectangle *geometry_ret)
833 {
834   if (state && state->done_p)           /* done! */
835     {
836       if (filename_ret)
837         *filename_ret = state->filename;
838       else if (state->filename)
839         free (state->filename);
840
841       if (geometry_ret)
842         *geometry_ret = state->geom;
843
844       free (state);
845       return 0;
846     }
847   else if (! state)                     /* first time */
848     {
849       state = (async_load_state *) calloc (1, sizeof(*state));
850       state->done_p = False;
851       print_loading_msg (screen, window);
852       load_image_async (screen, window, drawable, 
853                         load_image_async_simple_cb,
854                         state);
855       return state;
856     }
857   else                                  /* still waiting */
858     return state;
859 }