From http://www.jwz.org/xscreensaver/xscreensaver-5.31.tar.gz
[xscreensaver] / driver / windows.c
index 909abe513d4817d4c351b8ffdcc81483ed98d26b..52d56691cb5171e262900438a76a1986213286de 100644 (file)
@@ -1,5 +1,5 @@
 /* windows.c --- turning the screen black; dealing with visuals, virtual roots.
- * xscreensaver, Copyright (c) 1991-1998 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
 #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 <X11/Xos.h>           /* for time() */
 #include <signal.h>            /* for the signal names */
+#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 */
 
+#ifdef HAVE_XF86VMODE
+# include <X11/extensions/xf86vmode.h>
+#endif /* HAVE_XF86VMODE */
 
-#ifdef HAVE_XHPDISABLERESET
-# include <X11/XHPlib.h>
-
- /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
-    or BadAccess errors occur.  (Ok for this to be global, since it
-    affects the whole machine, not just the current screen.) */
-  Bool hp_locked_p = False;
-
-#endif /* HAVE_XHPDISABLERESET */
-
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
 
 /* This file doesn't need the Xt headers, so stub these types out... */
 #undef XtPointer
 #include "fade.h"
 
 
-#ifdef HAVE_VT_LOCKSWITCH
-# include <fcntl.h>
-# include <sys/ioctl.h>
-# include <sys/vt.h>
-  static void lock_vt (saver_info *si, Bool lock_p);
-#endif /* HAVE_VT_LOCKSWITCH */
-
-
 extern int kill (pid_t, int);          /* signal() is in sys/signal.h... */
 
-Atom XA_VROOT, XA_XSETROOT_ID;
+Atom XA_VROOT, XA_XSETROOT_ID, XA_ESETROOT_PMAP_ID, XA_XROOTPMAP_ID;
 Atom XA_SCREENSAVER, XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
-Atom XA_SCREENSAVER_TIME;
+Atom XA_SCREENSAVER_STATUS;
 
 
 extern saver_info *global_si_kludge;   /* I hate C so much... */
 
-
-static void store_activate_time (saver_info *si, Bool use_last_p);
+static void maybe_transfer_grabs (saver_screen_info *ssi,
+                                  Window old_w, Window new_w, int new_screen);
 
 #define ALL_POINTER_EVENTS \
        (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
@@ -98,11 +95,11 @@ grab_string(int status)
 {
   switch (status)
     {
-    case GrabSuccess:     return "GrabSuccess";     break;
-    case AlreadyGrabbed:  return "AlreadyGrabbed";  break;
-    case GrabInvalidTime: return "GrabInvalidTime"; break;
-    case GrabNotViewable: return "GrabNotViewable"; break;
-    case GrabFrozen:      return "GrabFrozen";      break;
+    case GrabSuccess:     return "GrabSuccess";
+    case AlreadyGrabbed:  return "AlreadyGrabbed";
+    case GrabInvalidTime: return "GrabInvalidTime";
+    case GrabNotViewable: return "GrabNotViewable";
+    case GrabFrozen:      return "GrabFrozen";
     default:
       {
        static char foo[255];
@@ -113,7 +110,7 @@ grab_string(int status)
 }
 
 static int
-grab_kbd(saver_info *si, Window w)
+grab_kbd(saver_info *si, Window w, int screen_no)
 {
   saver_preferences *p = &si->prefs;
   int status = XGrabKeyboard (si->dpy, w, True,
@@ -122,28 +119,34 @@ grab_kbd(saver_info *si, Window w)
                              GrabModeSync, GrabModeAsync,
                              CurrentTime);
   if (status == GrabSuccess)
-    si->keyboard_grab_window = w;
+    {
+      si->keyboard_grab_window = w;
+      si->keyboard_grab_screen = screen_no;
+    }
 
   if (p->verbose_p)
-    fprintf(stderr, "%s: grabbing keyboard on 0x%x... %s.\n",
-           blurb(), (unsigned long) w, grab_string(status));
+    fprintf(stderr, "%s: %d: grabbing keyboard on 0x%lx... %s.\n",
+           blurb(), screen_no, (unsigned long) w, grab_string(status));
   return status;
 }
 
 
 static int
-grab_mouse (saver_info *si, Window w, Cursor cursor)
+grab_mouse (saver_info *si, Window w, Cursor cursor, int screen_no)
 {
   saver_preferences *p = &si->prefs;
   int status = XGrabPointer (si->dpy, w, True, ALL_POINTER_EVENTS,
-                            GrabModeAsync, GrabModeAsync, None,
+                            GrabModeAsync, GrabModeAsync, w,
                             cursor, CurrentTime);
   if (status == GrabSuccess)
-    si->mouse_grab_window = w;
+    {
+      si->mouse_grab_window = w;
+      si->mouse_grab_screen = screen_no;
+    }
 
   if (p->verbose_p)
-    fprintf(stderr, "%s: grabbing mouse on 0x%x... %s.\n",
-           blurb(), (unsigned long) w, grab_string(status));
+    fprintf(stderr, "%s: %d: grabbing mouse on 0x%lx... %s.\n",
+           blurb(), screen_no, (unsigned long) w, grab_string(status));
   return status;
 }
 
@@ -154,8 +157,9 @@ ungrab_kbd(saver_info *si)
   saver_preferences *p = &si->prefs;
   XUngrabKeyboard(si->dpy, CurrentTime);
   if (p->verbose_p)
-    fprintf(stderr, "%s: ungrabbing keyboard (was 0x%x).\n", blurb(),
-           (unsigned long) si->keyboard_grab_window);
+    fprintf(stderr, "%s: %d: ungrabbing keyboard (was 0x%lx).\n",
+            blurb(), si->keyboard_grab_screen,
+            (unsigned long) si->keyboard_grab_window);
   si->keyboard_grab_window = 0;
 }
 
@@ -166,47 +170,197 @@ ungrab_mouse(saver_info *si)
   saver_preferences *p = &si->prefs;
   XUngrabPointer(si->dpy, CurrentTime);
   if (p->verbose_p)
-    fprintf(stderr, "%s: ungrabbing mouse (was 0x%x).\n", blurb(),
-           (unsigned long) si->mouse_grab_window);
+    fprintf(stderr, "%s: %d: ungrabbing mouse (was 0x%lx).\n",
+            blurb(), si->mouse_grab_screen,
+            (unsigned long) si->mouse_grab_window);
   si->mouse_grab_window = 0;
 }
 
 
-static Bool
-grab_keyboard_and_mouse (saver_info *si, Window window, Cursor cursor)
+/* 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)
 {
-  Status mstatus, kstatus;
+  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)
+{
+  Status mstatus = 0, kstatus = 0;
+  int i;
+  int retries = 4;
+  Bool focus_fuckus = False;
+
+ AGAIN:
+
+  for (i = 0; i < retries; i++)
+    {
+      XSync (si->dpy, False);
+      kstatus = grab_kbd (si, window, screen_no);
+      if (kstatus == GrabSuccess)
+        break;
+
+      /* else, wait a second and try to grab again. */
+      sleep (1);
+    }
 
