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