From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / driver / windows.c
index 694236113b4a1581c6a0300e0084b9e270df9cfc..29edc82b66b1349fb3a3f6879a83fbd6f69ea4f6 100644 (file)
@@ -1,5 +1,5 @@
 /* windows.c --- turning the screen black; dealing with visuals, virtual roots.
- * xscreensaver, Copyright (c) 1991-2001 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1991-2014 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
@@ -30,7 +30,7 @@
 #endif /* HAVE_UNAME */
 
 #include <stdio.h>
-#include <X11/Xproto.h>                /* for CARD32 */
+/* #include <X11/Xproto.h>     / * for CARD32 */
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>         /* for XSetClassHint() */
 #include <X11/Xatom.h>
 #include <time.h>
 #include <sys/time.h>
 
+/* You might think that to store an array of 32-bit quantities onto a
+   server-side property, you would pass an array of 32-bit data quantities
+   into XChangeProperty().  You would be wrong.  You have to use an array
+   of longs, even if long is 64 bits (using 32 of each 64.)
+ */
+typedef long PROP32;
+
 #ifdef HAVE_MIT_SAVER_EXTENSION
 # include <X11/extensions/scrnsaver.h>
 #endif /* HAVE_MIT_SAVER_EXTENSION */
@@ -51,7 +58,6 @@
 # include <X11/extensions/Xinerama.h>
 #endif /* HAVE_XINERAMA */
 
-
 /* This file doesn't need the Xt headers, so stub these types out... */
 #undef XtPointer
 #define XtAppContext void*
@@ -171,6 +177,63 @@ ungrab_mouse(saver_info *si)
 }
 
 
+/* Apparently there is this program called "rdesktop" which is a windows
+   terminal server client for Unix.  It would seem that this program holds
+   the keyboard GRABBED the whole time it has focus!  This is, of course,
+   completely idiotic: the whole point of grabbing is to get events when
+   you do *not* have focus, so grabbing *only when* you have focus is
+   completely redundant -- unless your goal is to make xscreensaver not
+   able to ever lock the screen when your program is running.
+
+   If xscreensaver blanks while rdesktop still has a keyboard grab, then
+   when we try to prompt for the password, we won't get the characters:
+   they'll be typed into rdesktop.
+
+   Perhaps rdesktop will release its keyboard grab if it loses focus?
+   What the hell, let's give it a try.  If we fail to grab the keyboard
+   four times in a row, we forcibly set focus to "None" and try four
+   more times.  (We don't touch focus unless we're already having a hard
+   time getting a grab.)
+ */
+static void
+nuke_focus (saver_info *si, int screen_no)
+{
+  saver_preferences *p = &si->prefs;
+  Window focus = 0;
+  int rev = 0;
+
+  XGetInputFocus (si->dpy, &focus, &rev);
+
+  if (p->verbose_p)
+    {
+      char w[255], r[255];
+
+      if      (focus == PointerRoot) strcpy (w, "PointerRoot");
+      else if (focus == None)        strcpy (w, "None");
+      else    sprintf (w, "0x%lx", (unsigned long) focus);
+
+      if      (rev == RevertToParent)      strcpy (r, "RevertToParent");
+      else if (rev == RevertToPointerRoot) strcpy (r, "RevertToPointerRoot");
+      else if (rev == RevertToNone)        strcpy (r, "RevertToNone");
+      else    sprintf (r, "0x%x", rev);
+
+      fprintf (stderr, "%s: %d: removing focus from %s / %s.\n",
+               blurb(), screen_no, w, r);
+    }
+
+  XSetInputFocus (si->dpy, None, RevertToNone, CurrentTime);
+  XSync (si->dpy, False);
+}
+
+
+static void
+ungrab_keyboard_and_mouse (saver_info *si)
+{
+  ungrab_mouse (si);
+  ungrab_kbd (si);
+}
+
+
 static Bool
 grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor,
                          int screen_no)
@@ -178,6 +241,9 @@ grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor,
   Status mstatus = 0, kstatus = 0;
   int i;
   int retries = 4;
+  Bool focus_fuckus = False;
+
+ AGAIN:
 
   for (i = 0; i < retries; i++)
     {
@@ -191,8 +257,17 @@ grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor,
     }
 
   if (kstatus != GrabSuccess)
