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