http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.06.tar.gz
[xscreensaver] / driver / screens.c
diff --git a/driver/screens.c b/driver/screens.c
new file mode 100644 (file)
index 0000000..cef66b7
--- /dev/null
@@ -0,0 +1,894 @@
+/* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports.
+ * xscreensaver, Copyright (c) 1991-2008 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 above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/*   There are a bunch of different mechanisms for multiple monitors
+ *   available in X.  XScreenSaver needs to care about this for two
+ *   reasons: first, to ensure that all visible areas go black; and
+ *   second, so that the windows of screen savers exactly fill the
+ *   glass of each monitor (instead of one saver spanning multiple
+ *   monitors, or a monitor displaying only a sub-rectangle of the
+ *   screen saver.)
+ *
+ *   1) Multi-screen:
+ *
+ *      This is the original way.  Each monitor gets its own display
+ *      number.  :0.0 is the first one, :0.1 is the next, etc.  The
+ *      value of $DISPLAY determines which screen windows open on by
+ *      default.  A single app can open windows on multiple screens
+ *      with the same display connection, but windows cannot be moved
+ *      from one screen to another.  The mouse can be moved from one
+ *      screen to another, though.  Screens may be different depths
+ *      (e.g., one can be TrueColor and one can be PseudoColor.)
+ *      Screens cannot be resized or moved without restarting X.
+ *
+ *      Everyone hates this way of doing things because of the
+ *      inability to move a window from one screen to another without
+ *      restarting the application.
+ *
+ *   2) Xinerama:
+ *
+ *      There is a single giant root window that spans all the
+ *      monitors.  All monitors are the same depth, and windows can be
+ *      moved around.  Applications can learn which rectangles are
+ *      actually visible on monitors by querying the Xinerama server
+ *      extension.  (If you don't do that, you end up with dialog
+ *      boxes that try to appear in the middle of the screen actually
+ *      spanning the gap between two monitors.)
+ *
+ *      Xinerama doesn't work with DRI, which means that if you use
+ *      it, you lose hardware acceleration on OpenGL programs.  Also,
+ *      screens can't be resized or moved without restarting X.
+ *
+ *   3) Vidmode Viewports:
+ *
+ *      With this extension, the root window can be bigger than the
+ *      monitor.  Moving the mouse near the edges of the screen
+ *      scrolls around, like a pan-and-scan movie.  There can also be
+ *      a hot key for changing the monitor's resolution (zooming
+ *      in/out).
+ *
+ *      Trying to combine this with Xinerama crashes the server, so
+ *      you can only use this if you have only a single screen, or are
+ *      in old-multi-screen mode.
+ *
+ *      Also, half the time it doesn't work at all: it tends to lie
+ *      about the size of the rectangle in use.
+ *
+ *   4) RANDR 1.0:
+ *
+ *      The first version of the "Resize and Rotate" extension let you
+ *      change the resolution of a screen on the fly.  The root window
+ *      would actually resize.  However, it was also incompatible with
+ *      Xinerama (did it crash, or just do nothing? I can't remember)
+ *      so you needed to be in single-screen or old multi-screen mode.
+ *      I believe RANDR could co-exist with Vidmode Viewports, but I'm
+ *      not sure.
+ *
+ *   5) RANDR 1.2:
+ *
+ *      Finally, RANDR added the functionality of Xinerama, plus some.
+ *      Each X screen (in the sense of #1, "multi-screen") can have a
+ *      number of sub-rectangles that are displayed on monitors, and
+ *      each of those sub-rectangles can be displayed on more than one
+ *      monitor.  So it's possible (I think) to have a hybrid of
+ *      multi-screen and Xinerama (e.g., to have two monitors running
+ *      in one depth, and three monitors running in another?)
+ *      Typically though, there will be a single X screen, with
+ *      Xinerama-like division of that large root window onto multiple
+ *      monitors.  Also everything's dynamic: monitors can be added,
+ *      removed, and resized at runtime.
+ *
+ *      I believe that as of RANDR 1.2, the Xinerama extension still
+ *      exists but only as a compatiblity layer: it's actually
+ *      returning data from the RANDR extension.
+ *
+ *      Though RANDR 1.2 allows the same image to be cloned onto more
+ *      than one monitor, and also allows one monitor to show a
+ *      subsection of something on another monitor (e.g., the
+ *      rectangles can be enclosed or overlap).  Since there's no way
+ *      to put seperate savers on those duplicated-or-overlapping
+ *      monitors, xscreensaver just ignores them (which allows them to
+ *      display duplicates or overlaps).
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <X11/Xlib.h>
+
+#ifdef HAVE_RANDR
+# include <X11/extensions/Xrandr.h>
+#endif /* HAVE_RANDR */
+
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
+#ifdef HAVE_XF86VMODE
+# include <X11/extensions/xf86vmode.h>
+#endif /* HAVE_XF86VMODE */
+
+/* This file doesn't need the Xt headers, so stub these types out... */
+#undef XtPointer
+#define XtAppContext void*
+#define XrmDatabase  void*
+#define XtIntervalId void*
+#define XtPointer    void*
+#define Widget       void*
+
+#include "xscreensaver.h"
+#include "visual.h"
+
+
+typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP, 
+               S_OFFSCREEN, S_DISABLED } monitor_sanity;
+
+/* 'typedef monitor' is in types.h */
+struct _monitor {
+  int id;
+  char *desc;
+  Screen *screen;
+  int x, y, width, height;
+  monitor_sanity sanity;       /* I'm not crazy you're the one who's crazy */
+  int enemy;                   /* which monitor it overlaps or duplicates */
+};
+
+static void
+free_monitors (monitor **monitors)
+{
+  monitor **m2 = monitors;
+  if (! monitors) return;
+  while (*m2) 
+    {
+      if ((*m2)->desc) free ((*m2)->desc);
+      free (*m2);
+      m2++;
+    }
+  free (monitors);
+}
+
+
+#ifdef HAVE_XINERAMA
+
+static monitor **
+xinerama_scan_monitors (Display *dpy)
+{
+  Screen *screen = DefaultScreenOfDisplay (dpy);
+  int event, error, nscreens, i;
+  XineramaScreenInfo *xsi;
+  monitor **monitors;
+
+  if (! XineramaQueryExtension (dpy, &event, &error))
+    return 0;
+
+  if (! XineramaIsActive (dpy)) 
+    return 0;
+
+  xsi = XineramaQueryScreens (dpy, &nscreens);
+  if (!xsi) return 0;
+
+  monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+  if (!monitors) return 0;
+
+  for (i = 0; i < nscreens; i++)
+    {
+      monitor *m = (monitor *) calloc (1, sizeof (monitor));
+      monitors[i] = m;
+      m->id       = i;
+      m->screen   = screen;
+      m->x        = xsi[i].x_org;
+      m->y        = xsi[i].y_org;
+      m->width    = xsi[i].width;
+      m->height   = xsi[i].height;
+    }
+  return monitors;
+}
+
+#endif /* HAVE_XINERAMA */
+
+
+#ifdef HAVE_XF86VMODE
+
+static monitor **
+vidmode_scan_monitors (Display *dpy)
+{
+  int event, error, nscreens, i;
+  monitor **monitors;
+
+  /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops
+     that have a docking station or external monitor that runs in a different
+     resolution than the laptop's screen:
+
+         http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593
+         http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417
+         http://bugs.xfree86.org/show_bug.cgi?id=421
+
+     Presumably this is fixed by using RANDR instead of VidMode.
+   */
+
+# ifdef HAVE_XINERAMA
+  /* Attempts to use the VidMode extension when the Xinerama extension is
+     active can result in a server crash! Yay! */
+  if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error))
+    return 0;
+#  endif /* !HAVE_XINERAMA */
+
+  if (! XF86VidModeQueryExtension (dpy, &event, &error))
+    return 0;
+
+  nscreens = ScreenCount (dpy);
+  monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+  if (!monitors) return 0;
+
+  for (i = 0; i < nscreens; i++)
+    {
+      monitor *m = (monitor *) calloc (1, sizeof (monitor));
+      XF86VidModeModeLine ml;
+      int dot;
+      Screen *screen = ScreenOfDisplay (dpy, i);
+
+      monitors[i] = m;
+      m->id       = i;
+      m->screen   = screen;
+
+      if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y))
+        m->x = m->y = -1;
+
+      if (XF86VidModeGetModeLine (dpy, i, &dot, &ml))
+        {
+          m->width  = ml.hdisplay;
+          m->height = ml.vdisplay;
+        }
+
+      /* 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 (m->x > 0 && m->x < m->width - 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.
+           */
+          m->x = ((m->x - 1) / FUDGE) * FUDGE;
+          m->width += (FUDGE * 2);
+        }
+#  undef FUDGE
+    }
+
+  return monitors;
+}
+
+#endif /* HAVE_XF86VMODE */
+
+
+#ifdef HAVE_RANDR
+
+static monitor **
+randr_scan_monitors (Display *dpy)
+{
+  int event, error, major, minor, nscreens, i, j;
+  monitor **monitors;
+  Bool new_randr_p = False;
+
+  if (! XRRQueryExtension (dpy, &event, &error))
+    return 0;
+
+  if (! XRRQueryVersion (dpy, &major, &minor))
+    return 0;
+
+  if (major <= 0)    /* Protocol was still in flux back then -- fuck it. */
+    return 0;
+
+# ifdef HAVE_RANDR_12
+  new_randr_p = (major > 1 || (major == 1 && minor >= 2));
+# endif
+
+  if (! new_randr_p)
+    /* RANDR 1.0 -- no Xinerama-like virtual screens. */
+    nscreens = ScreenCount (dpy);
+  else  /* RANDR 1.2 or newer -- built-in Xinerama */
+    {
+# ifdef HAVE_RANDR_12
+      int xsc = ScreenCount (dpy);
+      nscreens = 0;
+      /* Add up the virtual screens on each X screen. */
+      for (i = 0; i < xsc; i++)
+        {
+          XRRScreenResources *res = 
+            XRRGetScreenResources (dpy, RootWindow (dpy, i));
+          nscreens += res->noutput;
+          XRRFreeScreenResources (res);
+        }
+# endif /* HAVE_RANDR_12 */
+    }
+
+  monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+  if (!monitors) return 0;
+
+  for (i = 0, j = 0; i < ScreenCount (dpy); i++)
+    {
+      Screen *screen = ScreenOfDisplay (dpy, j);
+
+      if (! new_randr_p)  /* RANDR 1.0 */
+        {
+          XRRScreenConfiguration *rrc;
+          monitor *m = (monitor *) calloc (1, sizeof (monitor));
+          monitors[i] = m;
+          m->screen   = screen;
+          m->id       = i;
+
+          rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
+          if (rrc)
+            {
+              SizeID size = -1;
+              Rotation rot = ~0;
+              XRRScreenSize *rrsizes;
+              int nsizes;
+
+              size = XRRConfigCurrentConfiguration (rrc, &rot);
+              rrsizes = XRRConfigSizes (rrc, &nsizes);
+
+              if (rot & (RR_Rotate_90|RR_Rotate_270))
+                {
+                  m->width  = rrsizes[size].height;
+                  m->height = rrsizes[size].width;
+                }
+              else
+                {
+                  m->width  = rrsizes[size].width;
+                  m->height = rrsizes[size].height;
+                }
+
+              /* don't free 'rrsizes' */
+              XRRFreeScreenConfigInfo (rrc);
+            }
+        }
+      else   /* RANDR 1.2 or newer */
+        {
+# ifdef HAVE_RANDR_12
+          int k;
+          XRRScreenResources *res = 
+            XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
+          for (k = 0; k < res->noutput; k++)
+            {
+              monitor *m = (monitor *) calloc (1, sizeof (monitor));
+              XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, 
+                                                      res->outputs[k]);
+              RRCrtc crtc = (rroi->crtc ? rroi->crtc : rroi->crtcs[0]);
+              XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res, crtc);
+
+              monitors[j] = m;
+              m->screen   = screen;
+              m->id       = (i * 1000) + j;
+              m->desc     = (rroi->name ? strdup (rroi->name) : 0);
+              m->x        = crtci->x;
+              m->y        = crtci->y;
+
+              if (crtci->rotation & (RR_Rotate_90|RR_Rotate_270))
+                {
+                  m->width  = crtci->height;
+                  m->height = crtci->width;
+                }
+              else
+                {
+                  m->width  = crtci->width;
+                  m->height = crtci->height;
+                }
+
+              j++;
+
+              if (rroi->connection == RR_Disconnected)
+                m->sanity = S_DISABLED;
+              /* #### do the same for RR_UnknownConnection? */
+
+              XRRFreeCrtcInfo (crtci);
+              XRRFreeOutputInfo (rroi);
+            }
+          XRRFreeScreenResources (res);
+# endif /* HAVE_RANDR_12 */
+        }
+    }
+
+  return monitors;
+}
+
+#endif /* HAVE_RANDR */
+
+
+static monitor **
+basic_scan_monitors (Display *dpy)
+{
+  int nscreens = ScreenCount (dpy);
+  int i;
+  monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+  if (!monitors) return 0;
+
+  for (i = 0; i < nscreens; i++)
+    {
+      Screen *screen = ScreenOfDisplay (dpy, i);
+      monitor *m = (monitor *) calloc (1, sizeof (monitor));
+      monitors[i] = m;
+      m->id       = i;
+      m->screen   = screen;
+      m->x        = 0;
+      m->y        = 0;
+      m->width    = WidthOfScreen (screen);
+      m->height   = HeightOfScreen (screen);
+    }
+  return monitors;
+}
+
+
+#ifdef DEBUG_MULTISCREEN
+
+/* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
+   will pretend that it is changing the number of connected monitors
+   every few seconds, using the geometries in the following list,
+   for stress-testing purposes.
+ */
+static monitor **
+debug_scan_monitors (Display *dpy)
+{
+  static const char * const geoms[] = {
+    "1600x1028+0+22",
+    "1024x768+0+22",
+    "800x600+0+22",
+    "800x600+0+22,800x600+800+22",
+    "800x600+0+22,800x600+800+22,800x600+300+622",
+    "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622",
+    "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502",
+    "640x480+240+22,640x480+0+502,640x480+640+502",
+    "640x480+0+200,640x480+640+200",
+    "800x600+400+22",
+    "320x200+0+22,320x200+320+22,320x200+640+22,320x200+960+22,320x200+0+222,320x200+320+222,320x200+640+222,320x200+960+222,320x200+0+422,320x200+320+422,320x200+640+422,320x200+960+422,320x200+0+622,320x200+320+622,320x200+640+622,320x200+960+622,320x200+0+822,320x200+320+822,320x200+640+822,320x200+960+822"
+  };
+  static int index = 0;
+  monitor **monitors = (monitor **) calloc (100, sizeof(*monitors));
+  int nscreens = 0;
+  Screen *screen = DefaultScreenOfDisplay (dpy);
+
+  char *s = strdup (geoms[index]);
+  char *token = strtok (s, ",");
+  while (token)
+    {
+      monitor *m = calloc (1, sizeof (monitor));
+      char c;
+      m->id = nscreens;
+      m->screen = screen;
+      if (4 != sscanf (token, "%dx%d+%d+%d%c", 
+                       &m->width, &m->height, &m->x, &m->y, &c))
+        abort();
+      m->width -= 2;
+      m->height -= 2;
+      monitors[nscreens++] = m;
+      token = strtok (0, ",");
+    }
+  free (s);
+  
+  index = (index+1) % countof(geoms);
+  return monitors;
+}
+
+#endif /* DEBUG_MULTISCREEN */
+
+
+#ifdef QUAD_MODE
+static monitor **
+quadruple (monitor **monitors, Bool debug_p)
+{
+  int i, j, count = 0;
+  monitor **monitors2;
+  while (monitors[count])
+    count++;
+  monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors));
+  if (!monitors2) abort();
+
+  for (i = 0, j = 0; i < count; i++)
+    {
+      int k;
+      for (k = 0; k < 4; k++)
+        {
+          monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor));
+          *monitors2[j+k] = *monitors[i];
+          monitors2[j+k]->width  /= (debug_p ? 4 : 2);
+          monitors2[j+k]->height /= 2;
+          monitors2[j+k]->id = (monitors[i]->id * 4) + k;
+          monitors2[j+k]->name = (monitors[i]->name
+                                  ? strdup (monitors[i]->name) : 0);
+        }
+      monitors2[j+1]->x += monitors2[j]->width;
+      monitors2[j+2]->y += monitors2[j]->height;
+      monitors2[j+3]->x += monitors2[j]->width;
+      monitors2[j+3]->y += monitors2[j]->height;
+      j += 4;
+    }
+
+  free_monitors (monitors);
+  return monitors2;
+}
+#endif /* QUAD_MODE */
+
+
+static monitor **
+scan_monitors (saver_info *si)
+{
+  saver_preferences *p = &si->prefs;
+  monitor **monitors = 0;
+
+# ifdef DEBUG_MULTISCREEN
+    if (! monitors) monitors = debug_scan_monitors (si->dpy);
+# endif
+
+# ifdef HAVE_RANDR
+  if (! p->getviewport_full_of_lies_p)
+    if (! monitors) monitors = randr_scan_monitors (si->dpy);
+# endif
+
+# ifdef HAVE_XF86VMODE
+  if (! monitors) monitors = vidmode_scan_monitors (si->dpy);
+# endif
+
+# ifdef HAVE_XF86VMODE
+  if (! monitors) monitors = xinerama_scan_monitors (si->dpy);
+# endif
+
+  if (! monitors) monitors = basic_scan_monitors (si->dpy);
+
+# ifdef QUAD_MODE
+  if (p->quad_p)
+    monitors = quadruple (monitors, p->debug_p);
+# endif
+
+  return monitors;
+}
+
+
+static Bool
+monitors_overlap_p (monitor *a, monitor *b)
+{
+  /* Two rectangles overlap if the max of the tops is less than the
+     min of the bottoms and the max of the lefts is less than the min
+     of the rights.
+   */
+# undef MAX
+# undef MIN
+# define MAX(A,B) ((A)>(B)?(A):(B))
+# define MIN(A,B) ((A)<(B)?(A):(B))
+
+  int maxleft  = MAX(a->x, b->x);
+  int maxtop   = MAX(a->y, b->y);
+  int minright = MIN(a->x + a->width  - 1, b->x + b->width);
+  int minbot   = MIN(a->y + a->height - 1, b->y + b->height);
+  return (maxtop < minbot && maxleft < minright);
+}
+
+
+/* Mark the ones that overlap, etc.
+ */
+static void
+check_monitor_sanity (monitor **monitors)
+{
+  int i, j, count = 0;
+
+  while (monitors[count])
+    count++;
+
+#  define X1 monitors[i]->x
+#  define X2 monitors[j]->x
+#  define Y1 monitors[i]->y
+#  define Y2 monitors[j]->y
+#  define W1 monitors[i]->width
+#  define W2 monitors[j]->width
+#  define H1 monitors[i]->height
+#  define H2 monitors[j]->height
+
+  /* If a monitor is enclosed by any other monitor, that's insane.
+   */
+  for (i = 0; i < count; i++)
+    for (j = 0; j < count; j++)
+      if (i != j &&
+          monitors[i]->sanity == S_SANE &&
+          monitors[j]->sanity == S_SANE &&
+          X2 >= X1 &&
+          Y2 >= Y1 &&
+          (X2+W2) <= (X1+W1) &&
+          (Y2+H2) <= (Y1+H1))
+        {
+          if (X1 == X2 &&
+              Y1 == Y2 &&
+              W1 == W2 &&
+              H1 == H2)
+            monitors[j]->sanity = S_DUPLICATE;
+          else 
+            monitors[j]->sanity = S_ENCLOSED;
+          monitors[j]->enemy = i;
+        }
+
+  /* After checking for enclosure, check for other lossage against earlier
+     monitors.  We do enclosure first so that we make sure to pick the
+     larger one.
+   */
+  for (i = 0; i < count; i++)
+    for (j = 0; j < i; j++)
+      {
+        if (monitors[i]->sanity != S_SANE) continue; /* already marked */
+        if (monitors[j]->sanity != S_SANE) continue;
+
+        if (monitors_overlap_p (monitors[i], monitors[j]))
+          {
+            monitors[i]->sanity = S_OVERLAP;
+            monitors[i]->enemy = j;
+          }
+      }
+
+  /* Finally, make sure all monitors are enclosed by their X screen.
+     Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
+   */
+  for (i = 0; i < count; i++)
+    {
+      int sw = WidthOfScreen (monitors[i]->screen)  * 2;
+      int sh = HeightOfScreen (monitors[i]->screen) * 2;
+      if (monitors[i]->sanity != S_SANE) continue; /* already marked */
+      if (X1    <  0 || Y1    <  0 || 
+          W1    <= 0 || H1    <= 0 || 
+          X1+W1 > sw || Y1+H1 > sh)
+        {
+          monitors[i]->sanity = S_OFFSCREEN;
+          monitors[i]->enemy = 0;
+        }
+    }
+
+#  undef X1
+#  undef X2
+#  undef Y1
+#  undef Y2
+#  undef W1
+#  undef W2
+#  undef H1
+#  undef H2
+}
+
+
+static Bool
+layouts_differ_p (monitor **a, monitor **b)
+{
+  if (!a || !b) return True;
+  while (1)
+    {
+      if (!*a) break;
+      if (!*b) break;
+      if ((*a)->screen != (*b)->screen ||
+          (*a)->x      != (*b)->x      ||
+          (*a)->y      != (*b)->y      ||
+          (*a)->width  != (*b)->width  ||
+          (*a)->height != (*b)->height)
+        return True;
+      a++;
+      b++;
+    }
+  if (*a) return True;
+  if (*b) return True;
+
+  return False;
+}
+
+
+void
+describe_monitor_layout (saver_info *si)
+{
+  monitor **monitors = si->monitor_layout;
+  int count = 0;
+  int good_count = 0;
+  int bad_count = 0;
+  while (monitors[count])
+    {
+      if (monitors[count]->sanity == S_SANE)
+        good_count++;
+      else
+        bad_count++;
+      count++;
+    }
+
+  if (count == 0)
+    fprintf (stderr, "%s: no screens!\n", blurb());
+  else
+    {
+      int i;
+      fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count);
+      for (i = 0; i < count; i++)
+        {
+          monitor *m = monitors[i];
+          if (m->sanity != S_SANE) continue;
+          fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
+                   blurb(), m->id, screen_number (m->screen),
+                   m->width, m->height, m->x, m->y);
+          if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
+          fprintf (stderr, "\n");
+        }
+      if (bad_count > 0)
+        {
+          fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count);
+          for (i = 0; i < count; i++)
+            {
+              monitor *m = monitors[i];
+              monitor *e = monitors[m->enemy];
+              if (m->sanity == S_SANE) continue;
+              fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
+                       blurb(), m->id, screen_number (m->screen),
+                       m->width, m->height, m->x, m->y);
+              if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
+              fprintf (stderr, " -- ");
+              switch (m->sanity)
+                {
+                case S_SANE: abort(); break;
+                case S_ENCLOSED:
+                  fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n",
+                           e->id, e->width, e->height, e->x, e->y);
+                  break;
+                case S_DUPLICATE:
+                  fprintf (stderr, "duplicate of %d\n", e->id);
+                  break;
+                case S_OVERLAP:
+                  fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n",
+                           e->id, e->width, e->height, e->x, e->y);
+                  break;
+                case S_OFFSCREEN:
+                  fprintf (stderr, "off screen (%dx%d)\n",
+                           WidthOfScreen (e->screen), 
+                           HeightOfScreen (e->screen));
+                  break;
+                case S_DISABLED:
+                  fprintf (stderr, "output disabled\n");
+                  break;
+                }
+            }
+        }
+    }
+}
+
+
+/* Synchronize the contents of si->ssi to the current state of the monitors.
+   Doesn't change anything if nothing has changed; otherwise, alters and
+   reuses existing saver_screen_info structs as much as possible.
+   Returns True if anything changed.
+ */
+Bool
+update_screen_layout (saver_info *si)
+{
+  monitor **monitors = scan_monitors (si);
+  int count = 0;
+  int good_count = 0;
+  int i, j;
+  int seen_screens[100] = { 0, };
+
+  if (! layouts_differ_p (monitors, si->monitor_layout))
+    {
+      free_monitors (monitors);
+      return False;
+    }
+
+  free_monitors (si->monitor_layout);
+  si->monitor_layout = monitors;
+  check_monitor_sanity (si->monitor_layout);
+
+  while (monitors[count])
+    {
+      if (monitors[count]->sanity == S_SANE)
+        good_count++;
+      count++;
+    }
+
+  if (si->ssi_count == 0)
+    {
+      si->ssi_count = 10;
+      si->screens = (saver_screen_info *)
+        calloc (sizeof(*si->screens), si->ssi_count);
+    }
+
+  if (si->ssi_count <= good_count)
+    {
+      si->ssi_count = good_count + 10;
+      si->screens = (saver_screen_info *)
+        realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
+      memset (si->screens + si->nscreens, 0, 
+              sizeof(*si->screens) * (si->ssi_count - si->nscreens));
+    }
+
+  if (! si->screens) abort();
+
+  si->nscreens = good_count;
+
+  /* Regenerate the list of GL visuals as needed. */
+  if (si->best_gl_visuals)
+    free (si->best_gl_visuals);
+  si->best_gl_visuals = 0;
+
+  for (i = 0, j = 0; i < count; i++)
+    {
+      monitor *m = monitors[i];
+      saver_screen_info *ssi = &si->screens[j];
+      Screen *old_screen = ssi->screen;
+      int sn;
+      if (monitors[i]->sanity != S_SANE) continue;
+
+      ssi->global = si;
+      ssi->number = j;
+
+      sn = screen_number (m->screen);
+      ssi->screen = m->screen;
+      ssi->real_screen_number = sn;
+      ssi->real_screen_p = (seen_screens[sn] == 0);
+      seen_screens[sn]++;
+
+      ssi->default_visual =
+       get_visual_resource (ssi->screen, "visualID", "VisualID", False);
+      ssi->current_visual = ssi->default_visual;
+      ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
+
+      /* If the screen changed (or if this is the first time) we need
+         a new toplevel shell for this screen's depth.
+       */
+      if (ssi->screen != old_screen)
+        initialize_screen_root_widget (ssi);
+
+      ssi->poll_mouse_last_root_x = -1;
+      ssi->poll_mouse_last_root_y = -1;
+
+      ssi->x      = m->x;
+      ssi->y      = m->y;
+      ssi->width  = m->width;
+      ssi->height = m->height;
+
+# ifndef DEBUG_MULTISCREEN
+      {
+        saver_preferences *p = &si->prefs;
+        if (p->debug_p
+#  ifdef QUAD_MODE
+            && !p->quad_p
+#  endif
+            )
+          ssi->width /= 2;
+      }
+# endif
+
+      j++;
+    }
+
+  si->default_screen = &si->screens[0];
+  return True;
+}