-  kstatus = grab_kbd (si, window);
   if (kstatus != GrabSuccess)
-    {  /* try again in a second */
+    {
+      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++)
+    {
+      XSync (si->dpy, False);
+      mstatus = grab_mouse (si, window, cursor, screen_no);
+      if (mstatus == GrabSuccess)
+        break;
+
+      /* else, wait a second and try to grab again. */
       sleep (1);
-      kstatus = grab_kbd (si, window);
-      if (kstatus != GrabSuccess)
-       fprintf (stderr, "%s: couldn't grab keyboard!  (%s)\n",
-                blurb(), grab_string(kstatus));
     }
 
-  mstatus = grab_mouse (si, window, cursor);
   if (mstatus != GrabSuccess)
-    {  /* try again in a second */
-      sleep (1);
-      mstatus = grab_mouse (si, window, cursor);
-      if (mstatus != GrabSuccess)
-       fprintf (stderr, "%s: couldn't grab pointer!  (%s)\n",
-                blurb(), grab_string(mstatus));
+    fprintf (stderr, "%s: couldn't grab pointer!  (%s)\n",
+             blurb(), grab_string(mstatus));
+
+
+  /* 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 (kstatus == GrabSuccess ||
-         mstatus == GrabSuccess);
+  return True;                 /* Grab is good, go ahead and blank.  */
 }
 
-static void
-ungrab_keyboard_and_mouse (saver_info *si)
+
+int
+move_mouse_grab (saver_info *si, Window to, Cursor cursor, int to_screen_no)
 {
-  ungrab_mouse (si);
-  ungrab_kbd (si);
+  Window old = si->mouse_grab_window;
+
+  if (old == 0)
+    return grab_mouse (si, to, cursor, to_screen_no);
+  else
+    {
+      saver_preferences *p = &si->prefs;
+      int status;
+
+      XSync (si->dpy, False);
+      XGrabServer (si->dpy);                   /* ############ DANGER! */
+      XSync (si->dpy, False);
+
+      if (p->verbose_p)
+        fprintf(stderr, "%s: grabbing server...\n", blurb());
+
+      ungrab_mouse (si);
+      status = grab_mouse (si, to, cursor, to_screen_no);
+
+      if (status != GrabSuccess)   /* Augh! */
+        {
+          sleep (1);               /* Note dramatic evil of sleeping
+                                      with server grabbed. */
+          XSync (si->dpy, False);
+          status = grab_mouse (si, to, cursor, to_screen_no);
+        }
+
+      if (status != GrabSuccess)   /* Augh!  Try to get the old one back... */
+        grab_mouse (si, old, cursor, to_screen_no);
+
+      XUngrabServer (si->dpy);
+      XSync (si->dpy, False);                  /* ###### (danger over) */
+
+      if (p->verbose_p)
+        fprintf(stderr, "%s: ungrabbing server.\n", blurb());
+
+      return status;
+    }
 }
 
 
@@ -233,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);
@@ -304,13 +474,17 @@ remove_vroot_property (Display *dpy, Window win)
 }
 
 
