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