ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / driver / xscreensaver-getimage.c
index 47247686b428a9508bcc1e3573153b1d22a492c5..68637a7be4e2fb8662d035a293247ea48ef11993 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2001, 2002, 2003 by Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2001-2006 by Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 #include "version.h"
 #include "vroot.h"
 
+#ifndef _XSCREENSAVER_VROOT_H_
+# error Error!  You have an old version of vroot.h!  Check -I args.
+#endif /* _XSCREENSAVER_VROOT_H_ */
+
 #ifdef HAVE_GDK_PIXBUF
 # undef HAVE_JPEGLIB
 # ifdef HAVE_GTK2
 #endif
 
 
+#ifdef __APPLE__
+  /* On MacOSX / XDarwin, the usual X11 mechanism of getting a screen shot
+     doesn't work, and we need to use an external program. */
+# define USE_EXTERNAL_SCREEN_GRABBER
+#endif
+
+
+#ifdef __GNUC__
+ __extension__     /* shut up about "string length is greater than the length
+                      ISO C89 compilers are required to support" when including
+                      the .ad file... */
+#endif
+
 static char *defaults[] = {
 #include "../driver/XScreenSaver_ad.h"
  0
@@ -75,9 +92,16 @@ XtAppContext app;
 
 extern void grabscreen_verbose (void);
 
+typedef enum {
+  GRAB_DESK, GRAB_VIDEO, GRAB_FILE, GRAB_BARS
+} grab_type;
 
-#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video"
-#define GETIMAGE_FILE_PROGRAM  "xscreensaver-getimage-file"
+
+#define GETIMAGE_VIDEO_PROGRAM   "xscreensaver-getimage-video"
+#define GETIMAGE_FILE_PROGRAM    "xscreensaver-getimage-file"
+#define GETIMAGE_SCREEN_PROGRAM  "xscreensaver-getimage-desktop"
+
+extern const char *blurb (void);
 
 const char *
 blurb (void)
@@ -89,8 +113,17 @@ blurb (void)
 static int
 x_ehandler (Display *dpy, XErrorEvent *error)
 {
-  fprintf (stderr, "\nX error in %s:\n", progname);
-  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  if (error->error_code == BadWindow || error->error_code == BadDrawable)
+    {
+      fprintf (stderr, "%s: target %s 0x%lx unexpectedly deleted\n", progname,
+               (error->error_code == BadWindow ? "window" : "pixmap"),
+               (unsigned long) error->resourceid);
+    }
+  else
+    {
+      fprintf (stderr, "\nX error in %s:\n", progname);
+      XmuPrintDefaultErrorMessage (dpy, error, stderr);
+    }
   exit (-1);
   return 0;
 }
@@ -105,6 +138,17 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
   return 0;
 }
 
+#ifndef USE_EXTERNAL_SCREEN_GRABBER
+static int
+ignore_badmatch_ehandler (Display *dpy, XErrorEvent *error)
+{
+  if (error->error_code == BadMatch)
+    return ignore_all_errors_ehandler (dpy, error);
+  else
+    return x_ehandler (dpy, error);
+}
+#endif /* ! USE_EXTERNAL_SCREEN_GRABBER */
+
 
 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
  */
@@ -129,6 +173,35 @@ drawable_window_p (Display *dpy, Drawable d)
 }
 
 
+/* Returns true if the window is the root window, or a virtual root window,
+   but *not* the xscreensaver window.  That is, if it's a "real" desktop
+   root window of some kind.
+ */
+static Bool
+root_window_p (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Atom type;
+  int format;
+  unsigned long nitems, bytesafter;
+  unsigned char *version;
+
+  if (window != RootWindowOfScreen (screen))
+    return False;
+
+  if (XGetWindowProperty (dpy, window,
+                         XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
+                         0, 1, False, XA_STRING,
+                         &type, &format, &nitems, &bytesafter,
+                         &version)
+      == Success
+      && type != None)
+    return False;
+
+  return True;
+}
+
+
 /* Clear the window or pixmap to black, or its background color.
  */
 static void
