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