fixed problem with distort theme
[xscreensaver] / 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   int  view = 1;
454   char *cmd;
455   char id[400];
456
457   view = get_integer_resource(dpy, "view", "view");
458   if (!grabber || !*grabber)
459     {
460       fprintf (stderr,
461          "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
462                progname);
463       exit (1);
464     }
465
466   sprintf (id, "-file /home/user/.backgrounds/background-%i.png 0x%lx 0x%lx", view,
467            (unsigned long) window,
468            (unsigned long) drawable);
469   cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
470
471   /* Needn't worry about buffer overflows here, because the buffer is
472      longer than the length of the format string, and the length of what
473      we're putting into it.  So the only way to crash would be if the
474      format string itself was corrupted, but that comes from the
475      resource database, and if hostile forces have access to that,
476      then the game is already over.
477    */
478   sprintf (cmd, grabber, id);
479   free (grabber);
480   grabber = 0;
481
482   /* In case "cmd" fails, leave some random image on the screen, not just
483      black or white, so that it's more obvious what went wrong. */
484   checkerboard (screen, drawable);
485   if (window == drawable)
486     print_loading_msg (screen, window);
487
488   XSync (dpy, True);
489   hack_subproc_environment (dpy);
490
491   if (callback)
492     {
493       /* Start the image loading in another fork and return immediately.
494          Invoke the callback function when done.
495        */
496       if (name_ret) abort();
497       fork_exec_cb (cmd, screen, window, drawable, callback, closure);
498     }
499   else
500     {
501       /* Wait for the image to load, and return it immediately.
502        */
503       fork_exec_wait (cmd);
504       if (name_ret)
505         *name_ret = get_name (dpy, window);
506       if (geom_ret)
507         get_geometry (dpy, window, geom_ret);
508     }
509
510   free (cmd);
511   XSync (dpy, True);
512 }
513
514 #else  /* HAVE_COCOA */
515
516 /* Gets the name of an image file to load by running xscreensaver-getimage-file
517    at the end of a pipe.  This can be very slow!
518  */
519 static FILE *
520 open_image_name_pipe (const char *dir)
521 {
522   char *cmd = malloc (strlen(dir) * 2 + 100);
523   char *s;
524   strcpy (cmd, "xscreensaver-getimage-file --name ");
525   s = cmd + strlen (cmd);
526   while (*dir) {
527     char c = *dir++;
528     /* put a backslash in front of any character that might confuse sh. */
529     if (! ((c >= 'a' && c <= 'z') ||
530            (c >= 'A' && c <= 'Z') ||
531            (c >= '0' && c <= '9') ||
532            c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
533       *s++ = '\\';
534     *s++ = c;
535   }
536   *s = 0;
537
538   FILE *pipe = popen (cmd, "r");
539   free (cmd);
540   return pipe;
541 }
542
543
544 struct pipe_closure {
545   FILE *pipe;
546   XtInputId id;
547   Screen *screen;
548   Window xwindow;
549   Drawable drawable;
550   void (*callback) (Screen *, Window, Drawable,
551                     const char *name, XRectangle *geom,
552                     void *closure);
553   void *closure;
554 };
555
556
557 static void
558 pipe_cb (XtPointer closure, int *source, XtInputId *id)
559 {
560   /* This is not called from a signal handler, so doing stuff here is fine.
561    */
562   struct pipe_closure *clo2 = (struct pipe_closure *) closure;
563   char buf[10240];
564   fgets (buf, sizeof(buf)-1, clo2->pipe);
565   pclose (clo2->pipe);
566   clo2->pipe = 0;
567   XtRemoveInput (clo2->id);
568   clo2->id = 0;
569
570   /* strip trailing newline */
571   int L = strlen(buf);
572   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
573     buf[--L] = 0;
574
575   Display *dpy = DisplayOfScreen (clo2->screen);
576   XRectangle geom;
577
578   if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
579                              buf, &geom)) {
580     /* unable to load image - draw colorbars 
581      */
582     XWindowAttributes xgwa;
583     XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
584     Window r;
585     int x, y;
586     unsigned int w, h, bbw, d;
587     struct stat *st;
588
589     /* Log something to syslog so we can tell the difference between
590        corrupted images and broken symlinks. */
591     if (!*buf)
592       fprintf (stderr, "%s: no image filename found\n", progname);
593     else if (! stat (buf, st))
594       fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
595     else
596       {
597         char buf2[2048];
598         sprintf (buf2, "%.255s: %.1024s", progname, buf);
599         perror (buf2);
600       }
601
602     XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
603     draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
604                     0, 0, w, h);
605     geom.x = geom.y = 0;
606     geom.width = w;
607     geom.height = h;
608   }
609
610   clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
611                   clo2->closure);
612   clo2->callback = 0;
613   free (clo2);
614 }
615
616
617 static void
618 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
619                            const char *dir,
620                            void (*callback) (Screen *, Window, Drawable,
621                                              const char *name,
622                                              XRectangle *geom,
623                                              void *closure),
624                        void *closure)
625 {
626 #if 0   /* do it synchronously */
627
628   FILE *pipe = open_image_name_pipe (dir);
629   char buf[10240];
630   *buf = 0;
631   fgets (buf, sizeof(buf)-1, pipe);
632   pclose (pipe);
633
634   /* strip trailing newline */
635   int L = strlen(buf);
636   while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
637     buf[--L] = 0;
638
639   XRectangle geom;
640   if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
641     /* draw colorbars */
642     abort();
643   }
644   callback (screen, xwindow, drawable, buf, &geom, closure);
645
646 #else   /* do it asynchronously */
647
648   Display *dpy = DisplayOfScreen (screen);
649   struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
650   clo2->pipe = open_image_name_pipe (dir);
651   clo2->id = XtAppAddInput (XtDisplayToApplicationContext (dpy), 
652                             fileno (clo2->pipe),
653                             (XtPointer) (XtInputReadMask | XtInputExceptMask),
654                             pipe_cb, (XtPointer) clo2);
655   clo2->screen = screen;
656   clo2->xwindow = xwindow;
657   clo2->drawable = drawable;
658   clo2->callback = callback;
659   clo2->closure = closure;
660 #endif
661 }
662
663
664 /* Loads an image into the Drawable, returning once the image is loaded.
665  */
666 static void
667 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
668                      void (*callback) (Screen *, Window, Drawable,
669                                        const char *name, XRectangle *geom,
670                                        void *closure),
671                      void *closure,
672                      char **name_ret,
673                      XRectangle *geom_ret)
674 {
675   Display *dpy = DisplayOfScreen (screen);
676   XWindowAttributes xgwa;
677   Bool deskp = get_boolean_resource (dpy, "grabDesktopImages",  "Boolean");
678   Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
679   const char *dir = 0;
680   Bool done = False;
681   XRectangle geom_ret_2;
682   char *name_ret_2 = 0;
683
684   if (!drawable) abort();
685
686   if (callback) {
687     geom_ret = &geom_ret_2;
688     name_ret = &name_ret_2;
689   }
690
691   XGetWindowAttributes (dpy, window, &xgwa);
692   {
693     Window r;
694     int x, y;
695     unsigned int w, h, bbw, d;
696     XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
697     xgwa.width = w;
698     xgwa.height = h;
699   }
700
701   if (name_ret)
702     *name_ret = 0;
703
704   if (geom_ret) {
705     geom_ret->x = 0;
706     geom_ret->y = 0;
707     geom_ret->width  = xgwa.width;
708     geom_ret->height = xgwa.height;
709   }
710
711   if (filep)
712     dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
713
714   if (!dir || !*dir)
715     filep = False;
716
717   if (deskp && filep) {
718     deskp = !(random() & 5);    /* if both, desktop 1/5th of the time */
719     filep = !deskp;
720   }
721
722   if (filep && !done) {
723     osx_load_image_file_async (screen, window, drawable, dir, 
724                                callback, closure);
725     return;
726   }
727
728   if (deskp && !done) {
729     osx_grab_desktop_image (screen, window, drawable);
730     if (name_ret)
731       *name_ret = strdup ("desktop");
732     done = True;
733   }
734
735   if (! done) {
736     draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
737                     0, 0, xgwa.width, xgwa.height);
738     done = True;
739   }
740
741   if (callback) {
742     /* If we got here, we loaded synchronously even though they wanted async.
743      */
744     callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
745   }
746 }
747
748 #endif /* HAVE_COCOA */
749
750
751 /* Writes the string "Loading..." in the middle of the screen.
752    This will presumably get blown away when the image finally loads,
753    minutes or hours later...
754
755    This is called by load_image_async_simple() but not by load_image_async(),
756    since it is assumed that hacks that are loading more than one image
757    *at one time* will be doing something more clever than just blocking
758    with a blank screen.
759  */
760 static void
761 print_loading_msg (Screen *screen, Window window)
762 {
763   Display *dpy = DisplayOfScreen (screen);
764   XWindowAttributes xgwa;
765   XGCValues gcv;
766   XFontStruct *f = 0;
767   GC gc;
768   char *fn = get_string_resource (dpy, "labelFont", "Font");
769   const char *text = "Loading...";
770   int w;
771
772   if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
773   if (!fn) fn = get_string_resource (dpy, "font", "Font");
774   if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
775   f = XLoadQueryFont (dpy, fn);
776   if (!f) f = XLoadQueryFont (dpy, "fixed");
777   if (!f) abort();
778   free (fn);
779   fn = 0;
780
781   XGetWindowAttributes (dpy, window, &xgwa);
782   w = XTextWidth (f, text, strlen(text));
783
784   gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
785                                        "foreground", "Foreground");
786   gcv.background = get_pixel_resource (dpy, xgwa.colormap,
787                                        "background", "Background");
788   gcv.font = f->fid;
789   gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
790   XDrawImageString (dpy, window, gc,
791                     (xgwa.width - w) / 2,
792                     (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
793                     text, strlen(text));
794   XFreeFont (dpy, f);
795   XFreeGC (dpy, gc);
796   XSync (dpy, False);
797 }
798
799
800 /* Loads an image into the Drawable in the background;
801    when the image is fully loaded, runs the callback.
802    When grabbing desktop images, the Window will be unmapped first.
803  */
804 void
805 load_image_async (Screen *screen, Window window, Drawable drawable,
806                   void (*callback) (Screen *, Window, Drawable,
807                                     const char *name, XRectangle *geom,
808                                     void *closure),
809                   void *closure)
810 {
811   load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
812 }
813
814 struct async_load_state {
815   Bool done_p;
816   char *filename;
817   XRectangle geom;
818 };
819
820 static void
821 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
822                             const char *name, XRectangle *geom, void *closure)
823 {
824   async_load_state *state = (async_load_state *) closure;
825   state->done_p = True;
826   state->filename = (name ? strdup (name) : 0);
827   state->geom = *geom;
828 }
829
830 async_load_state *
831 load_image_async_simple (async_load_state *state,
832                          Screen *screen,
833                          Window window,
834                          Drawable drawable, 
835                          char **filename_ret,
836                          XRectangle *geometry_ret)
837 {
838   if (state && state->done_p)           /* done! */
839     {
840       if (filename_ret)
841         *filename_ret = state->filename;
842       else if (state->filename)
843         free (state->filename);
844
845       if (geometry_ret)
846         *geometry_ret = state->geom;
847
848       free (state);
849       return 0;
850     }
851   else if (! state)                     /* first time */
852     {
853       state = (async_load_state *) calloc (1, sizeof(*state));
854       state->done_p = False;
855       print_loading_msg (screen, window);
856       load_image_async (screen, window, drawable, 
857                         load_image_async_simple_cb,
858                         state);
859       return state;
860     }
861   else                                  /* still waiting */
862     return state;
863 }