-    fprintf (stderr, "%s: couldn't grab keyboard!  (%s)\n",
-             blurb(), grab_string(kstatus));
+    {
+      fprintf (stderr, "%s: couldn't grab keyboard!  (%s)\n",
+               blurb(), grab_string(kstatus));
+
+      if (! focus_fuckus)
+        {
+          focus_fuckus = True;
+          nuke_focus (si, screen_no);
+          goto AGAIN;
+        }
+    }
 
   for (i = 0; i < retries; i++)
     {
@@ -209,15 +284,39 @@ grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor,
     fprintf (stderr, "%s: couldn't grab pointer!  (%s)\n",
              blurb(), grab_string(mstatus));
 
-  return (kstatus == GrabSuccess ||
-         mstatus == GrabSuccess);
-}
 
-static void
-ungrab_keyboard_and_mouse (saver_info *si)
-{
-  ungrab_mouse (si);
-  ungrab_kbd (si);
+  /* When should we allow blanking to proceed?  The current theory
+     is that a keyboard grab is mandatory; a mouse grab is optional.
+
+     - If we don't have a keyboard grab, then we won't be able to
+       read a password to unlock, so the kbd grab is mandatory.
+       (We can't conditionalize this on locked_p, because someone
+       might run "xscreensaver-command -lock" at any time.)
+
+     - If we don't have a mouse grab, then we might not see mouse
+       clicks as a signal to unblank -- but we will still see kbd
+       activity, so that's not a disaster.
+
+     It has been suggested that we should allow blanking if locking
+     is disabled, and we have a mouse grab but no keyboard grab
+     (that is: kstatus != GrabSuccess &&
+               mstatus == GrabSuccess && 
+               si->locking_disabled_p)
+     That would allow screen blanking (but not locking) while the gdm
+     login screen had the keyboard grabbed, but one would have to use
+     the mouse to unblank.  Keyboard characters would go to the gdm
+     login field without unblanking.  I have not made this change
+     because I'm not completely convinced it is a safe thing to do.
+   */
+
+  if (kstatus != GrabSuccess)  /* Do not blank without a kbd grab.   */
+    {
+      /* If we didn't get both grabs, release the one we did get. */
+      ungrab_keyboard_and_mouse (si);
+      return False;
+    }
+
+  return True;                 /* Grab is good, go ahead and blank.  */
 }
 
 
@@ -288,27 +387,43 @@ ensure_no_screensaver_running (Display *dpy, Screen *screen)
       Atom type;
       int format;
       unsigned long nitems, bytesafter;
-      char *version;
+      unsigned char *version;
 
       if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1,
                              False, XA_STRING, &type, &format, &nitems,
-                             &bytesafter, (unsigned char **) &version)
+                             &bytesafter, &version)
          == Success
          && type != None)
        {
-         char *id;
-         if (!XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
+         unsigned char *id;
+         if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
                                   False, XA_STRING, &type, &format, &nitems,
-                                  &bytesafter, (unsigned char **) &id)
-             == Success
+                                  &bytesafter, &id)
+             != Success
              || type == None)
-           id = "???";
+           id = (unsigned char *) "???";
 
          fprintf (stderr,
       "%s: already running on display %s (window 0x%x)\n from process %s.\n",
-                  blurb(), DisplayString (dpy), (int) kids [i], id);
+                  blurb(), DisplayString (dpy), (int) kids [i],
+                   (char *) id);
          status = True;
        }
+
+      else if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
+                                   False, XA_STRING, &type, &format, &nitems,
+                                   &bytesafter, &version)
+               == Success
+               && type != None
+               && !strcmp ((char *) version, "gnome-screensaver"))
+       {
+         fprintf (stderr,
+                 "%s: \"%s\" is already running on display %s (window 0x%x)\n",
+                  blurb(), (char *) version,
+                   DisplayString (dpy), (int) kids [i]);
+         status = True;
+          break;
+       }
     }
 
   if (kids) XFree ((char *) kids);
@@ -361,11 +476,6 @@ remove_vroot_property (Display *dpy, Window win)
 
 static Bool safe_XKillClient (Display *dpy, XID id);
 