@@ -184,7 +257,10 @@ compute_image_scaling (int src_w, int src_h,
       int th = src_h * r;
       int pct = (r * 100);
 
+#if 0
+      /* this optimization breaks things */
       if (pct < 95 || pct > 105)  /* don't scale if it's close */
+#endif
         {
           if (verbose_p)
             fprintf (stderr, "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
@@ -213,9 +289,65 @@ compute_image_scaling (int src_w, int src_h,
   *scaled_to_y_ret = desty;
 
   if (verbose_p)
-    fprintf (stderr, "%s: displaying %dx%d image at %d,%d.\n",
-             progname, src_w, src_h, destx, desty);
+    fprintf (stderr, "%s: displaying %dx%d image at %d,%d in %dx%d.\n",
+             progname, src_w, src_h, destx, desty, dest_w, dest_h);
+}
+
+
+/* Scales an XImage, modifying it in place.
+   This doesn't do dithering or smoothing, so it might have artifacts.
+   If out of memory, returns False, and the XImage will have been
+   destroyed and freed.
+ */
+#if !defined(USE_EXTERNAL_SCREEN_GRABBER) || defined(HAVE_JPEGLIB)
+static Bool
+scale_ximage (Screen *screen, Visual *visual,
+              XImage *ximage, int new_width, int new_height)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int depth = visual_depth (screen, visual);
+  int x, y;
+  double xscale, yscale;
+
+  XImage *ximage2 = XCreateImage (dpy, visual, depth,
+                                  ZPixmap, 0, 0,
+                                  new_width, new_height, 8, 0);
+  ximage2->data = (char *) calloc (ximage2->height, ximage2->bytes_per_line);
+
+  if (!ximage2->data)
+    {
+      fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n",
+               progname,
+               ximage->width, ximage->height,
+               ximage2->width, ximage2->height);
+      if (ximage->data) free (ximage->data);
+      if (ximage2->data) free (ximage2->data);
+      ximage->data = 0;
+      ximage2->data = 0;
+      XDestroyImage (ximage);
+      XDestroyImage (ximage2);
+      return False;
+    }
+
+  /* Brute force scaling... */
+  xscale = (double) ximage->width  / ximage2->width;
+  yscale = (double) ximage->height / ximage2->height;
+  for (y = 0; y < ximage2->height; y++)
+    for (x = 0; x < ximage2->width; x++)
+      XPutPixel (ximage2, x, y,
+                 XGetPixel (ximage, x * xscale, y * yscale));
+
+  free (ximage->data);
+  ximage->data = 0;
+
+  (*ximage) = (*ximage2);
+
+  ximage2->data = 0;
+  XDestroyImage (ximage2);
+
+  return True;
 }
+#endif /* !USE_EXTERNAL_SCREEN_GRABBER || HAVE_JPEGLIB */
 
 
 #ifdef HAVE_GDK_PIXBUF
@@ -225,24 +357,23 @@ compute_image_scaling (int src_w, int src_h,
  */
 static Bool
 read_file_gdk (Screen *screen, Window window, Drawable drawable,
-               const char *filename, Bool verbose_p)
+               const char *filename, Bool verbose_p,
+               XRectangle *geom_ret)
 {
   GdkPixbuf *pb;
   Display *dpy = DisplayOfScreen (screen);
-  unsigned int win_width, win_height;
+  unsigned int win_width, win_height, win_depth;
 # ifdef HAVE_GTK2
   GError *gerr = 0;
 # endif /* HAVE_GTK2 */
 
+  /* Find the size of the Drawable. */
   {
     Window root;
     int x, y;
-    unsigned int bw, d;
-    XWindowAttributes xgwa;
-    XGetWindowAttributes (dpy, window, &xgwa);
-    screen = xgwa.screen;
+    unsigned int bw;
     XGetGeometry (dpy, drawable,
-                  &root, &x, &y, &win_width, &win_height, &bw, &d);
+                  &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
   }
 
   gdk_pixbuf_xlib_init (dpy, screen_number (screen));
@@ -272,6 +403,7 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable,
       int w = gdk_pixbuf_get_width (pb);
       int h = gdk_pixbuf_get_height (pb);
       int srcx, srcy, destx, desty, w2, h2;
+      Bool bg_p = False;
 
       compute_image_scaling (w, h, win_width, win_height, verbose_p,
                              &srcx, &srcy, &destx, &desty, &w2, &h2);
@@ -290,7 +422,25 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable,
             fprintf (stderr, "%s: out of memory when scaling?\n", progname);
         }
 
-      clear_drawable (screen, drawable);
+      /* If we're rendering onto the root window (and it's not the
+         xscreensaver pseudo-root) then put the image in the window's
+         background.  Otherwise, just paint the image onto the window.
+       */
+      bg_p = (window == drawable && root_window_p (screen, window));
+
+      if (bg_p)
+        {
+          XGCValues gcv;
+          GC gc;
+          drawable = XCreatePixmap (dpy, window,
+                                    win_width, win_height, win_depth);
+          gcv.foreground = BlackPixelOfScreen (screen);
+          gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+          XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
+          XFreeGC (dpy, gc);
+        }
+      else
+        clear_drawable (screen, drawable);
 
       /* #### Note that this always uses the default colormap!  Morons!
          Owen says that in Gnome 2.0, I should try using
@@ -303,9 +453,22 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable,
                                                 GDK_PIXBUF_ALPHA_FULL, 127,
                                                 XLIB_RGB_DITHER_NORMAL,
                                                 0, 0);
-      XSync (dpy, False);
+      if (bg_p)
+        {
+          XSetWindowBackgroundPixmap (dpy, window, drawable);
+          XClearWindow (dpy, window);
+        }
+
+      if (geom_ret)
+        {
+          geom_ret->x = destx;
+          geom_ret->y = desty;
+          geom_ret->width  = w;
+          geom_ret->height = h;
+        }
     }
 
+  XSync (dpy, False);
   return True;
 }
 
@@ -575,8 +738,7 @@ maybe_read_ppm (Screen *screen, Visual *visual,
   ximage = XCreateImage (dpy, visual, depth, ZPixmap, 0, 0,
                          w, h, 8, 0);
   if (ximage)
-    ximage->data = (unsigned char *)
-      calloc (ximage->height, ximage->bytes_per_line);
+    ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
   if (!ximage || !ximage->data)
     {
       fprintf (stderr, "%s: out of memory loading %dx%d PPM file %s\n",
@@ -722,8 +884,7 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
                          cinfo.output_width, cinfo.output_height,
                          8, 0);
   if (ximage)
-    ximage->data = (unsigned char *)
-      calloc (ximage->height, ximage->bytes_per_line);
+    ximage->data = (char *) calloc (ximage->height, ximage->bytes_per_line);
 
   if (ximage && ximage->data)
     scanbuf = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE,
@@ -748,7 +909,7 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
           int x;
           for (x = 0; x < ximage->width; x++)
             {
-              int j = x * cinfo.num_components;
+              int j = x * cinfo.output_components;
               unsigned char r = scanbuf[i][j];
               unsigned char g = scanbuf[i][j+1];
               unsigned char b = scanbuf[i][j+2];
@@ -760,10 +921,12 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
                 pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
               else if (depth == 12)
                 pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
-              else if (depth == 16 || depth == 15)
+              else if (depth == 15)
                 /* Gah! I don't understand why these are in the other
                    order. */
                 pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
+              else if (depth == 16)
+                pixel = (((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3)));
               else
                 abort();
 
@@ -798,66 +961,13 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable,
 }
 
 
