24c0246e262d2c1a1214f5e5d666121c5f2f8fd8
[xscreensaver] / driver / xscreensaver-getimage.c
1 /* xscreensaver, Copyright (c) 2001-2004 by 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 /* xscreensaver-getimage -- helper program that puts a random image
13    onto the given window or pixmap.  That image is either a screen-grab,
14    a file loaded from disk, or a frame grabbed from the system's video
15    input.
16  */
17
18 #include "utils.h"
19
20 #include <X11/Intrinsic.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <sys/stat.h>
24 #include <sys/time.h>
25
26 #ifdef HAVE_SYS_WAIT_H
27 # include <sys/wait.h>          /* for waitpid() and associated macros */
28 #endif
29
30 #ifdef HAVE_XMU
31 # ifndef VMS
32 #  include <X11/Xmu/Error.h>
33 # else /* VMS */
34 #  include <Xmu/Error.h>
35 # endif
36 #else
37 # include "xmu.h"
38 #endif
39
40 #include "yarandom.h"
41 #include "grabscreen.h"
42 #include "resources.h"
43 #include "colorbars.h"
44 #include "visual.h"
45 #include "prefs.h"
46 #include "version.h"
47 #include "vroot.h"
48
49 #ifndef _XSCREENSAVER_VROOT_H_
50 # error Error!  You have an old version of vroot.h!  Check -I args.
51 #endif /* _XSCREENSAVER_VROOT_H_ */
52
53 #ifdef HAVE_GDK_PIXBUF
54 # undef HAVE_JPEGLIB
55 # ifdef HAVE_GTK2
56 #  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
57 # else  /* !HAVE_GTK2 */
58 #  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
59 # endif /* !HAVE_GTK2 */
60 #endif /* HAVE_GDK_PIXBUF */
61
62 #ifdef HAVE_JPEGLIB
63 # undef HAVE_GDK_PIXBUF
64 # include <jpeglib.h>
65 #endif
66
67
68 #ifdef __APPLE__
69   /* On MacOSX / XDarwin, the usual X11 mechanism of getting a screen shot
70      doesn't work, and we need to use an external program. */
71 # define USE_EXTERNAL_SCREEN_GRABBER
72 #endif
73
74
75 #ifdef __GNUC__
76  __extension__     /* shut up about "string length is greater than the length
77                       ISO C89 compilers are required to support" when including
78                       the .ad file... */
79 #endif
80
81 static char *defaults[] = {
82 #include "../driver/XScreenSaver_ad.h"
83  0
84 };
85
86
87
88 char *progname = 0;
89 char *progclass = "XScreenSaver";
90 XrmDatabase db;
91 XtAppContext app;
92
93 extern void grabscreen_verbose (void);
94
95 typedef enum {
96   GRAB_DESK, GRAB_VIDEO, GRAB_FILE, GRAB_BARS
97 } grab_type;
98
99
100 #define GETIMAGE_VIDEO_PROGRAM   "xscreensaver-getimage-video"
101 #define GETIMAGE_FILE_PROGRAM    "xscreensaver-getimage-file"
102 #define GETIMAGE_SCREEN_PROGRAM  "xscreensaver-getimage-desktop"
103
104 const char *
105 blurb (void)
106 {
107   return progname;
108 }
109
110
111 static int
112 x_ehandler (Display *dpy, XErrorEvent *error)
113 {
114   if (error->error_code == BadWindow || error->error_code == BadDrawable)
115     {
116       fprintf (stderr, "%s: target %s 0x%lx unexpectedly deleted\n", progname,
117                (error->error_code == BadWindow ? "window" : "pixmap"),
118                (unsigned long) error->resourceid);
119     }
120   else
121     {
122       fprintf (stderr, "\nX error in %s:\n", progname);
123       XmuPrintDefaultErrorMessage (dpy, error, stderr);
124     }
125   exit (-1);
126   return 0;
127 }
128
129
130 static Bool error_handler_hit_p = False;
131
132 static int
133 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
134 {
135   error_handler_hit_p = True;
136   return 0;
137 }
138
139 #ifndef USE_EXTERNAL_SCREEN_GRABBER
140 static int
141 ignore_badmatch_ehandler (Display *dpy, XErrorEvent *error)
142 {
143   if (error->error_code == BadMatch)
144     return ignore_all_errors_ehandler (dpy, error);
145   else
146     return x_ehandler (dpy, error);
147 }
148 #endif /* ! USE_EXTERNAL_SCREEN_GRABBER */
149
150
151 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
152  */
153 static Bool
154 drawable_window_p (Display *dpy, Drawable d)
155 {
156   XErrorHandler old_handler;
157   XWindowAttributes xgwa;
158
159   XSync (dpy, False);
160   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
161   error_handler_hit_p = False;
162   XGetWindowAttributes (dpy, d, &xgwa);
163   XSync (dpy, False);
164   XSetErrorHandler (old_handler);
165   XSync (dpy, False);
166
167   if (!error_handler_hit_p)
168     return True;   /* It's a Window. */
169   else
170     return False;  /* It's a Pixmap, or an invalid ID. */
171 }
172
173
174 /* Returns true if the window is the root window, or a virtual root window,
175    but *not* the xscreensaver window.  That is, if it's a "real" desktop
176    root window of some kind.
177  */
178 static Bool
179 root_window_p (Screen *screen, Window window)
180 {
181   Display *dpy = DisplayOfScreen (screen);
182   Atom type;
183   int format;
184   unsigned long nitems, bytesafter;
185   unsigned char *version;
186
187   if (window != RootWindowOfScreen (screen))
188     return False;
189
190   if (XGetWindowProperty (dpy, window,
191                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
192                           0, 1, False, XA_STRING,
193                           &type, &format, &nitems, &bytesafter,
194                           &version)
195       == Success
196       && type != None)
197     return False;
198
199   return True;
200 }
201
202
203 /* Clear the window or pixmap to black, or its background color.
204  */
205 static void
206 clear_drawable (Screen *screen, Drawable drawable)
207 {
208   Display *dpy = DisplayOfScreen (screen);
209   XGCValues gcv;
210   GC gc;
211   Window root;
212   int x, y;
213   unsigned int w, h, bw, d;
214   XGetGeometry (dpy, drawable, &root, &x, &y, &w, &h, &bw, &d);
215
216   /* The window might have no-op background of None, so to clear it,
217      draw a black rectangle first, then do XClearWindow (in case the
218      actual background color is non-black...) */
219
220   /* #### really we should allocate "black" instead, but I'm lazy... */
221   gcv.foreground = BlackPixelOfScreen (screen);
222   gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
223   XFillRectangle (dpy, drawable, gc, 0, 0, w, h);
224   XFreeGC (dpy, gc);
225   if (drawable_window_p (dpy, drawable))
226     XClearWindow (dpy, (Window) drawable);
227   XFlush (dpy);
228 }
229
230
231 /* Figure out what kind of scaling/positioning we ought to do to display
232    a src-sized image in a dest-sized window/pixmap.  Returns the width
233    and height to which the image should be scaled, and the position where
234    it should be displayed to center it.
235  */
236 static void
237 compute_image_scaling (int src_w, int src_h,
238                        int dest_w, int dest_h,
239                        Bool verbose_p,
240                        int *scaled_from_x_ret, int *scaled_from_y_ret,
241                        int *scaled_to_x_ret, int *scaled_to_y_ret,
242                        int *scaled_w_ret, int *scaled_h_ret)
243 {
244   int srcx, srcy, destx, desty;
245
246   Bool exact_fit_p = ((src_w == dest_w && src_h <= dest_h) ||
247                       (src_h == dest_h && src_w <= dest_w));
248
249   if (!exact_fit_p)  /* scale the image up or down */
250     {
251       float rw = (float) dest_w  / src_w;
252       float rh = (float) dest_h / src_h;
253       float r = (rw < rh ? rw : rh);
254       int tw = src_w * r;
255       int th = src_h * r;
256       int pct = (r * 100);
257
258 #if 0
259       /* this optimization breaks things */
260       if (pct < 95 || pct > 105)  /* don't scale if it's close */
261 #endif
262         {
263           if (verbose_p)
264             fprintf (stderr, "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
265                      progname, pct, src_w, src_h, tw, th);
266           src_w = tw;
267           src_h = th;
268         }
269     }
270
271   /* Center the image on the window/pixmap. */
272   srcx = 0;
273   srcy = 0;
274   destx = (dest_w - src_w) / 2;
275   desty = (dest_h - src_h) / 2;
276   if (destx < 0) srcx = -destx, destx = 0;
277   if (desty < 0) srcy = -desty, desty = 0;
278
279   if (dest_w < src_w) src_w = dest_w;
280   if (dest_h < src_h) src_h = dest_h;
281
282   *scaled_w_ret = src_w;
283   *scaled_h_ret = src_h;
284   *scaled_from_x_ret = srcx;
285   *scaled_from_y_ret = srcy;
286   *scaled_to_x_ret = destx;
287   *scaled_to_y_ret = desty;
288
289   if (verbose_p)
290     fprintf (stderr, "%s: displaying %dx%d image at %d,%d in %dx%d.\n",
291              progname, src_w, src_h, destx, desty, dest_w, dest_h);
292 }
293
294
295 /* Scales an XImage, modifying it in place.
296    This doesn't do dithering or smoothing, so it might have artifacts.
297    If out of memory, returns False, and the XImage will have been
298    destroyed and freed.
299  */
300 #ifndef USE_EXTERNAL_SCREEN_GRABBER
301 static Bool
302 scale_ximage (Screen *screen, Visual *visual,
303               XImage *ximage, int new_width, int new_height)
304 {
305   Display *dpy = DisplayOfScreen (screen);
306   int depth = visual_depth (screen, visual);
307   int x, y;
308   double xscale, yscale;
309
310   XImage *ximage2 = XCreateImage (dpy, visual, depth,
311                                   ZPixmap, 0, 0,
312                                   new_width, new_height, 8, 0);
313   ximage2->data = (char *) calloc (ximage2->height, ximage2->bytes_per_line);
314
315   if (!ximage2->data)
316     {
317       fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n",
318                progname,
319                ximage->width, ximage->height,
320                ximage2->width, ximage2->height);
321       if (ximage->data) free (ximage->data);
322       if (ximage2->data) free (ximage2->data);
323       ximage->data = 0;
324       ximage2->data = 0;
325       XDestroyImage (ximage);
326       XDestroyImage (ximage2);
327       return False;
328     }
329
330   /* Brute force scaling... */
331   xscale = (double) ximage->width  / ximage2->width;
332   yscale = (double) ximage->height / ximage2->height;
333   for (y = 0; y < ximage2->height; y++)
334     for (x = 0; x < ximage2->width; x++)
335       XPutPixel (ximage2, x, y,
336                  XGetPixel (ximage, x * xscale, y * yscale));
337
338   free (ximage->data);
339   ximage->data = 0;
340
341   (*ximage) = (*ximage2);
342
343   ximage2->data = 0;
344   XDestroyImage (ximage2);
345
346   return True;
347 }
348 #endif /* ! USE_EXTERNAL_SCREEN_GRABBER */
349
350
351 #ifdef HAVE_GDK_PIXBUF
352
353 /* Reads the given image file and renders it on the Drawable, using GDK.
354    Returns False if it fails.
355  */
356 static Bool
357 read_file_gdk (Screen *screen, Window window, Drawable drawable,
358                const char *filename, Bool verbose_p,
359                XRectangle *geom_ret)
360 {
361   GdkPixbuf *pb;
362   Display *dpy = DisplayOfScreen (screen);
363   unsigned int win_width, win_height, win_depth;
364 # ifdef HAVE_GTK2
365   GError *gerr = 0;
366 # endif /* HAVE_GTK2 */
367
368   /* Find the size of the Drawable. */
369   {
370     Window root;
371     int x, y;
372     unsigned int bw;
373     XGetGeometry (dpy, drawable,
374                   &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
375   }
376
377   gdk_pixbuf_xlib_init (dpy, screen_number (screen));
378 # ifdef HAVE_GTK2
379   g_type_init();
380 # else  /* !HAVE_GTK2 */
381   xlib_rgb_init (dpy, screen);
382 # endif /* !HAVE_GTK2 */
383
384   pb = gdk_pixbuf_new_from_file (filename
385 # ifdef HAVE_GTK2
386                                  , &gerr
387 # endif /* HAVE_GTK2 */
388                                  );
389
390   if (!pb)
391     {
392       fprintf (stderr, "%s: unable to load \"%s\"\n", progname, filename);
393 #  ifdef HAVE_GTK2
394       if (gerr && gerr->message && *gerr->message)
395         fprintf (stderr, "%s: reason: %s\n", progname, gerr->message);
396 #  endif /* HAVE_GTK2 */
397       return False;
398     }
399   else
400     {
401       int w = gdk_pixbuf_get_width (pb);
402       int h = gdk_pixbuf_get_height (pb);
403       int srcx, srcy, destx, desty, w2, h2;
404       Bool bg_p = False;
405
406       compute_image_scaling (w, h, win_width, win_height, verbose_p,
407                              &srcx, &srcy, &destx, &desty, &w2, &h2);
408       if (w != w2 || h != h2)
409         {
410           GdkPixbuf *pb2 = gdk_pixbuf_scale_simple (pb, w2, h2,
411                                                     GDK_INTERP_BILINEAR);
412           if (pb2)
413             {
414               gdk_pixbuf_unref (pb);
415               pb = pb2;
416               w = w2;
417               h = h2;
418             }
419           else
420             fprintf (stderr, "%s: out of memory when scaling?\n", progname);
421         }
422
423       /* If we're rendering onto the root window (and it's not the
424          xscreensaver pseudo-root) then put the image in the window's
425          background.  Otherwise, just paint the image onto the window.
426        */
427       bg_p = (window == drawable && root_window_p (screen, window));
428
429       if (bg_p)
430         {
431           XGCValues gcv;
432           GC gc;
433           drawable = XCreatePixmap (dpy, window,
434                                     win_width, win_height, win_depth);
435           gcv.foreground = BlackPixelOfScreen (screen);
436           gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
437           XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
438           XFreeGC (dpy, gc);
439         }
440       else
441         clear_drawable (screen, drawable);
442
443       /* #### Note that this always uses the default colormap!  Morons!
444          Owen says that in Gnome 2.0, I should try using
445          gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
446          But I haven't tried.
447        */
448       gdk_pixbuf_xlib_render_to_drawable_alpha (pb, drawable,
449                                                 srcx, srcy, destx, desty,
450                                                 w, h,
451                                                 GDK_PIXBUF_ALPHA_FULL, 127,
452                                                 XLIB_RGB_DITHER_NORMAL,
453                                                 0, 0);
454       if (bg_p)
455         {
456           XSetWindowBackgroundPixmap (dpy, window, drawable);
457           XClearWindow (dpy, window);
458         }
459
460       if (geom_ret)
461         {
462           geom_ret->x = destx;
463           geom_ret->y = desty;
464           geom_ret->width  = w;
465           geom_ret->height = h;
466         }
467     }
468
469   XSync (dpy, False);
470   return True;
471 }
472
473 #endif /* HAVE_GDK_PIXBUF */
474
475
476
477 #ifdef HAVE_JPEGLIB
478
479 /* Allocates a colormap that makes a PseudoColor or DirectColor
480    visual behave like a TrueColor visual of the same depth.
481  */
482 static void
483 allocate_cubic_colormap (Screen *screen, Visual *visual, Colormap cmap,
484                          Bool verbose_p)
485 {
486   Display *dpy = DisplayOfScreen (screen);
487   int nr, ng, nb, cells;
488   int r, g, b;
489   int depth;
490   XColor colors[4097];
491   int i;
492
493   depth = visual_depth (screen, visual);
494
495   switch (depth)
496     {
497     case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
498     case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
499     default: abort(); break;
500     }
501
502   memset(colors, 0, sizeof(colors));
503   for (r = 0; r < (1 << nr); r++)
504     for (g = 0; g < (1 << ng); g++)
505       for (b = 0; b < (1 << nb); b++)
506         {
507           i = (r | (g << nr) | (b << (nr + ng)));
508           colors[i].pixel = i;
509           colors[i].flags = DoRed|DoGreen|DoBlue;
510           if (depth == 8)
511             {
512               colors[i].red   = ((r << 13) | (r << 10) | (r << 7) |
513                                  (r <<  4) | (r <<  1));
514               colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
515                                  (g <<  4) | (g <<  1));
516               colors[i].blue  = ((b << 14) | (b << 12) | (b << 10) |
517                                  (b <<  8) | (b <<  6) | (b <<  4) |
518                                  (b <<  2) | b);
519             }
520           else
521             {
522               colors[i].red   = (r << 12) | (r << 8) | (r << 4) | r;
523               colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
524               colors[i].blue  = (b << 12) | (b << 8) | (b << 4) | b;
525             }
526         }
527
528   {
529     int j;
530     int allocated = 0;
531     int interleave = cells / 8;  /* skip around, rather than allocating in
532                                     order, so that we get better coverage if
533                                     we can't allocated all of them. */
534     for (j = 0; j < interleave; j++)
535       for (i = 0; i < cells; i += interleave)
536         if (XAllocColor (dpy, cmap, &colors[i + j]))
537           allocated++;
538
539     if (verbose_p)
540       fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n",
541                progname, allocated, cells);
542   }
543 }
544
545 /* Find the pixel index that is closest to the given color
546    (using linear distance in RGB space -- which is far from the best way.)
547  */
548 static unsigned long
549 find_closest_pixel (XColor *colors, int ncolors,
550                     unsigned long r, unsigned long g, unsigned long b)
551 {
552   unsigned long distance = ~0;
553   int i, found = 0;
554
555   if (ncolors == 0)
556     abort();
557   for (i = 0; i < ncolors; i++)
558     {
559       unsigned long d;
560       int rd, gd, bd;
561
562       rd = r - colors[i].red;
563       gd = g - colors[i].green;
564       bd = b - colors[i].blue;
565       if (rd < 0) rd = -rd;
566       if (gd < 0) gd = -gd;
567       if (bd < 0) bd = -bd;
568       d = (rd << 1) + (gd << 2) + bd;
569       
570       if (d < distance)
571         {
572           distance = d;
573           found = i;
574           if (distance == 0)
575               break;
576         }
577     }
578
579   return found;
580 }
581
582
583 /* Given an XImage with 8-bit or 12-bit RGB data, convert it to be 
584    displayable with the given X colormap.  The farther from a perfect
585    color cube the contents of the colormap are, the lossier the 
586    transformation will be.  No dithering is done.
587  */
588 static void
589 remap_image (Screen *screen, Colormap cmap, XImage *image, Bool verbose_p)
590 {
591   Display *dpy = DisplayOfScreen (screen);
592   unsigned long map[4097];
593   int x, y, i;
594   int cells;
595   XColor colors[4097];
596
597   if (image->depth == 8)
598     cells = 256;
599   else if (image->depth == 12)
600     cells = 4096;
601   else
602     abort();
603
604   memset(map,    -1, sizeof(*map));
605   memset(colors, -1, sizeof(*colors));
606
607   for (i = 0; i < cells; i++)
608     colors[i].pixel = i;
609   XQueryColors (dpy, cmap, colors, cells);
610
611   if (verbose_p)
612     fprintf(stderr, "%s: building color cube for %d bit image\n",
613             progname, image->depth);
614
615   for (i = 0; i < cells; i++)
616     {
617       unsigned short r, g, b;
618
619       if (cells == 256)
620         {
621           /* "RRR GGG BB" In an 8 bit map.  Convert that to
622              "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
623              an even spread. */
624           r = (i & 0x07);
625           g = (i & 0x38) >> 3;
626           b = (i & 0xC0) >> 6;
627
628           r = ((r << 13) | (r << 10) | (r << 7) | (r <<  4) | (r <<  1));
629           g = ((g << 13) | (g << 10) | (g << 7) | (g <<  4) | (g <<  1));
630           b = ((b << 14) | (b << 12) | (b << 10) | (b <<  8) |
631                (b <<  6) | (b <<  4) | (b <<  2) | b);
632         }
633       else
634         {
635           /* "RRRR GGGG BBBB" In a 12 bit map.  Convert that to
636              "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
637              spread. */
638           r = (i & 0x00F);
639           g = (i & 0x0F0) >> 4;
640           b = (i & 0xF00) >> 8;
641
642           r = (r << 12) | (r << 8) | (r << 4) | r;
643           g = (g << 12) | (g << 8) | (g << 4) | g;
644           b = (b << 12) | (b << 8) | (b << 4) | b;
645         }
646
647       map[i] = find_closest_pixel (colors, cells, r, g, b);
648     }
649
650   if (verbose_p)
651     fprintf(stderr, "%s: remapping colors in %d bit image\n",
652             progname, image->depth);
653
654   for (y = 0; y < image->height; y++)
655     for (x = 0; x < image->width; x++)
656       {
657         unsigned long pixel = XGetPixel(image, x, y);
658         if (pixel >= cells) abort();
659         XPutPixel(image, x, y, map[pixel]);
660       }
661 }
662
663
664 /* If the file has a PPM (P6) on it, read it and return an XImage.
665    Otherwise, rewind the fd back to the beginning, and return 0.
666  */
667 static XImage *
668 maybe_read_ppm (Screen *screen, Visual *visual,
669                 const char *filename, FILE *in, Bool verbose_p)
670 {
671   Display *dpy = DisplayOfScreen (screen);
672   int depth = visual_depth (screen, visual);
673   struct stat st;
674   char *buf = 0;
675   int bufsiz = 0;
676   char *s, dummy;
677   int i, j;
678   int x, y, w, h, maxval;
679   XImage *ximage = 0;
680
681   if (fstat (fileno (in), &st))
682     goto FAIL;
683
684   bufsiz = st.st_size;
685   buf = (char *) malloc (bufsiz + 1);
686   if (!buf)
687     {
688       fprintf (stderr, "%s: out of memory loading %d byte PPM file %s\n",
689                progname, bufsiz, filename);
690       goto FAIL;
691     }
692
693   if (! (s = fgets (buf, bufsiz, in)))   /* line 1 */
694     goto FAIL;
695
696   if (!strncmp (buf, "\107\111", 2))
697     {
698       fprintf (stderr, "%s: %s: sorry, GIF files not supported"
699                " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
700                progname, filename);
701       goto FAIL;
702     }
703   else if (!strncmp (buf, "\211\120", 2))
704     {
705       fprintf (stderr, "%s: %s: sorry, PNG files not supported"
706                " when compiled with JPEGlib instead of GDK_Pixbuf.\n",
707                progname, filename);
708       goto FAIL;
709     }
710
711   if (strncmp (s, "P6", 2))
712     goto FAIL;
713
714   if (! (s = fgets (buf, bufsiz, in)))   /* line 2 */
715     goto FAIL;
716   if (2 != sscanf (s, " %d %d %c", &w, &h, &dummy))
717     {
718       fprintf (stderr, "%s: %s: invalid PPM (line 2)\n", progname, filename);
719       goto FAIL;
720     }
721
722   if (! (s = fgets (buf, bufsiz, in)))   /* line 3 */
723     goto FAIL;
724   if (1 != sscanf (s, " %d %c", &maxval, &dummy))
725     {
726       fprintf (stderr, "%s: %s: invalid PPM (line 3)\n", progname, filename);
727       goto FAIL;
728     }
729   if (maxval != 255)
730     {
731       fprintf (stderr, "%s: %s: unparsable PPM: maxval is %d\n",
732                progname, filename, maxval);
733       goto FAIL;
734     }
735
736   ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
737                          w, h, 8, 0);
738   if (ximage)
739     ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
740   if (!ximage || !ximage->data)
741     {
742       fprintf (stderr, "%s: out of memory loading %dx%d PPM file %s\n",
743                progname, ximage->width, ximage->height, filename);
744       goto FAIL;
745     }
746
747   s = buf;
748   j = bufsiz;
749   while ((i = fread (s, 1, j, in)) > 0)
750     s += i, j -= i;
751
752   i = 0;
753   for (y = 0; y < ximage->height; y++)
754     for (x = 0; x < ximage->width; x++)
755       {
756         unsigned char r = buf[i++];
757         unsigned char g = buf[i++];
758         unsigned char b = buf[i++];
759         unsigned long pixel;
760
761         if (depth > 16)
762           pixel = (r << 16) | (g << 8) | b;
763         else if (depth == 8)
764           pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
765         else if (depth == 12)
766           pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
767         else if (depth == 16 || depth == 15)
768           pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
769         else
770           abort();
771
772         XPutPixel (ximage, x, y, pixel);
773       }
774
775   free (buf);
776   return ximage;
777
778  FAIL:
779   if (buf) free (buf);
780   if (ximage && ximage->data)
781     {
782       free (ximage->data);
783       ximage->data = 0;
784     }
785   if (ximage) XDestroyImage (ximage);
786   fseek (in, 0, SEEK_SET);
787   return 0;
788 }
789
790
791 typedef struct {
792   struct jpeg_error_mgr pub;   /* this is what passes for subclassing in C */
793   const char *filename;
794   Screen *screen;
795   Visual *visual;
796   Drawable drawable;
797   Colormap cmap;
798 } getimg_jpg_error_mgr;
799
800
801 static void
802 jpg_output_message (j_common_ptr cinfo)
803 {
804   getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
805   char buf[JMSG_LENGTH_MAX];
806   cinfo->err->format_message (cinfo, buf);
807   fprintf (stderr, "%s: %s: %s\n", progname, err->filename, buf);
808 }
809
810
811 static void
812 jpg_error_exit (j_common_ptr cinfo)
813 {
814   getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err;
815   cinfo->err->output_message (cinfo);
816   draw_colorbars (err->screen, err->visual, err->drawable, err->cmap,
817                   0, 0, 0, 0);
818   XSync (DisplayOfScreen (err->screen), False);
819   exit (1);
820 }
821
822
823 /* Reads a JPEG file, returns an RGB XImage of it.
824  */
825 static XImage *
826 read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
827                   Colormap cmap, const char *filename, Bool verbose_p)
828 {
829   Display *dpy = DisplayOfScreen (screen);
830   int depth = visual_depth (screen, visual);
831
832   FILE *in = 0;
833   XImage *ximage = 0;
834   struct jpeg_decompress_struct cinfo;
835   getimg_jpg_error_mgr jerr;
836   JSAMPARRAY scanbuf = 0;
837   int y;
838
839   jerr.filename = filename;
840   jerr.screen = screen;
841   jerr.visual = visual;
842   jerr.drawable = drawable;
843   jerr.cmap = cmap;
844
845   if (! (depth >= 15 || depth == 12 || depth == 8))
846     {
847       fprintf (stderr, "%s: unsupported depth: %d\n", progname, depth);
848       goto FAIL;
849     }
850
851   in = fopen (filename, "rb");
852   if (!in)
853     {
854       fprintf (stderr, "%s: %s: unreadable\n", progname, filename);
855       goto FAIL;
856     }
857
858   /* Check to see if it's a PPM, and if so, read that instead of using
859      the JPEG library.  Yeah, this is all modular and stuff.
860    */
861   if ((ximage = maybe_read_ppm (screen, visual, filename, in, verbose_p)))
862     {
863       fclose (in);
864       return ximage;
865     }
866
867   cinfo.err = jpeg_std_error (&jerr.pub);
868   jerr.pub.output_message = jpg_output_message;
869   jerr.pub.error_exit = jpg_error_exit;
870
871   jpeg_create_decompress (&cinfo);
872   jpeg_stdio_src (&cinfo, in);
873   jpeg_read_header (&cinfo, TRUE);
874
875   /* set some decode parameters */
876   cinfo.out_color_space = JCS_RGB;
877   cinfo.quantize_colors = FALSE;
878
879   jpeg_start_decompress (&cinfo);
880
881   ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
882                          cinfo.output_width, cinfo.output_height,
883                          8, 0);
884   if (ximage)
885     ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
886
887   if (ximage && ximage->data)
888     scanbuf = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE,
889                                           cinfo.rec_outbuf_height *
890                                           cinfo.output_width *
891                                           cinfo.output_components,
892                                           1);
893   if (!ximage || !ximage->data || !scanbuf)
894     {
895       fprintf (stderr, "%s: out of memory loading %dx%d file %s\n",
896                progname, ximage->width, ximage->height, filename);
897       goto FAIL;
898     }
899
900   y = 0;
901   while (cinfo.output_scanline < cinfo.output_height)
902     {
903       int n = jpeg_read_scanlines (&cinfo, scanbuf, 1);
904       int i;
905       for (i = 0; i < n; i++)
906         {
907           int x;
908           for (x = 0; x < ximage->width; x++)
909             {
910               int j = x * cinfo.output_components;
911               unsigned char r = scanbuf[i][j];
912               unsigned char g = scanbuf[i][j+1];
913               unsigned char b = scanbuf[i][j+2];
914               unsigned long pixel;
915
916               if (depth > 16)
917                 pixel = (r << 16) | (g << 8) | b;
918               else if (depth == 8)
919                 pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
920               else if (depth == 12)
921                 pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
922               else if (depth == 15)
923                 /* Gah! I don't understand why these are in the other
924                    order. */
925                 pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
926               else if (depth == 16)
927                 pixel = (((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3)));
928               else
929                 abort();
930
931               XPutPixel (ximage, x, y, pixel);
932             }
933           y++;
934         }
935     }
936
937   if (cinfo.output_scanline < cinfo.output_height)
938     /* don't goto FAIL -- we might have viewable partial data. */
939     jpeg_abort_decompress (&cinfo);
940   else
941     jpeg_finish_decompress (&cinfo);
942
943   jpeg_destroy_decompress (&cinfo);
944   fclose (in);
945   in = 0;
946
947   return ximage;
948
949  FAIL:
950   if (in) fclose (in);
951   if (ximage && ximage->data)
952     {
953       free (ximage->data);
954       ximage->data = 0;
955     }
956   if (ximage) XDestroyImage (ximage);
957   if (scanbuf) free (scanbuf);
958   return 0;
959 }
960
961
962 /* Reads the given image file and renders it on the Drawable, using JPEG lib.
963    Returns False if it fails.
964  */
965 static Bool
966 read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
967                    const char *filename, Bool verbose_p,
968                    XRectangle *geom_ret)
969 {
970   Display *dpy = DisplayOfScreen (screen);
971   XImage *ximage;
972   Visual *visual;
973   int class, depth;
974   Colormap cmap;
975   unsigned int win_width, win_height, win_depth;
976   int srcx, srcy, destx, desty, w2, h2;
977
978   /* Find the size of the Drawable, and the Visual/Colormap of the Window. */
979   {
980     Window root;
981     int x, y;
982     unsigned int bw;
983     XWindowAttributes xgwa;
984     XGetWindowAttributes (dpy, window, &xgwa);
985     visual = xgwa.visual;
986     cmap = xgwa.colormap;
987     XGetGeometry (dpy, drawable,
988                   &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
989   }
990
991   /* Make sure we're not on some weirdo visual...
992    */
993   class = visual_class (screen, visual);
994   depth = visual_depth (screen, visual);
995   if ((class == PseudoColor || class == DirectColor) &&
996       (depth != 8 && depth != 12))
997     {
998       fprintf (stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
999                progname, depth);
1000       return False;
1001     }
1002
1003   /* Read the file...
1004    */
1005   ximage = read_jpeg_ximage (screen, visual, drawable, cmap,
1006                              filename, verbose_p);
1007   if (!ximage) return False;
1008
1009   /* Scale it, if necessary...
1010    */
1011   compute_image_scaling (ximage->width, ximage->height,
1012                          win_width, win_height, verbose_p,
1013                          &srcx, &srcy, &destx, &desty, &w2, &h2);
1014   if (ximage->width != w2 || ximage->height != h2)
1015     if (! scale_ximage (screen, visual, ximage, w2, h2))
1016       return False;
1017
1018   /* Allocate a colormap, if we need to...
1019    */
1020   if (class == PseudoColor || class == DirectColor)
1021     {
1022       allocate_cubic_colormap (screen, visual, cmap, verbose_p);
1023       remap_image (screen, cmap, ximage, verbose_p);
1024     }
1025
1026   /* Finally, put the resized image on the window.
1027    */
1028   {
1029     GC gc;
1030     XGCValues gcv;
1031
1032     /* If we're rendering onto the root window (and it's not the xscreensaver
1033        pseudo-root) then put the image in the window's background.  Otherwise,
1034        just paint the image onto the window.
1035      */
1036     if (window == drawable && root_window_p (screen, window))
1037       {
1038         Pixmap bg = XCreatePixmap (dpy, window,
1039                                    win_width, win_height, win_depth);
1040         gcv.foreground = BlackPixelOfScreen (screen);
1041         gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
1042         XFillRectangle (dpy, bg, gc, 0, 0, win_width, win_height);
1043         XPutImage (dpy, bg, gc, ximage,
1044                    srcx, srcy, destx, desty, ximage->width, ximage->height);
1045         XSetWindowBackgroundPixmap (dpy, window, bg);
1046         XClearWindow (dpy, window);
1047       }
1048     else
1049       {
1050         gc = XCreateGC (dpy, drawable, 0, &gcv);
1051         clear_drawable (screen, drawable);
1052         XPutImage (dpy, drawable, gc, ximage,
1053                    srcx, srcy, destx, desty, ximage->width, ximage->height);
1054       }
1055
1056     XFreeGC (dpy, gc);
1057   }
1058
1059   if (geom_ret)
1060     {
1061       geom_ret->x = destx;
1062       geom_ret->y = desty;
1063       geom_ret->width  = ximage->width;
1064       geom_ret->height = ximage->height;
1065     }
1066
1067   free (ximage->data);
1068   ximage->data = 0;
1069   XDestroyImage (ximage);
1070   XSync (dpy, False);
1071   return True;
1072 }
1073
1074 #endif /* HAVE_JPEGLIB */
1075
1076
1077 /* Reads the given image file and renders it on the Drawable.
1078    Returns False if it fails.
1079  */
1080 static Bool
1081 display_file (Screen *screen, Window window, Drawable drawable,
1082               const char *filename, Bool verbose_p,
1083               XRectangle *geom_ret)
1084 {
1085   if (verbose_p)
1086     fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
1087
1088 # if defined(HAVE_GDK_PIXBUF)
1089   if (read_file_gdk (screen, window, drawable, filename, verbose_p, geom_ret))
1090     return True;
1091 # elif defined(HAVE_JPEGLIB)
1092   if (read_file_jpeglib (screen, window, drawable, filename, verbose_p,
1093                          geom_ret))
1094     return True;
1095 # else  /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
1096   /* shouldn't get here if we have no image-loading methods available. */
1097   abort();
1098 # endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
1099
1100   return False;
1101 }
1102
1103
1104 /* Invokes a sub-process and returns its output (presumably, a file to
1105    load.)  Free the string when done.  'grab_type' controls which program
1106    to run.
1107  */
1108 static char *
1109 get_filename_1 (Screen *screen, const char *directory, grab_type type,
1110                 Bool verbose_p)
1111 {
1112   Display *dpy = DisplayOfScreen (screen);
1113   pid_t forked;
1114   int fds [2];
1115   int in, out;
1116   char buf[1024];
1117   char *av[20];
1118   int ac = 0;
1119
1120   switch (type)
1121     {
1122     case GRAB_FILE:
1123       av[ac++] = GETIMAGE_FILE_PROGRAM;
1124       if (verbose_p)
1125         av[ac++] = "--verbose";
1126       av[ac++] = "--name";
1127       av[ac++] = (char *) directory;
1128       break;
1129
1130     case GRAB_VIDEO:
1131       av[ac++] = GETIMAGE_VIDEO_PROGRAM;
1132       if (verbose_p)
1133         av[ac++] = "--verbose";
1134       av[ac++] = "--name";
1135       break;
1136
1137 # ifdef USE_EXTERNAL_SCREEN_GRABBER
1138     case GRAB_DESK:
1139       av[ac++] = GETIMAGE_SCREEN_PROGRAM;
1140       if (verbose_p)
1141         av[ac++] = "--verbose";
1142       av[ac++] = "--name";
1143       break;
1144 # endif
1145
1146     default:
1147       abort();
1148     }
1149   av[ac] = 0;
1150
1151   if (verbose_p)
1152     {
1153       int i;
1154       fprintf (stderr, "%s: executing:", progname);
1155       for (i = 0; i < ac; i++)
1156         fprintf (stderr, " %s", av[i]);
1157       fprintf (stderr, "\n");
1158     }
1159
1160   if (pipe (fds))
1161     {
1162       sprintf (buf, "%s: error creating pipe", progname);
1163       perror (buf);
1164       return 0;
1165     }
1166
1167   in = fds [0];
1168   out = fds [1];
1169
1170   switch ((int) (forked = fork ()))
1171     {
1172     case -1:
1173       {
1174         sprintf (buf, "%s: couldn't fork", progname);
1175         perror (buf);
1176         return 0;
1177       }
1178     case 0:
1179       {
1180         int stdout_fd = 1;
1181
1182         close (in);  /* don't need this one */
1183         close (ConnectionNumber (dpy));         /* close display fd */
1184
1185         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
1186           {
1187             sprintf (buf, "%s: could not dup() a new stdout", progname);
1188             exit (-1);                          /* exits fork */
1189           }
1190
1191         execvp (av[0], av);                     /* shouldn't return. */
1192         exit (-1);                              /* exits fork */
1193         break;
1194       }
1195     default:
1196       {
1197         struct stat st;
1198         int wait_status = 0;
1199         FILE *f = fdopen (in, "r");
1200         int L;
1201
1202         close (out);  /* don't need this one */
1203         *buf = 0;
1204         if (! fgets (buf, sizeof(buf)-1, f))
1205           *buf = 0;
1206         fclose (f);
1207
1208         /* Wait for the child to die. */
1209         waitpid (-1, &wait_status, 0);
1210
1211         L = strlen (buf);
1212         while (L && buf[L-1] == '\n')
1213           buf[--L] = 0;
1214           
1215         if (!*buf)
1216           return 0;
1217         if (stat(buf, &st))
1218           {
1219             fprintf (stderr, "%s: file does not exist: \"%s\"\n",
1220                      progname, buf);
1221             return 0;
1222           }
1223         else
1224           return strdup (buf);
1225       }
1226     }
1227
1228   abort();
1229 }
1230
1231
1232 /* Returns a pathname to an image file.  Free the string when you're done.
1233  */
1234 static char *
1235 get_filename (Screen *screen, const char *directory, Bool verbose_p)
1236 {
1237   return get_filename_1 (screen, directory, GRAB_FILE, verbose_p);
1238 }
1239
1240
1241 /* Grabs a video frame to a file, and returns a pathname to that file.
1242    Delete that file when you are done with it (and free the string.)
1243  */
1244 static char *
1245 get_video_filename (Screen *screen, Bool verbose_p)
1246 {
1247   return get_filename_1 (screen, 0, GRAB_VIDEO, verbose_p);
1248 }
1249
1250 /* Grabs a desktop image to a file, and returns a pathname to that file.
1251    Delete that file when you are done with it (and free the string.)
1252  */
1253 # ifdef USE_EXTERNAL_SCREEN_GRABBER
1254 static char *
1255 get_desktop_filename (Screen *screen, Bool verbose_p)
1256 {
1257   return get_filename_1 (screen, 0, GRAB_DESK, verbose_p);
1258 }
1259 #endif /* USE_EXTERNAL_SCREEN_GRABBER */
1260
1261
1262 /* Grabs a video frame, and renders it on the Drawable.
1263    Returns False if it fails;
1264  */
1265 static Bool
1266 display_video (Screen *screen, Window window, Drawable drawable,
1267                Bool verbose_p, XRectangle *geom_ret)
1268 {
1269   char *filename = get_video_filename (screen, verbose_p);
1270   Bool status;
1271
1272   if (!filename)
1273     {
1274       if (verbose_p)
1275         fprintf (stderr, "%s: video grab failed.\n", progname);
1276       return False;
1277     }
1278
1279   status = display_file (screen, window, drawable, filename, verbose_p,
1280                          geom_ret);
1281
1282   if (unlink (filename))
1283     {
1284       char buf[512];
1285       sprintf (buf, "%s: rm %.100s", progname, filename);
1286       perror (buf);
1287     }
1288   else if (verbose_p)
1289     fprintf (stderr, "%s: rm %s\n", progname, filename);
1290
1291   if (filename) free (filename);
1292   return status;
1293 }
1294
1295
1296 /* Grabs a desktop screen shot onto the window and the drawable.
1297    If the window and drawable are not the same size, the image in
1298    the drawable is scaled to fit.
1299    Returns False if it fails.
1300  */
1301 static Bool
1302 display_desktop (Screen *screen, Window window, Drawable drawable,
1303                  Bool verbose_p, XRectangle *geom_ret)
1304 {
1305 # ifdef USE_EXTERNAL_SCREEN_GRABBER
1306
1307   Display *dpy = DisplayOfScreen (screen);
1308   Bool top_p = top_level_window_p (screen, window);
1309   char *filename;
1310   Bool status;
1311
1312   if (top_p)
1313     {
1314       if (verbose_p)
1315         fprintf (stderr, "%s: unmapping 0x%lx.\n", progname,
1316                  (unsigned long) window);
1317       XUnmapWindow (dpy, window);
1318       XSync (dpy, False);
1319     }
1320
1321   filename = get_desktop_filename (screen, verbose_p);
1322
1323   if (top_p)
1324     {
1325       if (verbose_p)
1326         fprintf (stderr, "%s: mapping 0x%lx.\n", progname,
1327                  (unsigned long) window);
1328       XMapRaised (dpy, window);
1329       XSync (dpy, False);
1330     }
1331
1332   if (!filename)
1333     {
1334       if (verbose_p)
1335         fprintf (stderr, "%s: desktop grab failed.\n", progname);
1336       return False;
1337     }
1338
1339   status = display_file (screen, window, drawable, filename, verbose_p,
1340                          geom_ret);
1341
1342   if (unlink (filename))
1343     {
1344       char buf[512];
1345       sprintf (buf, "%s: rm %.100s", progname, filename);
1346       perror (buf);
1347     }
1348   else if (verbose_p)
1349     fprintf (stderr, "%s: rm %s\n", progname, filename);
1350
1351   if (filename) free (filename);
1352   return status;
1353
1354 # else /* !USE_EXTERNAL_SCREEN_GRABBER */
1355
1356   Display *dpy = DisplayOfScreen (screen);
1357   XGCValues gcv;
1358   XWindowAttributes xgwa;
1359   Window root;
1360   int px, py;
1361   unsigned int pw, ph, pbw, pd;
1362   int srcx, srcy, destx, desty, w2, h2;
1363
1364   if (verbose_p)
1365     {
1366       fprintf (stderr, "%s: grabbing desktop image\n", progname);
1367       grabscreen_verbose();
1368     }
1369
1370   XGetWindowAttributes (dpy, window, &xgwa);
1371   XGetGeometry (dpy, drawable, &root, &px, &py, &pw, &ph, &pbw, &pd);
1372
1373   grab_screen_image_internal (screen, window);
1374
1375   compute_image_scaling (xgwa.width, xgwa.height,
1376                          pw, ph, verbose_p,
1377                          &srcx, &srcy, &destx, &desty, &w2, &h2);
1378
1379   if (pw == w2 && ph == h2)  /* it fits -- just copy server-side pixmaps */
1380     {
1381       GC gc = XCreateGC (dpy, drawable, 0, &gcv);
1382       XCopyArea (dpy, window, drawable, gc,
1383                  0, 0, xgwa.width, xgwa.height, 0, 0);
1384       XFreeGC (dpy, gc);
1385     }
1386   else  /* size mismatch -- must scale client-side images to fit drawable */
1387     {
1388       GC gc;
1389       XImage *ximage = 0;
1390       XErrorHandler old_handler;
1391
1392       XSync (dpy, False);
1393       old_handler = XSetErrorHandler (ignore_badmatch_ehandler);
1394       error_handler_hit_p = False;
1395
1396       /* This can return BadMatch if the window is not fully on screen.
1397          Trap that error and return color bars in that case.
1398          (Note that this only happens with XGetImage, not with XCopyArea:
1399          yet another totally gratuitous inconsistency in X, thanks.)
1400        */
1401       ximage = XGetImage (dpy, window, 0, 0, xgwa.width, xgwa.height,
1402                           ~0L, ZPixmap);
1403
1404       XSync (dpy, False);
1405       XSetErrorHandler (old_handler);
1406       XSync (dpy, False);
1407
1408       if (error_handler_hit_p)
1409         {
1410           ximage = 0;
1411           if (verbose_p)
1412             fprintf (stderr, "%s: BadMatch reading window 0x%x contents!\n",
1413                      progname, (unsigned int) window);
1414         }
1415
1416       if (!ximage ||
1417           !scale_ximage (xgwa.screen, xgwa.visual, ximage, w2, h2))
1418         return False;
1419
1420       gc = XCreateGC (dpy, drawable, 0, &gcv);
1421       clear_drawable (screen, drawable);
1422       XPutImage (dpy, drawable, gc, ximage, 
1423                  srcx, srcy, destx, desty, ximage->width, ximage->height);
1424       XDestroyImage (ximage);
1425       XFreeGC (dpy, gc);
1426     }
1427
1428   if (geom_ret)
1429     {
1430       geom_ret->x = destx;
1431       geom_ret->y = desty;
1432       geom_ret->width  = w2;
1433       geom_ret->height = h2;
1434     }
1435
1436   XSync (dpy, False);
1437   return True;
1438
1439 # endif /* !USE_EXTERNAL_SCREEN_GRABBER */
1440 }
1441
1442
1443 /* Grabs an image (from a file, video, or the desktop) and renders it on
1444    the Drawable.  If `file' is specified, always use that file.  Otherwise,
1445    select randomly, based on the other arguments.
1446  */
1447 static void
1448 get_image (Screen *screen,
1449            Window window, Drawable drawable,
1450            Bool verbose_p,
1451            Bool desk_p,
1452            Bool video_p,
1453            Bool image_p,
1454            const char *dir,
1455            const char *file)
1456 {
1457   Display *dpy = DisplayOfScreen (screen);
1458   grab_type which = GRAB_BARS;
1459   struct stat st;
1460   const char *file_prop = 0;
1461   XRectangle geom = { 0, 0, 0, 0 };
1462
1463   if (! drawable_window_p (dpy, window))
1464     {
1465       fprintf (stderr, "%s: 0x%lx is a pixmap, not a window!\n",
1466                progname, (unsigned long) window);
1467       exit (1);
1468     }
1469
1470   /* Make sure the Screen and the Window correspond. */
1471   {
1472     XWindowAttributes xgwa;
1473     XGetWindowAttributes (dpy, window, &xgwa);
1474     screen = xgwa.screen;
1475   }
1476
1477   if (file && stat (file, &st))
1478     {
1479       fprintf (stderr, "%s: file \"%s\" does not exist\n", progname, file);
1480       file = 0;
1481     }
1482
1483   if (verbose_p)
1484     {
1485       fprintf (stderr, "%s: grabDesktopImages:  %s\n",
1486                progname, desk_p ? "True" : "False");
1487       fprintf (stderr, "%s: grabVideoFrames:    %s\n",
1488                progname, video_p ? "True" : "False");
1489       fprintf (stderr, "%s: chooseRandomImages: %s\n",
1490                progname, image_p ? "True" : "False");
1491       fprintf (stderr, "%s: imageDirectory:     %s\n",
1492                progname, (file ? file : dir ? dir : ""));
1493     }
1494
1495 # if !(defined(HAVE_GDK_PIXBUF) || defined(HAVE_JPEGLIB))
1496   image_p = False;    /* can't load images from files... */
1497 #  ifdef USE_EXTERNAL_SCREEN_GRABBER
1498   desk_p = False;     /* ...or from desktops grabbed to files. */
1499 #  endif
1500
1501   if (file)
1502     {
1503       fprintf (stderr,
1504                "%s: image file loading not available at compile-time\n",
1505                progname);
1506       fprintf (stderr, "%s: can't load \"%s\"\n", progname, file);
1507       file = 0;
1508     }
1509 # endif /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
1510
1511   if (file)
1512     {
1513       desk_p = False;
1514       video_p = False;
1515       image_p = True;
1516     }
1517   else if (!dir || !*dir)
1518     {
1519       if (verbose_p && image_p)
1520         fprintf (stderr,
1521                  "%s: no imageDirectory: turning off chooseRandomImages.\n",
1522                  progname);
1523       image_p = False;
1524     }
1525
1526
1527 # ifndef _VROOT_H_
1528 #  error Error!  This file definitely needs vroot.h!
1529 # endif
1530
1531   /* We can grab desktop images (using the normal X11 method) if:
1532        - the window is the real root window;
1533        - the window is a toplevel window.
1534      We cannot grab desktop images that way if:
1535        - the window is a non-top-level window.
1536
1537      Using the MacOS X way, desktops are just like loaded image files.
1538    */
1539 # ifndef USE_EXTERNAL_SCREEN_GRABBER
1540   if (desk_p)
1541     {
1542       if (!top_level_window_p (screen, window))
1543         {
1544           desk_p = False;
1545           if (verbose_p)
1546             fprintf (stderr,
1547                     "%s: 0x%x not top-level: turning off grabDesktopImages.\n",
1548                      progname, (unsigned int) window);
1549         }
1550     }
1551 # endif /* !USE_EXTERNAL_SCREEN_GRABBER */
1552
1553   if (! (desk_p || video_p || image_p))
1554     which = GRAB_BARS;
1555   else
1556     {
1557       int i = 0;
1558       int n;
1559       /* Loop until we get one that's permitted.
1560          If files or video are permitted, do them more often
1561          than desktop.
1562
1563              D+V+I: 10% + 45% + 45%.
1564              V+I:   50% + 50%
1565              D+V:   18% + 82%
1566              D+I:   18% + 82%
1567        */
1568     AGAIN:
1569       n = (random() % 100);
1570       if (++i > 300) abort();
1571       else if (desk_p  && n < 10) which = GRAB_DESK;   /* 10% */
1572       else if (video_p && n < 55) which = GRAB_VIDEO;  /* 45% */
1573       else if (image_p)           which = GRAB_FILE;   /* 45% */
1574       else goto AGAIN;
1575     }
1576
1577
1578   /* If we're to search a directory to find an image file, do so now.
1579    */
1580   if (which == GRAB_FILE && !file)
1581     {
1582       file = get_filename (screen, dir, verbose_p);
1583       if (!file)
1584         {
1585           which = GRAB_BARS;
1586           if (verbose_p)
1587             fprintf (stderr, "%s: no image files found.\n", progname);
1588         }
1589     }
1590
1591   /* Now actually render something.
1592    */
1593   switch (which)
1594     {
1595     case GRAB_BARS:
1596       {
1597         XWindowAttributes xgwa;
1598       COLORBARS:
1599         if (verbose_p)
1600           fprintf (stderr, "%s: drawing colorbars.\n", progname);
1601         XGetWindowAttributes (dpy, window, &xgwa);
1602         draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
1603                         0, 0, 0, 0);
1604         XSync (dpy, False);
1605       }
1606       break;
1607
1608     case GRAB_DESK:
1609       if (! display_desktop (screen, window, drawable, verbose_p, &geom))
1610         goto COLORBARS;
1611       file_prop = "desktop";
1612       break;
1613
1614     case GRAB_FILE:
1615       if (! display_file (screen, window, drawable, file, verbose_p, &geom))
1616         goto COLORBARS;
1617       file_prop = file;
1618       break;
1619
1620     case GRAB_VIDEO:
1621       if (! display_video (screen, window, drawable, verbose_p, &geom))
1622         goto COLORBARS;
1623       file_prop = "video";
1624       break;
1625
1626     default:
1627       abort();
1628       break;
1629     }
1630
1631   {
1632     Atom a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
1633     if (file_prop && *file_prop)
1634       XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, 
1635                        (unsigned char *) file_prop, strlen(file_prop));
1636     else
1637       XDeleteProperty (dpy, window, a);
1638
1639     a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
1640     if (geom.width > 0)
1641       {
1642         char gstr[30];
1643         sprintf (gstr, "%dx%d+%d+%d", geom.width, geom.height, geom.x, geom.y);
1644         XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, 
1645                          (unsigned char *) gstr, strlen (gstr));
1646       }
1647     else
1648       XDeleteProperty (dpy, window, a);
1649   }
1650
1651   XSync (dpy, False);
1652 }
1653
1654
1655 #ifdef DEBUG
1656 static Bool
1657 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1658         XrmRepresentation *type, XrmValue *value, XPointer closure)
1659 {
1660   int i;
1661   for (i = 0; quarks[i]; i++)
1662     {
1663       if (bindings[i] == XrmBindTightly)
1664         fprintf (stderr, (i == 0 ? "" : "."));
1665       else if (bindings[i] == XrmBindLoosely)
1666         fprintf (stderr, "*");
1667       else
1668         fprintf (stderr, " ??? ");
1669       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1670     }
1671
1672   fprintf (stderr, ": %s\n", (char *) value->addr);
1673
1674   return False;
1675 }
1676 #endif /* DEBUG */
1677
1678
1679 #define USAGE "usage: %s [ -options... ] window-id [pixmap-id]\n"             \
1680    "\n"                                                                       \
1681    "    %s\n"                                                                 \
1682    "\n"                                                                       \
1683    "    %s puts an image on the given window or pixmap.\n"                    \
1684    "\n"                                                                       \
1685    "    It is used by those xscreensaver demos that operate on images.\n"     \
1686    "    The image may be a file loaded from disk, a frame grabbed from\n"     \
1687    "    the system's video camera, or a screenshot of the desktop,\n"         \
1688    "    depending on command-line options or the ~/.xscreensaver file.\n"     \
1689    "\n"                                                                       \
1690    "    Options include:\n"                                                   \
1691    "\n"                                                                       \
1692    "      -display host:dpy.screen    which display to use\n"                 \
1693    "      -root                       draw to the root window\n"              \
1694    "      -verbose                    print diagnostics\n"                    \
1695    "      -images  / -no-images       whether to allow image file loading\n"  \
1696    "      -video   / -no-video        whether to allow video grabs\n"         \
1697    "      -desktop / -no-desktop      whether to allow desktop screen grabs\n"\
1698    "      -directory <path>           where to find image files to load\n"    \
1699    "      -file <filename>            load this image file\n"                 \
1700    "\n"                                                                       \
1701    "    The XScreenSaver Control Panel (xscreensaver-demo) lets you set the\n"\
1702    "    defaults for these options in your ~/.xscreensaver file.\n"           \
1703    "\n"
1704
1705 int
1706 main (int argc, char **argv)
1707 {
1708   saver_preferences P;
1709   Widget toplevel;
1710   Display *dpy;
1711   Screen *screen;
1712   char *oprogname = progname;
1713   char *file = 0;
1714   char version[255];
1715
1716   Window window = (Window) 0;
1717   Drawable drawable = (Drawable) 0;
1718   const char *window_str = 0;
1719   const char *drawable_str = 0;
1720   char *s;
1721   int i;
1722
1723   progname = argv[0];
1724   s = strrchr (progname, '/');
1725   if (s) progname = s+1;
1726   oprogname = progname;
1727
1728   /* half-assed way of avoiding buffer-overrun attacks. */
1729   if (strlen (progname) >= 100) progname[100] = 0;
1730
1731 # ifndef _VROOT_H_
1732 #  error Error!  This file definitely needs vroot.h!
1733 # endif
1734
1735   /* Get the version number, for error messages. */
1736   {
1737     char *v = (char *) strdup(strchr(screensaver_id, ' '));
1738     char *s1, *s2, *s3, *s4;
1739     s1 = (char *) strchr(v,  ' '); s1++;
1740     s2 = (char *) strchr(s1, ' ');
1741     s3 = (char *) strchr(v,  '('); s3++;
1742     s4 = (char *) strchr(s3, ')');
1743     *s2 = 0;
1744     *s4 = 0;
1745     sprintf (version, "Part of XScreenSaver %s -- %s.", s1, s3);
1746     free(v);
1747   }
1748
1749   /* We must read exactly the same resources as xscreensaver.
1750      That means we must have both the same progclass *and* progname,
1751      at least as far as the resource database is concerned.  So,
1752      put "xscreensaver" in argv[0] while initializing Xt.
1753    */
1754   progname = argv[0] = "xscreensaver";
1755
1756   /* allow one dash or two. */
1757   for (i = 1; i < argc; i++)
1758     if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
1759
1760   toplevel = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
1761                               defaults, 0, 0);
1762   dpy = XtDisplay (toplevel);
1763   screen = XtScreen (toplevel);
1764   db = XtDatabase (dpy);
1765   XtGetApplicationNameAndClass (dpy, &s, &progclass);
1766   XSetErrorHandler (x_ehandler);
1767   XSync (dpy, False);
1768
1769   /* Randomize -- only need to do this here because this program
1770      doesn't use the `screenhack.h' or `lockmore.h' APIs. */
1771 # undef ya_rand_init
1772   ya_rand_init (0);
1773
1774   memset (&P, 0, sizeof(P));
1775   P.db = db;
1776   load_init_file (&P);
1777
1778   progname = argv[0] = oprogname;
1779
1780   for (i = 1; i < argc; i++)
1781     {
1782       unsigned long w;
1783       char dummy;
1784
1785       /* Have to re-process these, or else the .xscreensaver file
1786          has priority over the command line...
1787        */
1788       if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
1789         P.verbose_p = True;
1790       else if (!strcmp (argv[i], "-desktop"))    P.grab_desktop_p = True;
1791       else if (!strcmp (argv[i], "-no-desktop")) P.grab_desktop_p = False;
1792       else if (!strcmp (argv[i], "-video"))      P.grab_video_p = True;
1793       else if (!strcmp (argv[i], "-no-video"))   P.grab_video_p = False;
1794       else if (!strcmp (argv[i], "-images"))     P.random_image_p = True;
1795       else if (!strcmp (argv[i], "-no-images"))  P.random_image_p = False;
1796       else if (!strcmp (argv[i], "-file"))       file = argv[++i];
1797       else if (!strcmp (argv[i], "-directory") || !strcmp (argv[i], "-dir"))
1798         P.image_directory = argv[++i];
1799       else if (!strcmp (argv[i], "-root") || !strcmp (argv[i], "root"))
1800         {
1801           if (window)
1802             {
1803               fprintf (stderr, "%s: both %s and %s specified?\n",
1804                        progname, argv[i], window_str);
1805               goto LOSE;
1806             }
1807           window_str = argv[i];
1808           window = VirtualRootWindowOfScreen (screen);
1809         }
1810       else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
1811                 1 == sscanf (argv[i], " %lu %c",   &w, &dummy)) &&
1812                w != 0)
1813         {
1814           if (drawable)
1815             {
1816               fprintf (stderr, "%s: both %s and %s specified?\n",
1817                        progname, drawable_str, argv[i]);
1818               goto LOSE;
1819             }
1820           else if (window)
1821             {
1822               drawable_str = argv[i];
1823               drawable = (Drawable) w;
1824             }
1825           else
1826             {
1827               window_str = argv[i];
1828               window = (Window) w;
1829             }
1830         }
1831       else
1832         {
1833           if (argv[i][0] == '-')
1834             fprintf (stderr, "\n%s: unknown option \"%s\"\n",
1835                      progname, argv[i]);
1836           else
1837             fprintf (stderr, "\n%s: unparsable window/pixmap ID: \"%s\"\n",
1838                      progname, argv[i]);
1839         LOSE:
1840 # ifdef __GNUC__
1841           __extension__   /* don't warn about "string length is greater than
1842                              the length ISO C89 compilers are required to
1843                              support" in the usage string... */
1844 # endif
1845           fprintf (stderr, USAGE, progname, version, progname);
1846           exit (1);
1847         }
1848     }
1849
1850   if (window == 0)
1851     {
1852       fprintf (stderr, "\n%s: no window ID specified!\n", progname);
1853       goto LOSE;
1854     }
1855
1856
1857 #ifdef DEBUG
1858   if (P.verbose_p)       /* Print out all the resources we can see. */
1859     {
1860       XrmName name = { 0 };
1861       XrmClass class = { 0 };
1862       int count = 0;
1863       XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
1864                             (XtPointer) &count);
1865     }
1866 #endif /* DEBUG */
1867
1868   if (!window) abort();
1869   if (!drawable) drawable = window;
1870
1871   get_image (screen, window, drawable, P.verbose_p,
1872              P.grab_desktop_p, P.grab_video_p, P.random_image_p,
1873              P.image_directory, file);
1874   exit (0);
1875 }