-#ifdef HAVE_XF86VMODE
-static Bool safe_XF86VidModeGetViewPort (Display *, int, int *, int *);
-#endif /* HAVE_XF86VMODE */
-
-
 static void
 kill_xsetroot_data_1 (Display *dpy, Window window,
                       Atom prop, const char *atom_name,
@@ -374,7 +484,7 @@ kill_xsetroot_data_1 (Display *dpy, Window window,
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
-  Pixmap *dataP = 0;
+  unsigned char *dataP = 0;
 
   /* If the user has been using xv or xsetroot as a screensaver (to display
      an image on the screensaver window, as a kind of slideshow) then the
@@ -394,17 +504,18 @@ kill_xsetroot_data_1 (Display *dpy, Window window,
    */
   if (XGetWindowProperty (dpy, window, prop, 0, 1,
                          True, AnyPropertyType, &type, &format, &nitems, 
-                         &bytesafter, (unsigned char **) &dataP)
+                         &bytesafter, &dataP)
       == Success
       && type != None)
     {
-      if (dataP && *dataP && type == XA_PIXMAP && format == 32 &&
+      Pixmap *pixP = (Pixmap *) dataP;
+      if (pixP && *pixP && type == XA_PIXMAP && format == 32 &&
          nitems == 1 && bytesafter == 0)
        {
          if (verbose_p)
            fprintf (stderr, "%s: destroying %s data (0x%lX).\n",
-                    blurb(), atom_name, *dataP);
-         safe_XKillClient (dpy, *dataP);
+                    blurb(), atom_name, *pixP);
+         safe_XKillClient (dpy, *pixP);
        }
       else
        fprintf (stderr,
@@ -412,7 +523,7 @@ kill_xsetroot_data_1 (Display *dpy, Window window,
                  "\t%lu, %lu; type: %lu, format: %d, "
                  "nitems: %lu, bytesafter %ld\n",
                 blurb(), atom_name,
-                 (unsigned long) dataP, (dataP ? *dataP : 0), type,
+                 (unsigned long) pixP, (pixP ? *pixP : 0), type,
                 format, nitems, bytesafter);
     }
 }
@@ -462,15 +573,29 @@ save_real_vroot (saver_screen_info *ssi)
       Atom type;
       int format;
       unsigned long nitems, bytesafter;
-      Window *vrootP = 0;
+      unsigned char *dataP = 0;
+      Window *vrootP;
+      int j;
+
+      /* Skip this window if it is the xscreensaver window of any other
+         screen (this can happen in the Xinerama case.)
+       */
+      for (j = 0; j < si->nscreens; j++)
+        {
+          saver_screen_info *ssi2 = &si->screens[j];
+          if (kids[i] == ssi2->screensaver_window)
+            goto SKIP;
+        }
 
       if (XGetWindowProperty (dpy, kids[i], XA_VROOT, 0, 1, False, XA_WINDOW,
                              &type, &format, &nitems, &bytesafter,
-                             (unsigned char **) &vrootP)
+                             &dataP)
          != Success)
        continue;
-      if (! vrootP)
+      if (! dataP)
        continue;
+
+      vrootP = (Window *) dataP;
       if (ssi->real_vroot)
        {
          if (*vrootP == ssi->screensaver_window) abort ();
@@ -481,6 +606,8 @@ save_real_vroot (saver_screen_info *ssi)
        }
       ssi->real_vroot = kids [i];
       ssi->real_vroot_value = *vrootP;
+    SKIP:
+      ;
     }
 
   XSync (dpy, False);
@@ -506,7 +633,8 @@ restore_real_vroot_1 (saver_screen_info *ssi)
     fprintf (stderr,
             "%s: restoring __SWM_VROOT property on the real vroot (0x%lx).\n",
             blurb(), (unsigned long) ssi->real_vroot);
-  remove_vroot_property (si->dpy, ssi->screensaver_window);
+  if (ssi->screensaver_window)
+    remove_vroot_property (si->dpy, ssi->screensaver_window);
   if (ssi->real_vroot)
     {
       store_vroot_property (si->dpy, ssi->real_vroot, ssi->real_vroot_value);
@@ -708,9 +836,16 @@ saver_sighup_handler (int sig)
 
   fprintf (stderr, "%s: %s received: restarting...\n",
            blurb(), signal_name(sig));
-  unblank_screen (si);
-  kill_screenhack (si);
-  XSync (si->dpy, False);
+
+  if (si->screen_blanked_p)
+    {
+      int i;
+      for (i = 0; i < si->nscreens; i++)
+        kill_screenhack (&si->screens[i]);
+      unblank_screen (si);
+      XSync (si->dpy, False);
+    }
+
   restart_process (si);   /* Does not return */
   abort ();
 }