+static Bool safe_XKillClient (Display *dpy, XID id);
+
 static void
-kill_xsetroot_data (Display *dpy, Window window, Bool verbose_p)
+kill_xsetroot_data_1 (Display *dpy, Window window,
+                      Atom prop, const char *atom_name,
+                      Bool verbose_p)
 {
   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
@@ -323,31 +497,47 @@ kill_xsetroot_data (Display *dpy, Window window, Bool verbose_p)
      delete of the _XSETROOT_ID property, and if it held a pixmap, then we
      cause the RetainPermanent resources of the client which created it
      (and which no longer exists) to be freed.
+
+     Update: it seems that Gnome and KDE do this same trick, but with the
+     properties "ESETROOT_PMAP_ID" and/or "_XROOTPMAP_ID" instead of
+     "_XSETROOT_ID".  So, we'll kill those too.
    */
-  if (XGetWindowProperty (dpy, window, XA_XSETROOT_ID, 0, 1,
+  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 xsetroot data (0x%lX).\n",
-                    blurb(), *dataP);
-         XKillClient (dpy, *dataP);
+           fprintf (stderr, "%s: destroying %s data (0x%lX).\n",
+                    blurb(), atom_name, *pixP);
+         safe_XKillClient (dpy, *pixP);
        }
       else
-       fprintf (stderr, "%s: deleted unrecognised _XSETROOT_ID property: \n\
-       %lu, %lu; type: %lu, format: %d, nitems: %lu, bytesafter %ld\n",
-                blurb(), (unsigned long) dataP, (dataP ? *dataP : 0), type,
+       fprintf (stderr,
+                 "%s: deleted unrecognised %s property: \n"
+                 "\t%lu, %lu; type: %lu, format: %d, "
+                 "nitems: %lu, bytesafter %ld\n",
+                blurb(), atom_name,
+                 (unsigned long) pixP, (pixP ? *pixP : 0), type,
                 format, nitems, bytesafter);
     }
 }
 
 
-static void handle_signals (saver_info *si, Bool on_p);
+static void
+kill_xsetroot_data (Display *dpy, Window w, Bool verbose_p)
+{
+  kill_xsetroot_data_1 (dpy, w, XA_XSETROOT_ID, "_XSETROOT_ID", verbose_p);
+  kill_xsetroot_data_1 (dpy, w, XA_ESETROOT_PMAP_ID, "ESETROOT_PMAP_ID",
+                        verbose_p);
+  kill_xsetroot_data_1 (dpy, w, XA_XROOTPMAP_ID, "_XROOTPMAP_ID", verbose_p);
+}
+
 
 static void
 save_real_vroot (saver_screen_info *ssi)
@@ -383,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 ();
@@ -402,6 +606,8 @@ save_real_vroot (saver_screen_info *ssi)
        }
       ssi->real_vroot = kids [i];
       ssi->real_vroot_value = *vrootP;
+    SKIP:
+      ;
     }
 
   XSync (dpy, False);
@@ -410,7 +616,6 @@ save_real_vroot (saver_screen_info *ssi)
 
   if (ssi->real_vroot)
     {
-      handle_signals (si, True);
       remove_vroot_property (si->dpy, ssi->real_vroot);
       XSync (dpy, False);
     }
@@ -420,7 +625,7 @@ save_real_vroot (saver_screen_info *ssi)
 
 
 static Bool
-restore_real_vroot_2 (saver_screen_info *ssi)
+restore_real_vroot_1 (saver_screen_info *ssi)
 {
   saver_info *si = ssi->global;
   saver_preferences *p = &si->prefs;
@@ -428,7 +633,8 @@ restore_real_vroot_2 (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);
@@ -443,27 +649,20 @@ restore_real_vroot_2 (saver_screen_info *ssi)
   return False;
 }
 
-static Bool
-restore_real_vroot_1 (saver_info *si)
+Bool
+restore_real_vroot (saver_info *si)
 {
   int i;
   Bool did_any = False;
   for (i = 0; i < si->nscreens; i++)
     {
       saver_screen_info *ssi = &si->screens[i];
-      if (restore_real_vroot_2 (ssi))
+      if (restore_real_vroot_1 (ssi))
        did_any = True;
     }
   return did_any;
 }
 
-void
-restore_real_vroot (saver_info *si)
-{
-  if (restore_real_vroot_1 (si))
-    handle_signals (si, False);
-}
-
 \f
 /* Signal hackery to ensure that the vroot doesn't get left in an 
    inconsistent state
@@ -550,65 +749,109 @@ restore_real_vroot_handler (int sig)
   saver_info *si = global_si_kludge;   /* I hate C so much... */
 
   signal (sig, SIG_DFL);
-  if (restore_real_vroot_1 (si))
+  if (restore_real_vroot (si))
     fprintf (real_stderr, "\n%s: %s intercepted, vroot restored.\n",
             blurb(), signal_name(sig));
   kill (getpid (), sig);
 }
 
 static void