-/* Scales an XImage, modifying it in place.
-   If out of memory, returns False, and the XImage will have been
-   destroyed and freed.
- */
-static Bool
-scale_ximage (Screen *screen, Visual *visual,
-              XImage *ximage, int new_width, int new_height)
-{
-  Display *dpy = DisplayOfScreen (screen);
-  int depth = visual_depth (screen, visual);
-  int x, y;
-  double xscale, yscale;
-
-  XImage *ximage2 = XCreateImage (dpy, visual, depth,
-                                  ZPixmap, 0, 0,
-                                  new_width, new_height, 8, 0);
-  ximage2->data = (unsigned char *)
-    calloc (ximage2->height, ximage2->bytes_per_line);
-
-  if (!ximage2->data)
-    {
-      fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n",
-               progname,
-               ximage->width, ximage->height,
-               ximage2->width, ximage2->height);
-      if (ximage->data) free (ximage->data);
-      if (ximage2->data) free (ximage2->data);
-      ximage->data = 0;
-      ximage2->data = 0;
-      XDestroyImage (ximage);
-      XDestroyImage (ximage2);
-      return False;
-    }
-
-  /* Brute force scaling... */
-  xscale = (double) ximage->width  / ximage2->width;
-  yscale = (double) ximage->height / ximage2->height;
-  for (y = 0; y < ximage2->height; y++)
-    for (x = 0; x < ximage2->width; x++)
-      XPutPixel (ximage2, x, y,
-                 XGetPixel (ximage, x * xscale, y * yscale));
-
-  free (ximage->data);
-  ximage->data = 0;
-
-  (*ximage) = (*ximage2);
-
-  ximage2->data = 0;
-  XDestroyImage (ximage2);
-
-  return True;
-}
-
-
 /* Reads the given image file and renders it on the Drawable, using JPEG lib.
    Returns False if it fails.
  */
 static Bool
 read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
