summaryrefslogtreecommitdiffstats
path: root/driver/screens.c
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /driver/screens.c
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'driver/screens.c')
-rw-r--r--driver/screens.c1094
1 files changed, 1094 insertions, 0 deletions
diff --git a/driver/screens.c b/driver/screens.c
new file mode 100644
index 0000000..1a2f41d
--- /dev/null
+++ b/driver/screens.c
@@ -0,0 +1,1094 @@
+/* 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).
+ *
+ * 5a) Nvidia fucks it up:
+ *
+ * Nvidia drivers as of Aug 2008 running in "TwinView" mode
+ * apparently report correct screen geometry via Xinerama, but
+ * report one giant screen via RANDR. The response from the
+ * nvidia developers is, "we don't support RANDR, use Xinerama
+ * instead." Which is a seriously lame answer. So, xscreensaver
+ * has to query *both* extensions, and make a guess as to which
+ * is to be believed.
+ *
+ * 5b) Also sometimes RANDR says stupid shit like, "You have one
+ * screen, and it has no available orientations or sizes."
+ *
+ */
+
+#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 */
+ char *err; /* msg to print at appropriate later time;
+ exists only on monitor #0. */
+};
+
+static Bool layouts_differ_p (monitor **a, monitor **b);
+
+
+static void
+free_monitors (monitor **monitors)
+{
+ monitor **m2 = monitors;
+ if (! monitors) return;
+ while (*m2)
+ {
+ if ((*m2)->desc) free ((*m2)->desc);
+ if ((*m2)->err) free ((*m2)->err);
+ free (*m2);
+ m2++;
+ }
+ free (monitors);
+}
+
+
+static char *
+append (char *s1, const char *s2)
+{
+ char *s = (char *) malloc ((s1 ? strlen(s1) : 0) +
+ (s2 ? strlen(s2) : 0) + 3);
+ *s = 0;
+ if (s1) strcat (s, s1);
+ if (s1 && s2) strcat (s, "\n");
+ if (s2) strcat (s, s2);
+ if (s1) free (s1);
+ return s;
+}
+
+
+#ifdef HAVE_XINERAMA
+
+static monitor **
+xinerama_scan_monitors (Display *dpy, char **errP)
+{
+ 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, char **errP)
+{
+ 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;
+ }
+
+ /* On a system that has VidMode but does not have RANDR, and that has
+ "Option Rotate" set, WidthOfScreen/HeightOfScreen are the rotated
+ size, but XF86VidModeModeLine contains the unrotated size.
+ Maybe there's something in 'flags' that indicates this?
+ Or, we can just notice that the aspect ratios are inverted:
+ */
+ if (m->width > 0 &&
+ m->height > 0 &&
+ ((m->width > m->height) !=
+ (WidthOfScreen(screen) > HeightOfScreen(screen))))
+ {
+ int swap = m->width;
+ m->width = m->height;
+ m->height = swap;
+ }
+
+
+ /* 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 nontrivial.
+
+ 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, char **errP)
+{
+ 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 */
+ }
+
+ if (nscreens <= 0)
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR reported no screens! Ignoring it.");
+ return 0;
+ }
+
+ monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
+ if (!monitors) return 0;
+
+ for (i = 0, j = 0; i < ScreenCount (dpy); i++)
+ {
+ Screen *screen = ScreenOfDisplay (dpy, i);
+
+ 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 = 0;
+
+ size = XRRConfigCurrentConfiguration (rrc, &rot);
+ rrsizes = XRRConfigSizes (rrc, &nsizes);
+
+ if (nsizes <= 0) /* WTF? Shouldn't happen but does. */
+ {
+ m->width = DisplayWidth (dpy, i);
+ m->height = DisplayHeight (dpy, i);
+ }
+ else 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++, j++)
+ {
+ monitor *m = (monitor *) calloc (1, sizeof (monitor));
+ XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res,
+ res->outputs[k]);
+ RRCrtc crtc = (rroi->crtc ? rroi->crtc :
+ rroi->ncrtc ? rroi->crtcs[0] : 0);
+ XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0);
+
+ monitors[j] = m;
+ m->screen = screen;
+ m->id = (i * 1000) + j;
+ m->desc = (rroi->name ? strdup (rroi->name) : 0);
+
+ if (crtci)
+ {
+ /* Note: if the screen is rotated, XRRConfigSizes contains
+ the unrotated WxH, but XRRCrtcInfo contains rotated HxW.
+ */
+ m->x = crtci->x;
+ m->y = crtci->y;
+ m->width = crtci->width;
+ m->height = crtci->height;
+ }
+
+ if (rroi->connection == RR_Disconnected)
+ m->sanity = S_DISABLED;
+ /* #### do the same for RR_UnknownConnection? */
+
+ if (crtci)
+ XRRFreeCrtcInfo (crtci);
+ XRRFreeOutputInfo (rroi);
+ }
+ XRRFreeScreenResources (res);
+# endif /* HAVE_RANDR_12 */
+ }
+ }
+
+ /* Work around more fucking brain damage. */
+ {
+ int ok = 0;
+ int i = 0;
+ while (monitors[i])
+ {
+ if (monitors[i]->width != 0 && monitors[i]->height != 0)
+ ok++;
+ i++;
+ }
+ if (! ok)
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR says all screens are 0x0! Ignoring it.");
+ free_monitors (monitors);
+ monitors = 0;
+ }
+ }
+
+ return monitors;
+}
+
+#endif /* HAVE_RANDR */
+
+
+static monitor **
+basic_scan_monitors (Display *dpy, char **errP)
+{
+ 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;
+}
+
+
+#if defined(HAVE_RANDR) && defined(HAVE_XINERAMA)
+
+/* From: Aaron Plattner <aplattner@nvidia.com>
+ Date: August 7, 2008 10:21:25 AM PDT
+ To: linux-bugs@nvidia.com
+
+ The NVIDIA X driver does not yet support RandR 1.2. The X server has
+ a compatibility layer in it that allows RandR 1.2 clients to talk to
+ RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default".
+ This reports the total combined resolution of the TwinView display,
+ since it doesn't have any visibility into TwinView metamodes. There
+ is no way for the driver to prevent the server from turning on this
+ compatibility layer.
+
+ The intention is for X client applications to continue to use the
+ Xinerama extension to query the screen geometry. RandR 1.2 reports
+ its own Xinerama info for this purpose. I would recommend against
+ modifying xscreensaver to try to get this information from RandR.
+ */
+static monitor **
+randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors,
+ char **errP)
+{
+ monitor **xinerama_monitors;
+
+ if (!randr_monitors)
+ return 0;
+
+ xinerama_monitors = xinerama_scan_monitors (dpy, errP);
+ if (!xinerama_monitors)
+ return randr_monitors;
+
+ if (! layouts_differ_p (randr_monitors, xinerama_monitors))
+ {
+ free_monitors (xinerama_monitors);
+ return randr_monitors;
+ }
+ else if ( randr_monitors[0] && !randr_monitors[1] && /* 1 monitor */
+ xinerama_monitors[0] && xinerama_monitors[1]) /* >1 monitor */
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR reports 1 screen but Xinerama\n"
+ "\t\treports multiple. Believing Xinerama.");
+ free_monitors (randr_monitors);
+ return xinerama_monitors;
+ }
+ else
+ {
+ *errP = append (*errP,
+ "WARNING: RANDR and Xinerama report different\n"
+ "\t\tscreen layouts! Believing RANDR.");
+ free_monitors (xinerama_monitors);
+ return randr_monitors;
+ }
+}
+
+#endif /* HAVE_RANDR && HAVE_XINERAMA */
+
+
+#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, char **errP)
+{
+ 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, char **errP)
+{
+ 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;
+ char *err = 0;
+
+# ifdef DEBUG_MULTISCREEN
+ if (! monitors) monitors = debug_scan_monitors (si->dpy, &err);
+# endif
+
+# ifdef HAVE_RANDR
+ if (! p->getviewport_full_of_lies_p)
+ if (! monitors) monitors = randr_scan_monitors (si->dpy, &err);
+
+# ifdef HAVE_XINERAMA
+ monitors = randr_versus_xinerama_fight (si->dpy, monitors, &err);
+# endif
+# endif /* HAVE_RANDR */
+
+# ifdef HAVE_XF86VMODE
+ if (! monitors) monitors = vidmode_scan_monitors (si->dpy, &err);
+# endif
+
+# ifdef HAVE_XINERAMA
+ if (! monitors) monitors = xinerama_scan_monitors (si->dpy, &err);
+# endif
+
+ if (! monitors) monitors = basic_scan_monitors (si->dpy, &err);
+
+# ifdef QUAD_MODE
+ if (p->quad_p)
+ monitors = quadruple (monitors, p->debug_p, &err);
+# endif
+
+ if (monitors && err) monitors[0]->err = err;
+
+ 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);
+}
+
+
+static Bool
+plausible_aspect_ratio_p (monitor **monitors)
+{
+ /* Modern wide-screen monitors come in the following aspect ratios:
+
+ One monitor: If you tack a 640x480 monitor
+ onto the right, the ratio is:
+ 16 x 9 --> 1.78
+ 852 x 480 --> 1.77 852+640 x 480 --> 3.11 "SD 480p"
+ 1280 x 720 --> 1.78 1280+640 x 720 --> 2.67 "HD 720p"
+ 1280 x 920 --> 1.39 1280+640 x 920 --> 2.09
+ 1366 x 768 --> 1.78 1366+640 x 768 --> 2.61 "HD 768p"
+ 1440 x 900 --> 1.60 1440+640 x 900 --> 2.31
+ 1680 x 1050 --> 1.60 1680+640 x 1050 --> 2.21
+ 1690 x 1050 --> 1.61 1690+640 x 1050 --> 2.22
+ 1920 x 1080 --> 1.78 1920+640 x 1080 --> 2.37 "HD 1080p"
+ 1920 x 1200 --> 1.60 1920+640 x 1200 --> 2.13
+ 2560 x 1600 --> 1.60 2560+640 x 1600 --> 2.00
+
+ So that implies that if we ever see an aspect ratio >= 2.0,
+ we can be pretty sure that the X server is lying to us, and
+ that's actually two monitors, not one.
+ */
+ if (monitors[0] && !monitors[1] && /* exactly 1 monitor */
+ monitors[0]->height &&
+ monitors[0]->width / (double) monitors[0]->height >= 1.9)
+ return False;
+ else
+ return True;
+}
+
+
+/* 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 &&
+ monitors[i]->screen == monitors[j]->screen &&
+ 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[i]->screen != monitors[j]->screen) continue;
+
+ if (monitors_overlap_p (monitors[i], monitors[j]))
+ {
+ monitors[i]->sanity = S_OVERLAP;
+ monitors[i]->enemy = j;
+ }
+ }
+
+ /* Finally, make sure all monitors have sane positions and sizes.
+ Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
+ */
+ for (i = 0; i < count; i++)
+ {
+ if (monitors[i]->sanity != S_SANE) continue; /* already marked */
+ if (X1 < 0 || Y1 < 0 ||
+ W1 <= 0 || H1 <= 0 ||
+ X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF)
+ {
+ 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;
+ int implausible_p = !plausible_aspect_ratio_p (monitors);
+
+ while (monitors[count])
+ {
+ if (monitors[count]->sanity == S_SANE)
+ good_count++;
+ else
+ bad_count++;
+ count++;
+ }
+
+ if (monitors[0]->err) /* deferred error msg */
+ {
+ char *token = strtok (monitors[0]->err, "\n");
+ while (token)
+ {
+ fprintf (stderr, "%s: %s\n", blurb(), token);
+ token = strtok (0, "\n");
+ }
+ free (monitors[0]->err);
+ monitors[0]->err = 0;
+ }
+
+ 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;
+ }
+ }
+ }
+
+ if (implausible_p)
+ fprintf (stderr,
+ "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n"
+ "%s: probable X server bug in Xinerama/RANDR!\n",
+ blurb(), monitors[0]->width, monitors[0]->height,
+ monitors[0]->width / (double) monitors[0]->height,
+ blurb());
+ }
+}
+
+
+/* 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->last_poll_mouse.root_x = -1;
+ ssi->last_poll_mouse.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;
+}