-catch_signal (saver_info *si, int sig, Bool on_p)
+catch_signal (saver_info *si, int sig, RETSIGTYPE (*handler) (int))
 {
-  if (! on_p)
-    signal (sig, SIG_DFL);
-  else
+# ifdef HAVE_SIGACTION
+
+  struct sigaction a;
+  a.sa_handler = handler;
+  sigemptyset (&a.sa_mask);
+  a.sa_flags = 0;
+
+  /* On Linux 2.4.9 (at least) we need to tell the kernel to not mask delivery
+     of this signal from inside its handler, or else when we execvp() the
+     process again, it starts up with SIGHUP blocked, meaning that killing
+     it with -HUP only works *once*.  You'd think that execvp() would reset
+     all the signal masks, but it doesn't.
+   */
+#  if defined(SA_NOMASK)
+  a.sa_flags |= SA_NOMASK;
+#  elif defined(SA_NODEFER)
+  a.sa_flags |= SA_NODEFER;
+#  endif
+
+  if (sigaction (sig, &a, 0) < 0)
+# else  /* !HAVE_SIGACTION */
+  if (((long) signal (sig, handler)) == -1L)
+# endif /* !HAVE_SIGACTION */
     {
-      if (((long) signal (sig, restore_real_vroot_handler)) == -1L)
-       {
-         char buf [255];
-         sprintf (buf, "%s: couldn't catch %s", blurb(), signal_name(sig));
-         perror (buf);
-         saver_exit (si, 1, 0);
-       }
+      char buf [255];
+      sprintf (buf, "%s: couldn't catch %s", blurb(), signal_name(sig));
+      perror (buf);
+      saver_exit (si, 1, 0);
     }
 }
 
-static void
-handle_signals (saver_info *si, Bool on_p)
+static RETSIGTYPE saver_sighup_handler (int sig);
+
+void
+handle_signals (saver_info *si)
 {
-#if 0
-  if (on_p) fprintf (stderr, "handling signals\n");
-  else fprintf (stderr, "unhandling signals\n");
+  catch_signal (si, SIGHUP, saver_sighup_handler);
+
+  catch_signal (si, SIGINT,  restore_real_vroot_handler);
+  catch_signal (si, SIGQUIT, restore_real_vroot_handler);
+  catch_signal (si, SIGILL,  restore_real_vroot_handler);
+  catch_signal (si, SIGTRAP, restore_real_vroot_handler);
+#ifdef SIGIOT
+  catch_signal (si, SIGIOT,  restore_real_vroot_handler);
 #endif
-
-  catch_signal (si, SIGHUP,  on_p);
-  catch_signal (si, SIGINT,  on_p);
-  catch_signal (si, SIGQUIT, on_p);
-  catch_signal (si, SIGILL,  on_p);
-  catch_signal (si, SIGTRAP, on_p);
-  catch_signal (si, SIGIOT,  on_p);
-  catch_signal (si, SIGABRT, on_p);
+  catch_signal (si, SIGABRT, restore_real_vroot_handler);
 #ifdef SIGEMT
-  catch_signal (si, SIGEMT,  on_p);
+  catch_signal (si, SIGEMT,  restore_real_vroot_handler);
 #endif
-  catch_signal (si, SIGFPE,  on_p);
-  catch_signal (si, SIGBUS,  on_p);
-  catch_signal (si, SIGSEGV, on_p);
+  catch_signal (si, SIGFPE,  restore_real_vroot_handler);
+  catch_signal (si, SIGBUS,  restore_real_vroot_handler);
+  catch_signal (si, SIGSEGV, restore_real_vroot_handler);
 #ifdef SIGSYS
-  catch_signal (si, SIGSYS,  on_p);
+  catch_signal (si, SIGSYS,  restore_real_vroot_handler);
 #endif
-  catch_signal (si, SIGTERM, on_p);
+  catch_signal (si, SIGTERM, restore_real_vroot_handler);
 #ifdef SIGXCPU
-  catch_signal (si, SIGXCPU, on_p);
+  catch_signal (si, SIGXCPU, restore_real_vroot_handler);
 #endif
 #ifdef SIGXFSZ
-  catch_signal (si, SIGXFSZ, on_p);
+  catch_signal (si, SIGXFSZ, restore_real_vroot_handler);
 #endif
 #ifdef SIGDANGER
-  catch_signal (si, SIGDANGER, on_p);
+  catch_signal (si, SIGDANGER, restore_real_vroot_handler);
 #endif
 }
 
