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