-                   const char *filename, Bool verbose_p)
+                   const char *filename, Bool verbose_p,
+                   XRectangle *geom_ret)
 {
   Display *dpy = DisplayOfScreen (screen);
   XImage *ximage;
@@ -867,17 +977,15 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
   unsigned int win_width, win_height, win_depth;
   int srcx, srcy, destx, desty, w2, h2;
 
+  /* Find the size of the Drawable, and the Visual/Colormap of the Window. */
   {
     Window root;
     int x, y;
     unsigned int bw;
     XWindowAttributes xgwa;
-
     XGetWindowAttributes (dpy, window, &xgwa);
-    screen = xgwa.screen;
     visual = xgwa.visual;
     cmap = xgwa.colormap;
-
     XGetGeometry (dpy, drawable,
                   &root, &x, &y, &win_width, &win_height, &bw, &win_depth);
   }
@@ -919,16 +1027,45 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
 
   /* Finally, put the resized image on the window.
    */
-  clear_drawable (screen, drawable);
   {
     GC gc;
     XGCValues gcv;
-    gc = XCreateGC (dpy, drawable, 0, &gcv);
-    XPutImage (dpy, drawable, gc, ximage,
-               srcx, srcy, destx, desty, ximage->width, ximage->height);
+
+    /* If we're rendering onto the root window (and it's not the xscreensaver
+       pseudo-root) then put the image in the window's background.  Otherwise,
+       just paint the image onto the window.
+     */
+    if (window == drawable && root_window_p (screen, window))
+      {
+        Pixmap bg = XCreatePixmap (dpy, window,
+                                   win_width, win_height, win_depth);
+        gcv.foreground = BlackPixelOfScreen (screen);
+        gc = XCreateGC (dpy, drawable, GCForeground, &gcv);
+        XFillRectangle (dpy, bg, gc, 0, 0, win_width, win_height);
+        XPutImage (dpy, bg, gc, ximage,
+                   srcx, srcy, destx, desty, ximage->width, ximage->height);
+        XSetWindowBackgroundPixmap (dpy, window, bg);
+        XClearWindow (dpy, window);
+      }
+    else
+      {
+        gc = XCreateGC (dpy, drawable, 0, &gcv);
+        clear_drawable (screen, drawable);
+        XPutImage (dpy, drawable, gc, ximage,
+                   srcx, srcy, destx, desty, ximage->width, ximage->height);
+      }
+
     XFreeGC (dpy, gc);
   }
 
+  if (geom_ret)
+    {
+      geom_ret->x = destx;
+      geom_ret->y = desty;
+      geom_ret->width  = ximage->width;
+      geom_ret->height = ximage->height;
+    }
+
   free (ximage->data);
   ximage->data = 0;
   XDestroyImage (ximage);
@@ -944,16 +1081,18 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable,
  */
 static Bool
 display_file (Screen *screen, Window window, Drawable drawable,
-              const char *filename, Bool verbose_p)
+              const char *filename, Bool verbose_p,
+              XRectangle *geom_ret)
 {
   if (verbose_p)
     fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
 
 # if defined(HAVE_GDK_PIXBUF)
-  if (read_file_gdk (screen, window, drawable, filename, verbose_p))
+  if (read_file_gdk (screen, window, drawable, filename, verbose_p, geom_ret))
     return True;
 # elif defined(HAVE_JPEGLIB)
-  if (read_file_jpeglib (screen, window, drawable, filename, verbose_p))
+  if (read_file_jpeglib (screen, window, drawable, filename, verbose_p,
+                         geom_ret))
     return True;
 # else  /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */
   /* shouldn't get here if we have no image-loading methods available. */
@@ -965,11 +1104,11 @@ display_file (Screen *screen, Window window, Drawable drawable,
 
 
 /* Invokes a sub-process and returns its output (presumably, a file to
-   load.)  Free the string when done.  video_p controls which program
+   load.)  Free the string when done.  'grab_type' controls which program
    to run.
  */
 static char *
-get_filename_1 (Screen *screen, const char *directory, Bool video_p,
+get_filename_1 (Screen *screen, const char *directory, grab_type type,
                 Bool verbose_p)
 {
   Display *dpy = DisplayOfScreen (screen);
@@ -980,20 +1119,34 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p,
   char *av[20];
   int ac = 0;
 
-  if (!video_p)
+  switch (type)
     {
+    case GRAB_FILE:
       av[ac++] = GETIMAGE_FILE_PROGRAM;
       if (verbose_p)
         av[ac++] = "--verbose";
       av[ac++] = "--name";
       av[ac++] = (char *) directory;
-    }
-  else
-    {
+      break;
+
+    case GRAB_VIDEO:
       av[ac++] = GETIMAGE_VIDEO_PROGRAM;
       if (verbose_p)
         av[ac++] = "--verbose";
       av[ac++] = "--name";
+      break;
+
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+    case GRAB_DESK:
+      av[ac++] = GETIMAGE_SCREEN_PROGRAM;
+      if (verbose_p)
+        av[ac++] = "--verbose";
+      av[ac++] = "--name";
+      break;
+# endif
+
+    default:
+      abort();
     }
   av[ac] = 0;
 
@@ -1050,7 +1203,8 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p,
 
         close (out);  /* don't need this one */
         *buf = 0;
-        fgets (buf, sizeof(buf)-1, f);
+        if (! fgets (buf, sizeof(buf)-1, f))
+          *buf = 0;
         fclose (f);
 
         /* Wait for the child to die. */
@@ -1082,7 +1236,7 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p,
 static char *
 get_filename (Screen *screen, const char *directory, Bool verbose_p)
 {
-  return get_filename_1 (screen, directory, False, verbose_p);
+  return get_filename_1 (screen, directory, GRAB_FILE, verbose_p);
 }
 
 
@@ -1092,16 +1246,27 @@ get_filename (Screen *screen, const char *directory, Bool verbose_p)
 static char *
 get_video_filename (Screen *screen, Bool verbose_p)
 {
-  return get_filename_1 (screen, 0, True, verbose_p);
+  return get_filename_1 (screen, 0, GRAB_VIDEO, verbose_p);
 }
 
+/* Grabs a desktop image to a file, and returns a pathname to that file.
+   Delete that file when you are done with it (and free the string.)
+ */
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+static char *
+get_desktop_filename (Screen *screen, Bool verbose_p)
+{
+  return get_filename_1 (screen, 0, GRAB_DESK, verbose_p);
+}
+#endif /* USE_EXTERNAL_SCREEN_GRABBER */
+
 
 /* Grabs a video frame, and renders it on the Drawable.
    Returns False if it fails;
  */
 static Bool
 display_video (Screen *screen, Window window, Drawable drawable,
-               Bool verbose_p)
+               Bool verbose_p, XRectangle *geom_ret)
 {
   char *filename = get_video_filename (screen, verbose_p);
   Bool status;
@@ -1113,7 +1278,68 @@ display_video (Screen *screen, Window window, Drawable drawable,
       return False;
     }
 
-  status = display_file (screen, window, drawable, filename, verbose_p);
+  status = display_file (screen, window, drawable, filename, verbose_p,
+                         geom_ret);
+
+  if (unlink (filename))
+    {
+      char buf[512];
+      sprintf (buf, "%s: rm %.100s", progname, filename);
+      perror (buf);
+    }
+  else if (verbose_p)
+    fprintf (stderr, "%s: rm %s\n", progname, filename);
+
+  if (filename) free (filename);
+  return status;
+}
+
+
+/* Grabs a desktop screen shot onto the window and the drawable.
+   If the window and drawable are not the same size, the image in
+   the drawable is scaled to fit.
+   Returns False if it fails.
+ */
+static Bool
+display_desktop (Screen *screen, Window window, Drawable drawable,
+                 Bool verbose_p, XRectangle *geom_ret)
+{
+# ifdef USE_EXTERNAL_SCREEN_GRABBER
+
+  Display *dpy = DisplayOfScreen (screen);
+  Bool top_p = top_level_window_p (screen, window);
+  char *filename;
+  Bool status;
+
+  if (top_p)
+    {
+      if (verbose_p)
+        fprintf (stderr, "%s: unmapping 0x%lx.\n", progname,
+                 (unsigned long) window);
+      XUnmapWindow (dpy, window);
+      XSync (dpy, False);
+    }
+
+  filename = get_desktop_filename (screen, verbose_p);
+
+  if (top_p)
+    {
+      if (verbose_p)
+        fprintf (stderr, "%s: mapping 0x%lx.\n", progname,
+                 (unsigned long) window);
+      XMapRaised (dpy, window);
+      XSync (dpy, False);
+    }
+
+  if (!filename)
+    {
+      if (verbose_p)
+        fprintf (stderr, "%s: desktop grab failed.\n", progname);
+      return False;
+    }
+
+  status = display_file (screen, window, drawable, filename, verbose_p,
+                         geom_ret);
 
   if (unlink (filename))
     {
@@ -1126,6 +1352,106 @@ display_video (Screen *screen, Window window, Drawable drawable,
 
   if (filename) free (filename);
   return status;
+
+# else /* !USE_EXTERNAL_SCREEN_GRABBER */
+
+  Display *dpy = DisplayOfScreen (screen);
+  XGCValues gcv;
+  XWindowAttributes xgwa;
+  Window root;
+  int px, py;
+  unsigned int pw, ph, pbw, pd;
+  int srcx, srcy, destx, desty, w2, h2;
+
+  if (verbose_p)
+    {
+      fprintf (stderr, "%s: grabbing desktop image\n", progname);
+      grabscreen_verbose();
+    }
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  XGetGeometry (dpy, drawable, &root, &px, &py, &pw, &ph, &pbw, &pd);
+
+  grab_screen_image_internal (screen, window);
+
+  compute_image_scaling (xgwa.width, xgwa.height,
+                         pw, ph, verbose_p,
+                         &srcx, &srcy, &destx, &desty, &w2, &h2);
+
+  if (pw == w2 && ph == h2)  /* it fits -- just copy server-side pixmaps */
+    {
+      GC gc = XCreateGC (dpy, drawable, 0, &gcv);
+      XCopyArea (dpy, window, drawable, gc,
+                 0, 0, xgwa.width, xgwa.height, 0, 0);
+      XFreeGC (dpy, gc);
+    }
+  else  /* size mismatch -- must scale client-side images to fit drawable */
+    {
+      GC gc;
+      XImage *ximage = 0;
+      XErrorHandler old_handler;
+
+      XSync (dpy, False);
+      old_handler = XSetErrorHandler (ignore_badmatch_ehandler);
+      error_handler_hit_p = False;
+
+      /* This can return BadMatch if the window is not fully on screen.
+         Trap that error and return color bars in that case.
+         (Note that this only happens with XGetImage, not with XCopyArea:
+         yet another totally gratuitous inconsistency in X, thanks.)
+       */
+      ximage = XGetImage (dpy, window, 0, 0, xgwa.width, xgwa.height,
+                          ~0L, ZPixmap);
+
+      XSync (dpy, False);
+      XSetErrorHandler (old_handler);
+      XSync (dpy, False);
+
+      if (error_handler_hit_p)
+        {
+          ximage = 0;
+          if (verbose_p)
+            fprintf (stderr, "%s: BadMatch reading window 0x%x contents!\n",
+                     progname, (unsigned int) window);
+        }
+
+      if (!ximage ||
+          !scale_ximage (xgwa.screen, xgwa.visual, ximage, w2, h2))
+        return False;
+
+      gc = XCreateGC (dpy, drawable, 0, &gcv);
+      clear_drawable (screen, drawable);
+      XPutImage (dpy, drawable, gc, ximage, 
+                 srcx, srcy, destx, desty, ximage->width, ximage->height);
+      XDestroyImage (ximage);
+      XFreeGC (dpy, gc);
+    }
+
+  if (geom_ret)
+    {
+      geom_ret->x = destx;
+      geom_ret->y = desty;
+      geom_ret->width  = w2;
+      geom_ret->height = h2;
+    }
+
+  XSync (dpy, False);
+  return True;
+
+# endif /* !USE_EXTERNAL_SCREEN_GRABBER */
+}
+
+
+/* Whether the given Drawable is unreasonably small.
+ */
+static Bool
+drawable_miniscule_p (Display *dpy, Drawable drawable)
+{
+  Window root;
+  int xx, yy;
+  unsigned int bw, d, w = 0, h = 0;
+  XGetGeometry (dpy, drawable, &root, &xx, &yy, &w, &h, &bw, &d);
+  return (w < 32 || h < 32);
 }
 
 
@@ -1144,9 +1470,10 @@ get_image (Screen *screen,
            const char *file)
 {
   Display *dpy = DisplayOfScreen (screen);
-  enum { do_desk, do_video, do_image, do_bars } which = do_bars;
-  int count = 0;
+  grab_type which = GRAB_BARS;
   struct stat st;
+  const char *file_prop = 0;
+  XRectangle geom = { 0, 0, 0, 0 };
 
   if (! drawable_window_p (dpy, window))
     {
@@ -1155,6 +1482,13 @@ get_image (Screen *screen,
       exit (1);
     }
 
+  /* Make sure the Screen and the Window correspond. */
+  {
+    XWindowAttributes xgwa;
+    XGetWindowAttributes (dpy, window, &xgwa);
+    screen = xgwa.screen;
+  }
+
   if (file && stat (file, &st))
     {
       fprintf (stderr, "%s: file \"%s\" does not exist\n", progname, file);
@@ -1175,6 +1509,10 @@ get_image (Screen *screen,
 
 # if !(defined(HAVE_GDK_PIXBUF) || defined(HAVE_JPEGLIB))
   image_p = False;    /* can't load images from files... */
+#  ifdef USE_EXTERNAL_SCREEN_GRABBER
+  desk_p = False;     /* ...or from desktops grabbed to files. */
+#  endif
+
   if (file)
     {
       fprintf (stderr,
@@ -1191,8 +1529,7 @@ get_image (Screen *screen,
       video_p = False;
       image_p = True;
     }
-
-  if (!dir || !*dir)
+  else if (!dir || !*dir)
     {
       if (verbose_p && image_p)
         fprintf (stderr,
@@ -1201,18 +1538,29 @@ get_image (Screen *screen,
       image_p = False;
     }
 
+  /* If the target drawable is really small, no good can come of that.
+     Always do colorbars in that case.
+   */
+  if (drawable_miniscule_p (dpy, drawable))
+    {
+      desk_p  = False;
+      video_p = False;
+      image_p = False;
+    }
 
 # ifndef _VROOT_H_
 #  error Error!  This file definitely needs vroot.h!
 # endif
 
-  /* We can grab desktop images if:
+  /* We can grab desktop images (using the normal X11 method) if:
        - the window is the real root window;
-       - the window is the virtal root window;
        - the window is a toplevel window.
-     We cannot grab desktop images if:
+     We cannot grab desktop images that way if:
        - the window is a non-top-level window.
+
+     Using the MacOS X way, desktops are just like loaded image files.
    */
+# ifndef USE_EXTERNAL_SCREEN_GRABBER
   if (desk_p)
     {
       if (!top_level_window_p (screen, window))
@@ -1224,36 +1572,41 @@ get_image (Screen *screen,
                      progname, (unsigned int) window);
         }
     }
+# endif /* !USE_EXTERNAL_SCREEN_GRABBER */
 
-  count = 0;
-  if (desk_p)  count++;
-  if (video_p) count++;
-  if (image_p) count++;
-
-  if (count == 0)
-    which = do_bars;
+  if (! (desk_p || video_p || image_p))
+    which = GRAB_BARS;
   else
     {
       int i = 0;
-      while (1)  /* loop until we get one that's permitted */
-        {
-          which = (random() % 3);
-          if (which == do_desk  && desk_p)  break;
-          if (which == do_video && video_p) break;
-          if (which == do_image && image_p) break;
-          if (++i > 200) abort();
-        }
+      int n;
+      /* Loop until we get one that's permitted.
+         If files or video are permitted, do them more often
+         than desktop.
+
+             D+V+I: 10% + 45% + 45%.
+             V+I:   50% + 50%
+             D+V:   18% + 82%
+             D+I:   18% + 82%
+       */
+    AGAIN:
+      n = (random() % 100);
+      if (++i > 300) abort();
+      else if (desk_p  && n < 10) which = GRAB_DESK;   /* 10% */
+      else if (video_p && n < 55) which = GRAB_VIDEO;  /* 45% */
+      else if (image_p)           which = GRAB_FILE;   /* 45% */
+      else goto AGAIN;
     }
 
 
   /* If we're to search a directory to find an image file, do so now.
    */
-  if (which == do_image && !file)
+  if (which == GRAB_FILE && !file)
     {
       file = get_filename (screen, dir, verbose_p);
       if (!file)
         {
-          which = do_bars;
+          which = GRAB_BARS;
           if (verbose_p)
             fprintf (stderr, "%s: no image files found.\n", progname);
         }
@@ -1261,48 +1614,65 @@ get_image (Screen *screen,
 
   /* Now actually render something.
    */
-  if (which == do_bars)
-    {
-      XWindowAttributes xgwa;
-    COLORBARS:
-      if (verbose_p)
-        fprintf (stderr, "%s: drawing colorbars.\n", progname);
-      XGetWindowAttributes (dpy, window, &xgwa);
-      draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
-                      0, 0, 0, 0);
-      XSync (dpy, False);
-    }
-  else if (which == do_desk)
+  switch (which)
     {
-      GC gc;
-      XGCValues gcv;
-      XWindowAttributes xgwa;
+    case GRAB_BARS:
+      {
+        XWindowAttributes xgwa;
+      COLORBARS:
+        if (verbose_p)
+          fprintf (stderr, "%s: drawing colorbars.\n", progname);
+        XGetWindowAttributes (dpy, window, &xgwa);
+        draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
+                        0, 0, 0, 0);
+        XSync (dpy, False);
+      }
+      break;
 
-      if (verbose_p)
-        {
-          fprintf (stderr, "%s: grabbing desktop image\n", progname);
-          grabscreen_verbose();
-        }
-      gc = XCreateGC (dpy, drawable, 0, &gcv);
-      XGetWindowAttributes (dpy, window, &xgwa);
-      grab_screen_image (screen, window);
-      XCopyArea (dpy, window, drawable, gc,
-                 0, 0, xgwa.width, xgwa.height, 0, 0);
-      XFreeGC (dpy, gc);
-      XSync (dpy, False);
-    }
-  else if (which == do_image)
-    {
-      if (! display_file (screen, window, drawable, file, verbose_p))
+    case GRAB_DESK:
+      if (! display_desktop (screen, window, drawable, verbose_p, &geom))
         goto COLORBARS;
-    }
-  else if (which == do_video)
-    {
-      if (! display_video (screen, window, drawable, verbose_p))
+      file_prop = "desktop";
+      break;
+
+    case GRAB_FILE:
+      if (! display_file (screen, window, drawable, file, verbose_p, &geom))
+        goto COLORBARS;
+      file_prop = file;
+      break;
+
+    case GRAB_VIDEO:
+      if (! display_video (screen, window, drawable, verbose_p, &geom))
         goto COLORBARS;
+      file_prop = "video";
+      break;
+
+    default:
+      abort();
+      break;
     }
-  else
-    abort();
+
+  {
+    Atom a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
+    if (file_prop && *file_prop)
+      XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, 
+                       (unsigned char *) file_prop, strlen(file_prop));
+    else
+      XDeleteProperty (dpy, window, a);
+
+    a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
+    if (geom.width > 0)
+      {
+        char gstr[30];
+        sprintf (gstr, "%dx%d+%d+%d", geom.width, geom.height, geom.x, geom.y);
+        XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, 
+                         (unsigned char *) gstr, strlen (gstr));
+      }
+    else
+      XDeleteProperty (dpy, window, a);
+  }
+
+  XSync (dpy, False);
 }
 
 
@@ -1427,7 +1797,7 @@ main (int argc, char **argv)
 
   memset (&P, 0, sizeof(P));
   P.db = db;
-  load_init_file (&P);
+  load_init_file (dpy, &P);
 
   progname = argv[0] = oprogname;
 
@@ -1459,10 +1829,10 @@ main (int argc, char **argv)
               goto LOSE;
             }
           window_str = argv[i];
-          window = (Window) RootWindowOfScreen (screen);
+          window = VirtualRootWindowOfScreen (screen);
         }
       else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
-                1 == sscanf (argv[i], " %ld %c",   &w, &dummy)) &&
+                1 == sscanf (argv[i], " %lu %c",   &w, &dummy)) &&
                w != 0)
         {
           if (drawable)
@@ -1491,6 +1861,11 @@ main (int argc, char **argv)
             fprintf (stderr, "\n%s: unparsable window/pixmap ID: \"%s\"\n",
                      progname, argv[i]);
         LOSE:
+# ifdef __GNUC__
+          __extension__   /* don't warn about "string length is greater than
+                             the length ISO C89 compilers are required to
+                             support" in the usage string... */
+# endif
           fprintf (stderr, USAGE, progname, version, progname);
           exit (1);
         }