+
+static RETSIGTYPE
+saver_sighup_handler (int sig)
+{
+  saver_info *si = global_si_kludge;   /* I hate C so much... */
+
+  /* Re-establish SIGHUP handler */
+  catch_signal (si, SIGHUP, saver_sighup_handler);
+
+  fprintf (stderr, "%s: %s received: restarting...\n",
+           blurb(), signal_name(sig));
+
+  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 ();
+}
+
+
+
 void
 saver_exit (saver_info *si, int status, const char *dump_core_reason)
 {
@@ -622,13 +865,12 @@ saver_exit (saver_info *si, int status, const char *dump_core_reason)
 
   exiting = True;
   
-  vrs = restore_real_vroot_1 (si);
+  vrs = restore_real_vroot (si);
   emergency_kill_subproc (si);
+  shutdown_stderr (si);
 
-  if (vrs && (p->verbose_p || status != 0))
-    fprintf (real_stderr, "%s: vroot restored, exiting.\n", blurb());
-  else if (p->verbose_p)
-    fprintf (real_stderr, "%s: no vroot to restore; exiting.\n", blurb());
+  if (p->verbose_p && vrs)
+    fprintf (real_stderr, "%s: old vroot restored.\n", blurb());
 
   fflush(real_stdout);
 
@@ -656,7 +898,7 @@ saver_exit (saver_info *si, int status, const char *dump_core_reason)
       if (bugp)
        fprintf(real_stderr,
                "%s: see http://www.jwz.org/xscreensaver/bugs.html\n"
-               "\t\tfor bug reporting information.\n\n",
+               "\t\t\tfor bug reporting information.\n\n",
                blurb());
 
 # if defined(HAVE_GETCWD)
@@ -702,7 +944,7 @@ store_saver_id (saver_screen_info *ssi)
   struct passwd *p = getpwuid (getuid ());
   const char *name, *host;
   char *id;
-  
+
   /* First store the name and class on the window.
    */
   class_hints.res_name = progname;
@@ -757,6 +999,157 @@ store_saver_id (saver_screen_info *ssi)
 }
 
 
+void
+store_saver_status (saver_info *si)
+{
+  PROP32 *status;
+  int size = si->nscreens + 2;
+  int i;
+
+  status = (PROP32 *) calloc (size, sizeof(PROP32));
+
+  status[0] = (PROP32) (si->screen_blanked_p
+                        ? (si->locked_p ? XA_LOCK : XA_BLANK)
+                        : 0);
+  status[1] = (PROP32) si->blank_time;
+
+  for (i = 0; i < si->nscreens; i++)
+    {
+      saver_screen_info *ssi = &si->screens[i];
+      status [2 + i] = ssi->current_hack + 1;
+    }
+
+  XChangeProperty (si->dpy,
+                   RootWindow (si->dpy, 0),  /* always screen #0 */
+                   XA_SCREENSAVER_STATUS,
+                   XA_INTEGER, 32, PropModeReplace,
+                   (unsigned char *) status, size);
+  free (status);
+}
+
+
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+  error_handler_hit_p = True;
+  return 0;
+}
+
+
+/* Returns True if successful, False if an X error occurred.
+   We need this because other programs might have done things to
+   our window that will cause XChangeWindowAttributes() to fail:
+   if that happens, we give up, destroy the window, and re-create
+   it.
+ */
+static Bool
+safe_XChangeWindowAttributes (Display *dpy, Window window,
+                              unsigned long mask,
+                              XSetWindowAttributes *attrs)
+{
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  XChangeWindowAttributes (dpy, window, mask, attrs);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (!error_handler_hit_p);
+}
+
+
+/* This might not be necessary, but just in case. */
+static Bool
+safe_XConfigureWindow (Display *dpy, Window window,
+                       unsigned long mask, XWindowChanges *changes)
+{
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  XConfigureWindow (dpy, window, mask, changes);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (!error_handler_hit_p);
+}
+
+/* This might not be necessary, but just in case. */
+static Bool
+safe_XDestroyWindow (Display *dpy, Window window)
+{
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  XDestroyWindow (dpy, window);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (!error_handler_hit_p);
+}
+
+
+static Bool
+safe_XKillClient (Display *dpy, XID id)
+{
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  XKillClient (dpy, id);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (!error_handler_hit_p);
+}
+
+
+#ifdef HAVE_XF86VMODE
+Bool
+safe_XF86VidModeGetViewPort (Display *dpy, int screen, int *xP, int *yP)
+{
+  Bool result;
+  XErrorHandler old_handler;
+  XSync (dpy, False);
+  error_handler_hit_p = False;
+  old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+  result = XF86VidModeGetViewPort (dpy, screen, xP, yP);
+
+  XSync (dpy, False);
+  XSetErrorHandler (old_handler);
+  XSync (dpy, False);
+
+  return (error_handler_hit_p
+          ? False
+          : result);
+}
+
+/* There is no "safe_XF86VidModeGetModeLine" because it fails with an
+   untrappable I/O error instead of an X error -- so one must call
+   safe_XF86VidModeGetViewPort first, and assume that both have the
+   same error condition.  Thank you XFree, may I have another.
+ */
+
+#endif /* HAVE_XF86VMODE */
+
+
 static void
 initialize_screensaver_window_1 (saver_screen_info *ssi)
 {
@@ -772,9 +1165,8 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
   XColor black;
   XSetWindowAttributes attrs;
   unsigned long attrmask;
-  int width = WidthOfScreen (ssi->screen);
-  int height = HeightOfScreen (ssi->screen);
   static Bool printed_visual_info = False;  /* only print the message once. */
+  Window horked_window = 0;
 
   black.red = black.green = black.blue = 0;
 
@@ -828,13 +1220,11 @@ 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))
     {
-      fprintf (stderr, "%s: using default visual ", blurb());
+      fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
       describe_visual (stderr, ssi->screen, ssi->current_visual,
                       install_cmap_p);
     }
