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