From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / utils / grabscreen.c
index 328029e33eb2d785753cdf97e33ec5d30fb6ca2a..23f40aad9b98122db39454075c5eafffd79c4106 100644 (file)
@@ -1,5 +1,4 @@
-/* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997, 1998
- *  Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1992-2013 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
    the difference between drawing on the actual root window, and on the fake
    root window used by the screensaver, since at this level the illusion 
    breaks down...
+
+   The hacks themselves use utils/grabclient.c to invoke the
+   "xscreensaver-getimage" program as a sub-process.
+
+   This code is linked only into "driver/xscreensaver-getimage".  On normal
+   X11 systems, "xscreensaver-getimage.c" invokes the code in this file.
+
+   However, under X11 on MacOS, "xscreensaver-getimage" instead runs the
+   script "driver/xscreensaver-getimage-desktop", which invokes the MacOS-
+   specific program "/usr/sbin/screencapture" to get the desktop image.
+
+   However again, for the MacOS-native (Cocoa) build of the screen savers,
+   "utils/grabclient.c" instead links against "OSX/osxgrabscreen.m", which
+   grabs screen images directly without invoking a sub-process to do it.
  */
 
 #include "utils.h"
@@ -117,12 +130,12 @@ xscreensaver_window_p (Display *dpy, Window window)
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
-  char *version;
+  unsigned char *version;
   if (XGetWindowProperty (dpy, window,
                          XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
                          0, 1, False, XA_STRING,
                          &type, &format, &nitems, &bytesafter,
-                         (unsigned char **) &version)
+                         &version)
       == Success
       && type != None)
     return True;
@@ -131,10 +144,59 @@ xscreensaver_window_p (Display *dpy, Window window)
 
 
 