@@ -905,32 +1295,49 @@ initialize_screensaver_window_1 (saver_screen_info *ssi)
     {
       XWindowChanges changes;
       unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
-      changes.x = 0;
-      changes.y = 0;
-      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;
 
-      XConfigureWindow (si->dpy, ssi->screensaver_window,
-                       changesmask, &changes);
-      XChangeWindowAttributes (si->dpy, ssi->screensaver_window,
-                              attrmask, &attrs);
+      if (! (safe_XConfigureWindow (si->dpy, ssi->screensaver_window,
+                                  changesmask, &changes) &&
+             safe_XChangeWindowAttributes (si->dpy, ssi->screensaver_window,
+                                           attrmask, &attrs)))
+        {
+          horked_window = ssi->screensaver_window;
+          ssi->screensaver_window = 0;
+        }
     }
-  else
+
+  if (!ssi->screensaver_window)
     {
       ssi->screensaver_window =
-       XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen), 0, 0,
-                      width, height, 0, ssi->current_depth, InputOutput,
+       XCreateWindow (si->dpy, RootWindowOfScreen (ssi->screen),
+                       ssi->x, ssi->y, ssi->width, ssi->height,
+                       0, ssi->current_depth, InputOutput,
                       ssi->current_visual, attrmask, &attrs);
       reset_stderr (ssi);
-      store_activate_time(si, True);
+
+      if (horked_window)
+        {
+          fprintf (stderr,
+            "%s: someone horked our saver window (0x%lx)!  Recreating it...\n",
+                   blurb(), (unsigned long) horked_window);
+          maybe_transfer_grabs (ssi, horked_window, ssi->screensaver_window,
+                                ssi->number);
+          safe_XDestroyWindow (si->dpy, horked_window);
+          horked_window = 0;
+        }
+
       if (p->verbose_p)
-       fprintf (stderr, "%s: saver window is 0x%lx.\n",
-                blurb(), (unsigned long) ssi->screensaver_window);
+       fprintf (stderr, "%s: %d: saver window is 0x%lx.\n",
+                 blurb(), ssi->number,
+                 (unsigned long) ssi->screensaver_window);
     }
 
-
-  store_saver_id (ssi);
+  store_saver_id (ssi);       /* store window name and IDs */
 
   if (!ssi->cursor)
     {
@@ -962,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)
@@ -972,10 +1486,15 @@ raise_window (saver_info *si,
   if (si->demoing_p)
     inhibit_fade = True;
 
-  initialize_screensaver_window (si);
+  if (si->emergency_lock_p)
+    inhibit_fade = True;
+
+  if (!dont_clear)
+    initialize_screensaver_window (si);
+
   reset_watchdog_timer (si, True);
 
-  if (p->fade_p && p->fading_possible_p && !inhibit_fade)
+  if (p->fade_p && si->fading_possible_p && !inhibit_fade)
     {
       Window *current_windows = (Window *)
        calloc(sizeof(Window), si->nscreens);
@@ -1013,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);
@@ -1062,30 +1582,77 @@ raise_window (saver_info *si,
     }
 }
 
+
+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;
+
+  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)
+        {
+          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;
+}
+
+
 Bool
 blank_screen (saver_info *si)
 {
   int i;
   Bool ok;
+  Window w;
+  int mscreen;
 
   /* Note: we do our grabs on the root window, not on the screensaver window.
      If we grabbed on the saver window, then the demo mode and lock dialog
      boxes wouldn't get any events.
+
+     By "the root window", we mean "the root window that contains the mouse."
+     We use to always grab the mouse on screen 0, but that has the effect of
+     moving the mouse to screen 0 from whichever screen it was on, on
+     multi-head systems.
    */
-  ok = grab_keyboard_and_mouse (si,
-                                /*si->screens[0].screensaver_window,*/
-                                RootWindowOfScreen(si->screens[0].screen),
-                                (si->demoing_p
-                                 ? 0
-                                 : si->screens[0].cursor));
+  mscreen = mouse_screen (si);
+  w = RootWindowOfScreen(si->screens[mscreen].screen);
+  ok = grab_keyboard_and_mouse (si, w,
+                                (si->demoing_p ? 0 : si->screens[0].cursor),
+                                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;
@@ -1093,43 +1660,44 @@ 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);
-    }
-  store_activate_time (si, si->screen_blanked_p);
-  raise_window (si, False, False, False);
 
-#ifdef HAVE_XHPDISABLERESET
-  if (si->locked_p && !hp_locked_p)
-    {
-      XHPDisableReset (si->dpy);       /* turn off C-Sh-Reset */
-      hp_locked_p = True;
+#ifdef HAVE_XF86VMODE
+      {
+        int ev, er;
+        if (!XF86VidModeQueryExtension (si->dpy, &ev, &er) ||
+            !safe_XF86VidModeGetViewPort (si->dpy, i,
+                                          &ssi->blank_vp_x,
+                                          &ssi->blank_vp_y))
+          ssi->blank_vp_x = ssi->blank_vp_y = -1;
+      }
+#endif /* HAVE_XF86VMODE */
     }
