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