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