-#endif
 
-#ifdef HAVE_VT_LOCKSWITCH
-  if (si->locked_p)
-      lock_vt (si, True);              /* turn off C-Alt-Fn */
-#endif
+  raise_window (si, False, False, False);
 
   si->screen_blanked_p = True;
+  si->blank_time = time ((time_t *) 0);
+  si->last_wall_clock_time = 0;
+
+  store_saver_status (si);  /* store blank time */
 
   return True;
 }
 
+
 void
 unblank_screen (saver_info *si)
 {
   saver_preferences *p = &si->prefs;
-  Bool unfade_p = (p->fading_possible_p && p->unfade_p);
+  Bool unfade_p = (si->fading_possible_p && p->unfade_p);
   int i;
 
-  monitor_power_on (si);
-
-  store_activate_time (si, True);
+  monitor_power_on (si, True);
   reset_watchdog_timer (si, False);
 
   if (si->demoing_p)
@@ -1168,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);
 
@@ -1197,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?)
    */
@@ -1223,23 +1791,10 @@ unblank_screen (saver_info *si)
       kill_xsetroot_data (si->dpy, ssi->screensaver_window, p->verbose_p);
     }
 
-  store_activate_time(si, False);  /* store unblank time */
-
+  store_saver_status (si);  /* store unblank time */
   ungrab_keyboard_and_mouse (si);
   restore_real_vroot (si);
 
-#ifdef HAVE_XHPDISABLERESET
-  if (hp_locked_p)
-    {
-      XHPEnableReset (si->dpy);        /* turn C-Sh-Reset back on */
-      hp_locked_p = False;
-    }
-#endif
-
-#ifdef HAVE_VT_LOCKSWITCH
-  lock_vt (si, False);         /* turn C-Alt-Fn back on */
-#endif
-
   /* Unmap the windows a second time, dammit -- just to avoid a race
      with the screen-grabbing hacks.  (I'm not sure if this is really
      necessary; I'm stabbing in the dark now.)
@@ -1248,52 +1803,130 @@ unblank_screen (saver_info *si)
     XUnmapWindow (si->dpy, si->screens[i].screensaver_window);
 
   si->screen_blanked_p = False;
+  si->blank_time = time ((time_t *) 0);
+  si->last_wall_clock_time = 0;
+
+  store_saver_status (si);  /* store unblank time */
 }
 
 
+/* Transfer any grabs from the old window to the new.
+   Actually I think none of this is necessary, since we always
+   hold our grabs on the root window, but I wrote this before
+   re-discovering that...
+ */
 static void
-store_activate_time (saver_info *si, Bool use_last_p)
+maybe_transfer_grabs (saver_screen_info *ssi,
+                      Window old_w, Window new_w,
+                      int new_screen_no)
 {
-  static time_t last_time = 0;
-  time_t now = ((use_last_p && last_time) ? last_time : time ((time_t) 0));
-  CARD32 now32 = (CARD32) now;
-  int i;
-  last_time = now;
+  saver_info *si = ssi->global;
 
-  for (i = 0; i < si->nscreens; i++)
+  /* If the old window held our mouse grab, transfer the grab to the new
+     window.  (Grab the server while so doing, to avoid a race condition.)
+   */
+  if (old_w == si->mouse_grab_window)
     {
-      saver_screen_info *ssi = &si->screens[i];
-      if (!ssi->screensaver_window) continue;
-      XChangeProperty (si->dpy, ssi->screensaver_window, XA_SCREENSAVER_TIME,
-                      XA_INTEGER, 32, PropModeReplace,
-                      (unsigned char *) &now32, 1);
+      XGrabServer (si->dpy);           /* ############ DANGER! */
+      ungrab_mouse (si);
+      grab_mouse (si, ssi->screensaver_window,
+                  (si->demoing_p ? 0 : ssi->cursor),
+                  new_screen_no);
+      XUngrabServer (si->dpy);
+      XSync (si->dpy, False);          /* ###### (danger over) */
+    }
+
+  /* If the old window held our keyboard grab, transfer the grab to the new
+     window.  (Grab the server while so doing, to avoid a race condition.)
+   */
+  if (old_w == si->keyboard_grab_window)
+    {
+      XGrabServer (si->dpy);           /* ############ DANGER! */
+      ungrab_kbd(si);
+      grab_kbd(si, ssi->screensaver_window, ssi->number);
+      XUngrabServer (si->dpy);
+      XSync (si->dpy, False);          /* ###### (danger over) */
     }
 }
 
 
+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;
   Bool was_installed_p = (ssi->cmap != DefaultColormapOfScreen(ssi->screen));
-  Visual *new_v;
+  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"))
+      if (!strcmp(visual_name, "default-i") ||
+          !strcmp(visual_name, "Default-i") ||
+          !strcmp(visual_name, "Default-I")
+          )
        {
          visual_name = "default";
          install_cmap_p = True;
        }
-      else if (!strcmp(visual_name, "default-n"))
+      else if (!strcmp(visual_name, "default-n") ||
+               !strcmp(visual_name, "Default-n") ||
+               !strcmp(visual_name, "Default-N"))
        {
          visual_name = "default";
          install_cmap_p = False;
        }