@@ -867,16 +1002,16 @@ store_saver_id (saver_screen_info *ssi)
 void
 store_saver_status (saver_info *si)
 {
-  CARD32 *status;
+  PROP32 *status;
   int size = si->nscreens + 2;
   int i;
 
-  status = (CARD32 *) calloc (size, sizeof(CARD32));
+  status = (PROP32 *) calloc (size, sizeof(PROP32));
 
-  status[0] = (CARD32) (si->screen_blanked_p
+  status[0] = (PROP32) (si->screen_blanked_p
                         ? (si->locked_p ? XA_LOCK : XA_BLANK)
                         : 0);
-  status[1] = (CARD32) si->blank_time;
+  status[1] = (PROP32) si->blank_time;
 
   for (i = 0; i < si->nscreens; i++)
     {
@@ -893,190 +1028,6 @@ store_saver_status (saver_info *si)
 }
 
 
-
-/* Returns the area of the screen which the xscreensaver window should cover.
-   Normally this is the whole screen, but if the X server's root window is
-   actually larger than the monitor's displayable area, then we want to
-   operate in the currently-visible portion of the desktop instead.
- */
-void
-get_screen_viewport (saver_screen_info *ssi,
-                     int *x_ret, int *y_ret,
-                     int *w_ret, int *h_ret,
-                     int target_x, int target_y,
-                     Bool verbose_p)
-{
-  int w = WidthOfScreen (ssi->screen);
-  int h = HeightOfScreen (ssi->screen);
-
-#ifdef HAVE_XF86VMODE
-  saver_info *si = ssi->global;
-  int event, error;
-  int dot;
-  XF86VidModeModeLine ml;
-  int x, y;
-  Bool xinerama_p;
-  Bool placement_only_p = (target_x != -1 && target_y != -1);
-
-#ifdef HAVE_XINERAMA
-  xinerama_p = (XineramaQueryExtension (si->dpy, &event, &error) &&
-                XineramaIsActive (si->dpy));
-#else  /* !HAVE_XINERAMA */
-  /* Even if we don't have the client-side Xinerama lib, check to see if
-     the server supports Xinerama, so that we know to ignore the VidMode
-     extension -- otherwise a server crash could result.  Yay. */
-  xinerama_p = XQueryExtension (si->dpy, "XINERAMA", &error, &event, &error);
-  
-#endif /* !HAVE_XINERAMA */
-
-#ifdef HAVE_XINERAMA
-  if (xinerama_p && placement_only_p)
-    {
-      int nscreens = 0;
-      XineramaScreenInfo *xsi = XineramaQueryScreens (si->dpy, &nscreens);
-      if (xsi)
-        {
-          /* Find the screen that contains the mouse. */
-          int which = -1;
-          int i;
-          for (i = 0; i < nscreens; i++)
-            {
-              if (target_x >= xsi[i].x_org &&
-                  target_y >= xsi[i].y_org &&
-                  target_x < xsi[i].x_org + xsi[i].width &&
-                  target_y < xsi[i].y_org + xsi[i].height)
-                which = i;
-              if (verbose_p)
-                {
-                  fprintf (stderr, "%s: %d: xinerama vp: %dx%d+%d+%d",
-                           blurb(), i,
-                           xsi[which].width, xsi[which].height,
-                           xsi[i].x_org, xsi[i].y_org);
-                  if (which == i)
-                    fprintf (stderr, "; mouse at %d,%d",
-                             target_x, target_y);
-                  fprintf (stderr, ".\n");
-                }
-            }
-          if (which == -1) which = 0;  /* didn't find it?  Use the first. */
-          *x_ret = xsi[which].x_org;
-          *y_ret = xsi[which].y_org;
-          *w_ret = xsi[which].width;
-          *h_ret = xsi[which].height;
-          XFree (xsi);
-          return;
-        }
-    }
-#endif /* HAVE_XINERAMA */
-
-  if (!xinerama_p &&  /* Xinerama + VidMode = broken. */
-      XF86VidModeQueryExtension (si->dpy, &event, &error) &&
-      safe_XF86VidModeGetViewPort (si->dpy, ssi->number, &x, &y) &&
-      XF86VidModeGetModeLine (si->dpy, ssi->number, &dot, &ml))
-    {
-      char msg[512];
-      *x_ret = x;
-      *y_ret = y;
-      *w_ret = ml.hdisplay;
-      *h_ret = ml.vdisplay;
-
-      if (*x_ret == 0 && *y_ret == 0 && *w_ret == w && *h_ret == h)
-        /* There is no viewport -- the screen does not scroll. */
-        return;
-
-
-      /* Apparently some versions of XFree86 return nonsense here!
-         I've had reports of 1024x768 viewports at -1936862040, -1953705044.
-         So, sanity-check the values and give up if they are out of range.
-       */
-      if (*x_ret <  0 || *x_ret >= w ||
-          *y_ret <  0 || *y_ret >= h ||
-          *w_ret <= 0 || *w_ret >  w ||
-          *h_ret <= 0 || *h_ret >  h)
-        {
-          static int warned_once = 0;
-          if (!warned_once)
-            {
-              fprintf (stderr, "\n"
-                  "%s: X SERVER BUG: %dx%d viewport at %d,%d is impossible.\n"
-                  "%s: The XVidMode server extension is returning nonsense.\n"
-                  "%s: Please report this bug to your X server vendor.\n\n",
-                       blurb(), *w_ret, *h_ret, *x_ret, *y_ret,
-                       blurb(), blurb());
-              warned_once = 1;
-            }
-          *x_ret = 0;
-          *y_ret = 0;
-          *w_ret = w;
-          *h_ret = h;
-          return;
-        }
-
-      sprintf (msg, "%s: %d: vp is %dx%d+%d+%d",
-               blurb(), ssi->number,
-               *w_ret, *h_ret, *x_ret, *y_ret);
-
-
-      /* Apparently, though the server stores the X position in increments of
-         1 pixel, it will only make changes to the *display* in some other
-         increment.  With XF86_SVGA on a Thinkpad, the display only updates
-         in multiples of 8 pixels when in 8-bit mode, and in multiples of 4
-         pixels in 16-bit mode.  I don't know what it does in 24- and 32-bit
-         mode, because I don't have enough video memory to find out.
-
-         I consider it a bug that XF86VidModeGetViewPort() is telling me the
-         server's *target* scroll position rather than the server's *actual*
-         scroll position.  David Dawes agrees, and says they may fix this in
-         XFree86 4.0, but it's notrivial.
-
-         He also confirms that this behavior is server-dependent, so the
-         actual scroll position cannot be reliably determined by the client.
-         So... that means the only solution is to provide a ``sandbox''
-         around the blackout window -- we make the window be up to N pixels
-         larger than the viewport on both the left and right sides.  That
-         means some part of the outer edges of each hack might not be
-         visible, but screw it.
-
-         I'm going to guess that 16 pixels is enough, and that the Y dimension
-         doesn't have this problem.
-
-         The drawback of doing this, of course, is that some of the screenhacks
-         will still look pretty stupid -- for example, "slidescreen" will cut
-         off the left and right edges of the grid, etc.
-      */
-# define FUDGE 16
-      if (x > 0 && x < w - ml.hdisplay)  /* not at left edge or right edge */
-        {
-          /* Round X position down to next lower multiple of FUDGE.
-             Increase width by 2*FUDGE in case some server rounds up.
-           */
-          *x_ret = ((x - 1) / FUDGE) * FUDGE;
-          *w_ret += (FUDGE * 2);
-        }
-# undef FUDGE
-
-      if (*x_ret != x ||
-          *y_ret != y ||
-          *w_ret != ml.hdisplay ||
-          *h_ret != ml.vdisplay)
-        sprintf (msg + strlen(msg), "; fudged to %dx%d+%d+%d",
-                 *w_ret, *h_ret, *x_ret, *y_ret);
-
-      if (verbose_p)
-        fprintf (stderr, "%s.\n", msg);
-
-      return;
-    }
-
-#endif /* HAVE_XF86VMODE */
-
-  *x_ret = 0;
-  *y_ret = 0;
-  *w_ret = w;
-  *h_ret = h;
-}
-
-
 static Bool error_handler_hit_p = False;
 
 static int
@@ -1170,7 +1121,7 @@ safe_XKillClient (Display *dpy, XID id)
 
 
 #ifdef HAVE_XF86VMODE
-static Bool
+Bool
 safe_XF86VidModeGetViewPort (Display *dpy, int screen, int *xP, int *yP)
 {
   Bool result;
@@ -1214,13 +1165,9 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
   XColor black;
   XSetWindowAttributes attrs;
   unsigned long attrmask;
-  int x, y, width, height;
   static Bool printed_visual_info = False;  /* only print the message once. */
   Window horked_window = 0;
 
-  get_screen_viewport (ssi, &x, &y, &width, &height, -1, -1,
-                       (p->verbose_p && !si->screen_blanked_p));
-
   black.red = black.green = black.blue = 0;
 
   if (ssi->cmap == DefaultColormapOfScreen (ssi->screen))
@@ -1273,8 +1220,6 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
   attrs.backing_pixel = ssi->black_pixel;
   attrs.border_pixel = ssi->black_pixel;
 
-  if (p->debug_p) width = width / 2;
-
   if (!p->verbose_p || printed_visual_info)
     ;
   else if (ssi->current_visual == DefaultVisualOfScreen (ssi->screen))
@@ -1350,10 +1295,10 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
     {
       XWindowChanges changes;
       unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
-      changes.x = x;
-      changes.y = y;
-      changes.width = width;
-      changes.height = height;
+      changes.x = ssi->x;
+      changes.y = ssi->y;
+      changes.width = ssi->width;
+      changes.height = ssi->height;
       changes.border_width = 0;
 
       if (! (safe_XConfigureWindow (si->dpy, ssi->screensaver_window,
@@ -1370,10 +1315,9 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
     {
       ssi->screensaver_window =
        XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
-                       x, y, width, height,
+                       ssi->x, ssi->y, ssi->width, ssi->height,
                        0, ssi->current_depth, InputOutput,
                       ssi->current_visual, attrmask, &attrs);
-
       reset_stderr (ssi);
 
       if (horked_window)
@@ -1425,6 +1369,113 @@ initialize_screensaver_window (saver_info *si)
 }
 
 
+/* Called when the RANDR (Resize and Rotate) extension tells us that
+   the size of the screen has changed while the screen was blanked.
+   Call update_screen_layout() first, then call this to synchronize
+   the size of the saver windows to the new sizes of the screens.
+ */
+void
+resize_screensaver_window (saver_info *si)
+{
+  saver_preferences *p = &si->prefs;
+  int i;
+
+  for (i = 0; i < si->nscreens; i++)
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      XWindowAttributes xgwa;
+
+      /* Make sure a window exists -- it might not if a monitor was just
+         added for the first time.
+       */
+      if (! ssi->screensaver_window)
+        {
+          initialize_screensaver_window_1 (ssi);
+          if (p->verbose_p)
+            fprintf (stderr,
+                     "%s: %d: newly added window 0x%lx %dx%d+%d+%d\n",
+                     blurb(), i, (unsigned long) ssi->screensaver_window,
+                     ssi->width, ssi->height, ssi->x, ssi->y);
+        }
+
+      /* Make sure the window is the right size -- it might not be if
+         the monitor changed resolution, or if a badly-behaved hack
+         screwed with it.
+       */
+      XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+      if (xgwa.x      != ssi->x ||
+          xgwa.y      != ssi->y ||
+          xgwa.width  != ssi->width ||
+          xgwa.height != ssi->height)
+        {
+          XWindowChanges changes;
+          unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
+          changes.x      = ssi->x;
+          changes.y      = ssi->y;
+          changes.width  = ssi->width;
+          changes.height = ssi->height;
+          changes.border_width = 0;
+
+          if (p->verbose_p)
+            fprintf (stderr,
+                     "%s: %d: resize 0x%lx from %dx%d+%d+%d to %dx%d+%d+%d\n",
+                     blurb(), i, (unsigned long) ssi->screensaver_window,
+                     xgwa.width, xgwa.height, xgwa.x, xgwa.y,
+                     ssi->width, ssi->height, ssi->x, ssi->y);
+
+          if (! safe_XConfigureWindow (si->dpy, ssi->screensaver_window,
+                                       changesmask, &changes))
+            fprintf (stderr, "%s: %d: someone horked our saver window"
+                     " (0x%lx)!  Unable to resize it!\n",
+                     blurb(), i, (unsigned long) ssi->screensaver_window);
+        }
+
+      /* Now (if blanked) make sure that it's mapped and running a hack --
+         it might not be if we just added it.  (We also might be re-using
+         an old window that existed for a previous monitor that was
+         removed and re-added.)
+
+         Note that spawn_screenhack() calls select_visual() which may destroy
+         and re-create the window via initialize_screensaver_window_1().
+       */
+      if (si->screen_blanked_p)
+        {
+          if (ssi->cmap)
+            XInstallColormap (si->dpy, ssi->cmap);
+          XMapRaised (si->dpy, ssi->screensaver_window);
+          if (! ssi->pid)
+            spawn_screenhack (ssi);
+
+          /* Make sure the act of adding a screen doesn't present as 
+             pointer motion (and thus cause an unblank). */
+          {
+            Window root, child;
+            int x, y;
+            unsigned int mask;
+            XQueryPointer (si->dpy, ssi->screensaver_window, &root, &child,
+                           &ssi->last_poll_mouse.root_x,
+                           &ssi->last_poll_mouse.root_y,
+                           &x, &y, &mask);
+          }
+        }
+    }
+
+  /* Kill off any savers running on no-longer-extant monitors.
+   */
+  for (; i < si->ssi_count; i++)
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      if (ssi->pid)
+        kill_screenhack (ssi);
+      if (ssi->screensaver_window)
+        {
+          XUnmapWindow (si->dpy, ssi->screensaver_window);
+          restore_real_vroot_1 (ssi);
+        }
+    }
+}
+
+
 void 
 raise_window (saver_info *si,
              Bool inhibit_fade, Bool between_hacks_p, Bool dont_clear)
@@ -1481,7 +1532,8 @@ raise_window (saver_info *si,
 
       /* Note!  The server is grabbed, and this will take several seconds
         to complete! */
-      fade_screens (si->dpy, current_maps, current_windows,
+      fade_screens (si->dpy, current_maps,
+                    current_windows, si->nscreens,
                    p->fade_seconds/1000, p->fade_ticks, True, !dont_clear);
 
       free(current_maps);
@@ -1535,33 +1587,34 @@ int
 mouse_screen (saver_info *si)
 {
   saver_preferences *p = &si->prefs;
+  Window pointer_root, pointer_child;
+  int root_x, root_y, win_x, win_y;
+  unsigned int mask;
+  int i;
 
   if (si->nscreens == 1)
     return 0;
-  else
+
+  for (i = 0; i < si->nscreens; i++)
     {
-      int i;
-      for (i = 0; i < si->nscreens; i++)
+      saver_screen_info *ssi = &si->screens[i];
+      if (XQueryPointer (si->dpy, RootWindowOfScreen (ssi->screen),
+                         &pointer_root, &pointer_child,
+                         &root_x, &root_y, &win_x, &win_y, &mask) &&
+          root_x >= ssi->x &&
+          root_y >= ssi->y &&
+          root_x <  ssi->x + ssi->width &&
+          root_y <  ssi->y + ssi->height)
         {
-          saver_screen_info *ssi = &si->screens[i];
-          Window pointer_root, pointer_child;
-          int root_x, root_y, win_x, win_y;
-          unsigned int mask;
-          if (XQueryPointer (si->dpy,
-                             RootWindowOfScreen (ssi->screen),
-                             &pointer_root, &pointer_child,
-                             &root_x, &root_y, &win_x, &win_y, &mask))
-            {
-              if (p->verbose_p)
-                fprintf (stderr, "%s: mouse is on screen %d of %d\n",
-                         blurb(), i, si->nscreens);
-              return i;
-            }
+          if (p->verbose_p)
+            fprintf (stderr, "%s: mouse is on screen %d of %d\n",
+                     blurb(), i, si->nscreens);
+          return i;
         }
-
-      /* couldn't figure out where the mouse is?  Oh well. */
-      return 0;
     }
+
+  /* couldn't figure out where the mouse is?  Oh well. */
+  return 0;
 }
 
 
@@ -1589,12 +1642,17 @@ blank_screen (saver_info *si)
                                 mscreen);
 
 
+# if 0
   if (si->using_mit_saver_extension || si->using_sgi_saver_extension)
     /* If we're using a server extension, then failure to get a grab is
        not a big deal -- even without the grab, we will still be able
        to un-blank when there is user activity, since the server will
        tell us. */
+    /* #### No, that's not true: if we don't have a keyboard grab,
+            then we can't read passwords to unlock.
+     */
     ok = True;
+# endif /* 0 */
 
   if (!ok)
     return False;
@@ -1602,8 +1660,8 @@ blank_screen (saver_info *si)
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
-
-      save_real_vroot (ssi);
+      if (ssi->real_screen_p)
+        save_real_vroot (ssi);
       store_vroot_property (si->dpy,
                            ssi->screensaver_window,
                            ssi->screensaver_window);
@@ -1639,7 +1697,7 @@ unblank_screen (saver_info *si)
   Bool unfade_p = (si->fading_possible_p && p->unfade_p);
   int i;
 
-  monitor_power_on (si);
+  monitor_power_on (si, True);
   reset_watchdog_timer (si, False);
 
   if (si->demoing_p)
@@ -1678,8 +1736,8 @@ unblank_screen (saver_info *si)
       XUngrabServer (si->dpy);
       XSync (si->dpy, False);                  /* ###### (danger over) */
 
-
-      fade_screens (si->dpy, 0, current_windows,
+      fade_screens (si->dpy, 0,
+                    current_windows, si->nscreens,
                    p->fade_seconds/1000, p->fade_ticks,
                    False, False);
 
@@ -1707,7 +1765,7 @@ unblank_screen (saver_info *si)
 
   /* If the focus window does has a non-default colormap, then install
      that colormap as well.  (On SGIs, this will cause both the root map
-     and the focus map to be installed simultaniously.  It'd be nice to
+     and the focus map to be installed simultaneously.  It'd be nice to
      pick up the other colormaps that had been installed, too; perhaps
      XListInstalledColormaps could be used for that?)
    */
@@ -1792,10 +1850,30 @@ maybe_transfer_grabs (saver_screen_info *ssi,
 }
 
 
+static Visual *
+get_screen_gl_visual (saver_info *si, int real_screen_number)
+{
+  int i;
+  int nscreens = ScreenCount (si->dpy);
+
+  if (! si->best_gl_visuals)
+    si->best_gl_visuals = (Visual **) 
+      calloc (nscreens + 1, sizeof (*si->best_gl_visuals));
+
+  for (i = 0; i < nscreens; i++)
+    if (! si->best_gl_visuals[i])
+      si->best_gl_visuals[i] = 
+        get_best_gl_visual (si, ScreenOfDisplay (si->dpy, i));
+
+  if (real_screen_number < 0 || real_screen_number >= nscreens) abort();
+  return si->best_gl_visuals[real_screen_number];
+}
+
 
 Bool
 select_visual (saver_screen_info *ssi, const char *visual_name)
 {
+  XWindowAttributes xgwa;
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
   Bool install_cmap_p = p->install_cmap_p;
@@ -1803,6 +1881,24 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
   Visual *new_v = 0;
   Bool got_it;
 
+  /* On some systems (most recently, MacOS X) OpenGL programs get confused
+     when you kill one and re-start another on the same window.  So maybe
+     it's best to just always destroy and recreate the xscreensaver window
+     when changing hacks, instead of trying to reuse the old one?
+   */
+  Bool always_recreate_window_p = True;
+
+  get_screen_gl_visual (si, 0);   /* let's probe all the GL visuals early */
+
+  /* We make sure the existing window is actually on ssi->screen before
+     trying to use it, in case things moved around radically when monitors
+     were added or deleted.  If we don't do this we could get a BadMatch
+     even though the depths match.  I think.
+   */
+  memset (&xgwa, 0, sizeof(xgwa));
+  if (ssi->screensaver_window)
+    XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+
   if (visual_name && *visual_name)
     {
       if (!strcmp(visual_name, "default-i") ||
@@ -1824,7 +1920,7 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
                !strcmp(visual_name, "Gl") ||
                !strcmp(visual_name, "GL"))
         {
-          new_v = ssi->best_gl_visual;
+          new_v = get_screen_gl_visual (si, ssi->real_screen_number);
           if (!new_v && p->verbose_p)
             fprintf (stderr, "%s: no GL visuals.\n", progname);
         }
@@ -1845,12 +1941,16 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
 
   ssi->install_cmap_p = install_cmap_p;
 
-  if (new_v &&
-      ((ssi->current_visual != new_v) ||
-       (install_cmap_p != was_installed_p)))
+  if ((ssi->screen != xgwa.screen) ||
+      (new_v &&
+       (always_recreate_window_p ||
+        (ssi->current_visual != new_v) ||
+        (install_cmap_p != was_installed_p))))
     {
       Colormap old_c = ssi->cmap;
       Window old_w = ssi->screensaver_window;
+      if (! new_v) 
+        new_v = ssi->current_visual;
 
       if (p->verbose_p)
        {