8f6778bc0d3b0ff66cf7cb33c12730eb4d972c8c
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992-2006 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 (callback) {
661     geom_ret = &geom_ret_2;
662     name_ret = &name_ret_2;
663   }
664
665   XGetWindowAttributes (dpy, window, &xgwa);
666   {
667     Window r;
668     int x, y;
669     unsigned int w, h, bbw, d;
670     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
671     xgwa.width = w;
672     xgwa.height = h;
673   }
674
675   if (name_ret)
676     *name_ret = 0;
677
678   if (geom_ret) {
679     geom_ret->x = 0;
680     geom_ret->y = 0;
681     geom_ret->width  = xgwa.width;
682     geom_ret->height = xgwa.height;
683   }
684
685   if (filep)
686     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
687
688   if (!dir || !*dir)
689     filep = False;
690
691   if (deskp && filep) {
692     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
693     filep = !deskp;
694   }
695
696   if (filep && !done) {
697     osx_load_image_file_async (screen, window, drawable, dir, 
698                                callback, closure);
699     return;
700   }
701
702   if (deskp && !done) {
703     osx_grab_desktop_image (screen, window, drawable);
704     if (name_ret)
705       *name_ret = strdup ("desktop");
706     done = True;
707   }
708
709   if (! done) {
710     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
711                     0, 0, xgwa.width, xgwa.height);
712     done = True;
713   }
714
715   if (callback) {
716     /* If we got here, we loaded synchronously even though they wanted async.
717      */
718     callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
719   }
720 }
721
722 #endif /* HAVE_COCOA */
723
724
725 /* Writes the string "Loading..." in the middle of the screen.
726    This will presumably get blown away when the image finally loads,
727    minutes or hours later...
728
729    This is called by load_image_async_simple() but not by load_image_async(),
730    since it is assumed that hacks that are loading more than one image
731    *at one time* will be doing something more clever than just blocking
732    with a blank screen.
733  */
734 static void
735 print_loading_msg (Screen *screen, Window window)
736 {
737   Display *dpy = DisplayOfScreen (screen);
738   XWindowAttributes xgwa;
739   XGCValues gcv;
740   XFontStruct *f = 0;
741   GC gc;
742   char *fn = get_string_resource (dpy, "labelFont", "Font");
743   const char *text = "Loading...";
744   int w;
745
746   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
747   if (!fn) fn = get_string_resource (dpy, "font", "Font");
748   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
749   f = XLoadQueryFont (dpy, fn);
750   if (!f) f = XLoadQueryFont (dpy, "fixed");
751   if (!f) abort();
752   free (fn);
753   fn = 0;
754
755   XGetWindowAttributes (dpy, window, &xgwa);
756   w = XTextWidth (f, text, strlen(text));
757
758   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
759                                        "foreground", "Foreground");
760   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
761                                        "background", "Background");
762   gcv.font = f->fid;
763   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
764   XDrawImageString (dpy, window, gc,
765                     (xgwa.width - w) / 2,
766                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
767                     text, strlen(text));
768   XFreeFont (dpy, f);
769   XFreeGC (dpy, gc);
770   XSync (dpy, False);
771 }
772
773
774 /* Loads an image into the Drawable in the background;
775    when the image is fully loaded, runs the callback.
776    When grabbing desktop images, the Window will be unmapped first.
777  */
778 void
779 load_image_async (Screen *screen, Window window, Drawable drawable,
780                   void (*callback) (Screen *, Window, Drawable,
781                                     const char *name, XRectangle *geom,
782                                     void *closure),
783                   void *closure)
784 {
785   load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
786 }
787
788 struct async_load_state {
789   Bool done_p;
790   char *filename;
791   XRectangle geom;
792 };
793
794 static void
795 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
796                             const char *name, XRectangle *geom, void *closure)
797 {
798   async_load_state *state = (async_load_state *) closure;
799   state->done_p = True;
800   state->filename = (name ? strdup (name) : 0);
801   state->geom = *geom;
802 }
803
804 async_load_state *
805 load_image_async_simple (async_load_state *state,
806                          Screen *screen,
807                          Window window,
808                          Drawable drawable, 
809                          char **filename_ret,
810                          XRectangle *geometry_ret)
811 {
812   if (state && state->done_p)           /* done! */
813     {
814       if (filename_ret)
815         *filename_ret = state->filename;
816       else if (state->filename)
817         free (state->filename);
818
819       if (geometry_ret)
820         *geometry_ret = state->geom;
821
822       free (state);
823       return 0;
824     }
825   else if (! state)                     /* first time */
826     {
827       state = (async_load_state *) calloc (1, sizeof(*state));
828       state->done_p = False;
829       print_loading_msg (screen, window);
830       load_image_async (screen, window, drawable, 
831                         load_image_async_simple_cb,
832                         state);
833       return state;
834     }
835   else                                  /* still waiting */
836     return state;
837 }