+/* Whether the given window is:
+   - the real root window;
+   - a direct child of the root window;
+   - a direct child of the window manager's decorations.
+ */
+Bool
+top_level_window_p (Screen *screen, Window window)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Window root, parent, *kids;
+  unsigned int nkids;
+
+  if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
+    return False;
+
+  if (window == root)
+    return True;
+
+  /* If our direct parent is the real root window, then yes. */
+  if (parent == root)
+    return True;
+  else
+    {
+      Atom type = None;
+      int format;
+      unsigned long nitems, bytesafter;
+      unsigned char *data;
+
+      /* If our direct parent has the WM_STATE property, then it is a
+         window manager decoration -- yes.
+      */
+      if (XGetWindowProperty (dpy, window,
+                              XInternAtom (dpy, "WM_STATE", True),
+                              0, 0, False, AnyPropertyType,
+                              &type, &format, &nitems, &bytesafter,
+                              (unsigned char **) &data)
+          == Success
+          && type != None)
+        return True;
+    }
+
+  /* Else, no.  We're deep in a tree somewhere.
+   */
+  return False;
+}
+
+
+static Bool error_handler_hit_p = False;
 static XErrorHandler old_ehandler = 0;
 static int
 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
 {
+  error_handler_hit_p = True;
   if (error->error_code == BadWindow || error->error_code == BadDrawable)
     return 0;
   else if (!old_ehandler)
@@ -174,17 +236,18 @@ use_subwindow_mode_p(Screen *screen, Window window)
 static void
 install_screen_colormaps (Screen *screen)
 {
-  int i;
+  unsigned int i;
   Display *dpy = DisplayOfScreen (screen);
-  Window vroot, real_root;
+  Window real_root;
   Window parent, *kids = 0;
   unsigned int nkids = 0;
 
   XSync (dpy, False);
   old_ehandler = XSetErrorHandler (BadWindow_ehandler);
+  error_handler_hit_p = False;
 
-  vroot = VirtualRootWindowOfScreen (screen);
-  if (XQueryTree (dpy, vroot, &real_root, &parent, &kids, &nkids))
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  if (XQueryTree (dpy, real_root, &real_root, &parent, &kids, &nkids))
     for (i = 0; i < nkids; i++)
       {
        XWindowAttributes xgwa;
@@ -210,16 +273,23 @@ install_screen_colormaps (Screen *screen)
 
 
 void
-grab_screen_image (Screen *screen, Window window)
+grab_screen_image_internal (Screen *screen, Window window)
 {
   Display *dpy = DisplayOfScreen (screen);
   XWindowAttributes xgwa;
-  Window real_root = XRootWindowOfScreen (screen);  /* not vroot */
-  Bool root_p = (window == real_root);
-  Bool saver_p = xscreensaver_window_p (dpy, window);
+  Window real_root;
+  Bool root_p;
+  Bool saver_p;
   Bool grab_mouse_p = False;
   int unmap_time = 0;
 
+  real_root = XRootWindowOfScreen (screen);  /* not vroot */
+  root_p = (window == real_root);
+  saver_p = xscreensaver_window_p (dpy, window);
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  screen = xgwa.screen;
+
   if (saver_p)
     /* I think this is redundant, but just to be safe... */
     root_p = False;
@@ -234,12 +304,12 @@ grab_screen_image (Screen *screen, Window window)
       double unmap = 0;
       if (saver_p)
        {
-         unmap = get_float_resource("grabRootDelay", "Seconds");
+         unmap = get_float_resource(dpy, "grabRootDelay", "Seconds");
          if (unmap <= 0.00001 || unmap > 20) unmap = 2.5;
        }
       else
        {
-         unmap = get_float_resource("grabWindowDelay", "Seconds");
+         unmap = get_float_resource(dpy, "grabWindowDelay", "Seconds");
          if (unmap <= 0.00001 || unmap > 20) unmap = 0.66;
        }
       unmap_time = unmap * 100000;
@@ -247,18 +317,26 @@ grab_screen_image (Screen *screen, Window window)
 
   if (grab_verbose_p)
     {
-      XWindowAttributes xgwa2;
       fprintf(stderr,
               "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
               progname, (unsigned long) window,
               root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
 
-      XGetWindowAttributes (dpy, window, &xgwa2);
       fprintf(stderr, "%s: ", progname);
-      describe_visual(stderr, screen, xgwa2.visual, False);
+      describe_visual(stderr, screen, xgwa.visual, False);
       fprintf (stderr, "\n");
     }
 
+
+  if (!root_p && !top_level_window_p (screen, window))
+    {
+      if (grab_verbose_p)
+        fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n",
+                 progname, (unsigned long) window);
+      return;
+    }
+
+
   if (!root_p)
     XSetWindowBackgroundPixmap (dpy, window, None);
 
@@ -281,8 +359,6 @@ grab_screen_image (Screen *screen, Window window)
       usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
     }
 
-  XGetWindowAttributes (dpy, window, &xgwa);
-
   if (!root_p)
     {
 #ifdef HAVE_READ_DISPLAY_EXTENSION
@@ -308,15 +384,13 @@ grab_screen_image (Screen *screen, Window window)
   else  /* root_p */
     {
       Pixmap pixmap;
-      XWindowAttributes xgwa;
-      XGetWindowAttributes(dpy, window, &xgwa);
       pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
 
 #ifdef HAVE_READ_DISPLAY_EXTENSION
       if (! read_display(screen, window, pixmap, True))
 #endif
        {
-         Window real_root = XRootWindowOfScreen (xgwa.screen); /* not vroot */
+         Window real_root = XRootWindowOfScreen (screen); /* not vroot */
          XGCValues gcv;
          GC gc;
 
@@ -403,7 +477,7 @@ copy_default_colormap_contents (Screen *screen,
   XQueryColors (dpy, from_cmap, old_colors, max_cells);
 
   got_cells = max_cells;
-  allocate_writable_colors (dpy, to_cmap, pixels, &got_cells);
+  allocate_writable_colors (screen, to_cmap, pixels, &got_cells);
 
   if (grab_verbose_p && got_cells != max_cells)
     fprintf(stderr, "%s: got only %d of %d cells\n", progname,
@@ -470,24 +544,38 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
   /* Check to see if the server supports the extension, and bug out if not.
    */
   if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
-    return False;
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: no XReadDisplay extension\n", progname);
+      return False;
+    }
 
   /* If this isn't a visual we know how to handle, bug out.  We handle:
-      = TrueColor in depths 8, 12, 16, and 32;
+      = TrueColor in depths 8, 12, 15, 16, and 32;
       = PseudoColor and DirectColor in depths 8 and 12.
    */
   XGetWindowAttributes(dpy, window, &xgwa);
   class = visual_class (screen, xgwa.visual);
   if (class == TrueColor)
     {
-      if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 16 &&
-         xgwa.depth != 24 && xgwa.depth != 32)
-       return False;
+      if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 15 &&
+          xgwa.depth != 16 && xgwa.depth != 24 && xgwa.depth != 32)
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: TrueColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
     }
   else if (class == PseudoColor || class == DirectColor)
     {
       if (xgwa.depth != 8 && xgwa.depth != 12)
-       return False;
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: Pseudo/DirectColor depth %d unsupported\n",
+                    progname, xgwa.depth);
+          return False;
+        }
       else
        /* Allocate a TrueColor-like spread of colors for the image. */
        remap_p = True;
@@ -500,9 +588,15 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
   image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
                        hints, &hints);
   if (!image)
-    return False;
+    {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() failed\n", progname);
+      return False;
+    }
   if (!image->data)
     {
+      if (grab_verbose_p)
+        fprintf(stderr, "%s: XReadDisplay() returned no data\n", progname);
       XDestroyImage(image);
       return False;
     }
@@ -543,7 +637,11 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
                                     xgwa.width, xgwa.height,
                                     8, 0);
       if (!image2)
-       return False;
+        {
+          if (grab_verbose_p)
+            fprintf(stderr, "%s: out of memory?\n", progname);
+          return False;
+        }
 
       if (grab_verbose_p)
         fprintf(stderr, "%s: converting from depth %d to depth %d\n",
@@ -565,8 +663,9 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
              pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
            else if (xgwa.depth == 12)
              pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
-           else if (xgwa.depth == 16)
-             pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
+           else if (xgwa.depth == 16 || xgwa.depth == 15)
+              /* Gah! I don't understand why these are in the other order. */
+             pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3)));
            else
              abort();
 