-      new_v = get_visual (ssi->screen, visual_name, True, False);
+      else if (!strcmp(visual_name, "gl") ||
+               !strcmp(visual_name, "Gl") ||
+               !strcmp(visual_name, "GL"))
+        {
+          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);
+        }
+
+      if (!new_v)
+        new_v = get_visual (ssi->screen, visual_name, True, False);
     }
   else
     {
@@ -1308,19 +1941,23 @@ 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)
        {
-         fprintf (stderr, "%s: switching to visual ", blurb());
+         fprintf (stderr, "%s: %d: visual ", blurb(), ssi->number);
          describe_visual (stderr, ssi->screen, new_v, install_cmap_p);
 #if 0
-         fprintf (stderr, "%s:                from ", blurb());
+         fprintf (stderr, "%s:                  from ", blurb());
          describe_visual (stderr, ssi->screen, ssi->current_visual,
                           was_installed_p);
 #endif
@@ -1343,53 +1980,16 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
       raise_window (si, True, True, False);
       store_vroot_property (si->dpy,
                            ssi->screensaver_window, ssi->screensaver_window);
-      store_activate_time (si, True);
-
-
-
-      /* Transfer the grabs from the old window to the new.
-        Actually I think none of this is necessary, since we always
-        hold our grabs on the root window, but I wrote this before
-        re-discovering that...
-       */
-
-
-      /* If we're destroying the window that holds our mouse grab,
-        transfer the grab to the new window.  (Grab the server while
-        so doing, to avoid a race condition.)
-       */
-      if (old_w == si->mouse_grab_window)
-       {
-         XGrabServer (si->dpy);                /* ############ DANGER! */
-         ungrab_mouse (si);
-         grab_mouse (si, ssi->screensaver_window,
-                     (si->demoing_p
-                      ? 0
-                      : ssi->cursor));
-         XUngrabServer (si->dpy);
-         XSync (si->dpy, False);               /* ###### (danger over) */
-       }
-
-      /* If we're destroying the window that holds our keyboard grab,
-        transfer the grab to the new window.  (Grab the server while
-        so doing, to avoid a race condition.)
-       */
-      if (old_w == si->keyboard_grab_window)
-       {
-         XGrabServer (si->dpy);                /* ############ DANGER! */
-         ungrab_kbd(si);
-         grab_kbd(si, ssi->screensaver_window);
-         XUngrabServer (si->dpy);
-         XSync (si->dpy, False);               /* ###### (danger over) */
-       }
 
-      /* Now we can destroy this window without horking our grabs. */
+      /* Transfer any grabs from the old window to the new. */
+      maybe_transfer_grabs (ssi, old_w, ssi->screensaver_window, ssi->number);
 
+      /* Now we can destroy the old window without horking our grabs. */
       XDestroyWindow (si->dpy, old_w);
 
       if (p->verbose_p)
-       fprintf (stderr, "%s: destroyed old saver window 0x%lx.\n",
-                blurb(), (unsigned long) old_w);
+       fprintf (stderr, "%s: %d: destroyed old saver window 0x%lx.\n",
+                blurb(), ssi->number, (unsigned long) old_w);
 
       if (old_c &&
          old_c != DefaultColormapOfScreen (ssi->screen) &&
@@ -1399,56 +1999,3 @@ select_visual (saver_screen_info *ssi, const char *visual_name)
 
   return got_it;
 }
-
-\f
-/* VT locking */
-
-#ifdef HAVE_VT_LOCKSWITCH
-static void
-lock_vt (saver_info *si, Bool lock_p)
-{
-  saver_preferences *p = &si->prefs;
-  static Bool locked_p = False;
-  const char *dev_console = "/dev/console";
-  int fd;
-
-  if (lock_p == locked_p)
-    return;
-
-  if (lock_p && !p->lock_vt_p)
-    return;
-
-  fd = open (dev_console, O_RDWR);
-  if (fd < 0)
-    {
-      char buf [255];
-      sprintf (buf, "%s: couldn't %s VTs: %s", blurb(),
-              (lock_p ? "lock" : "unlock"),
-              dev_console);
-#if 0 /* #### doesn't work yet, so don't bother complaining */
-      perror (buf);
-#endif
-      return;
-    }
-
-  if (ioctl (fd, (lock_p ? VT_LOCKSWITCH : VT_UNLOCKSWITCH)) == 0)
-    {
-      locked_p = lock_p;
-
-      if (p->verbose_p)
-       fprintf (stderr, "%s: %s VTs\n", blurb(),
-                (lock_p ? "locked" : "unlocked"));
-    }
-  else
-    {
-      char buf [255];
-      sprintf (buf, "%s: couldn't %s VTs: ioctl", blurb(),
-              (lock_p ? "lock" : "unlock"));
-#if 0 /* #### doesn't work yet, so don't bother complaining */
-      perror (buf);
-#endif
-    }
-
-  close (fd);
-}
-#endif /* HAVE_VT_LOCKSWITCH */