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