@@ -631,6 +730,8 @@ read_display (Screen *screen, Window window, Pixmap into_pixmap,
 
 /* Makes and installs a colormap that makes a PseudoColor or DirectColor
    visual behave like a TrueColor visual of the same depth.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
  */
 static void
 allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
@@ -646,7 +747,7 @@ allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
 
   XGetWindowAttributes (dpy, window, &xgwa);
   cmap = xgwa.colormap;
-  depth = visual_depth(screen, visual);
+  depth = visual_depth (screen, visual);
 
   switch (depth)
     {
@@ -698,6 +799,11 @@ allocate_cubic_colormap (Screen *screen, Window window, Visual *visual)
   }
 }
 
+/* Find the pixel index that is closest to the given color
+   (using linear distance in RGB space -- which is far from the best way.)
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
 static unsigned long
 find_closest_pixel (XColor *colors, int ncolors,
                     unsigned long r, unsigned long g, unsigned long b)
@@ -733,6 +839,13 @@ find_closest_pixel (XColor *colors, int ncolors,
 }
 
 
+/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be 
+   displayable with the given X colormap.  The farther from a perfect
+   color cube the contents of the colormap are, the lossier the 
+   transformation will be.  No dithering is done.
+
+   #### Duplicated in driver/xscreensaver-getimage.c
+ */
 void
 remap_image (Screen *screen, Window window, Colormap cmap, XImage *image)
 {