From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / driver / screens.c
1 /* screens.c --- dealing with RANDR, Xinerama, and VidMode Viewports.
2  * xscreensaver, Copyright (c) 1991-2008 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /*   There are a bunch of different mechanisms for multiple monitors
14  *   available in X.  XScreenSaver needs to care about this for two
15  *   reasons: first, to ensure that all visible areas go black; and
16  *   second, so that the windows of screen savers exactly fill the
17  *   glass of each monitor (instead of one saver spanning multiple
18  *   monitors, or a monitor displaying only a sub-rectangle of the
19  *   screen saver.)
20  *
21  *   1) Multi-screen:
22  *
23  *      This is the original way.  Each monitor gets its own display
24  *      number.  :0.0 is the first one, :0.1 is the next, etc.  The
25  *      value of $DISPLAY determines which screen windows open on by
26  *      default.  A single app can open windows on multiple screens
27  *      with the same display connection, but windows cannot be moved
28  *      from one screen to another.  The mouse can be moved from one
29  *      screen to another, though.  Screens may be different depths
30  *      (e.g., one can be TrueColor and one can be PseudoColor.)
31  *      Screens cannot be resized or moved without restarting X.
32  *
33  *      Everyone hates this way of doing things because of the
34  *      inability to move a window from one screen to another without
35  *      restarting the application.
36  *
37  *   2) Xinerama:
38  *
39  *      There is a single giant root window that spans all the
40  *      monitors.  All monitors are the same depth, and windows can be
41  *      moved around.  Applications can learn which rectangles are
42  *      actually visible on monitors by querying the Xinerama server
43  *      extension.  (If you don't do that, you end up with dialog
44  *      boxes that try to appear in the middle of the screen actually
45  *      spanning the gap between two monitors.)
46  *
47  *      Xinerama doesn't work with DRI, which means that if you use
48  *      it, you lose hardware acceleration on OpenGL programs.  Also,
49  *      screens can't be resized or moved without restarting X.
50  *
51  *   3) Vidmode Viewports:
52  *
53  *      With this extension, the root window can be bigger than the
54  *      monitor.  Moving the mouse near the edges of the screen
55  *      scrolls around, like a pan-and-scan movie.  There can also be
56  *      a hot key for changing the monitor's resolution (zooming
57  *      in/out).
58  *
59  *      Trying to combine this with Xinerama crashes the server, so
60  *      you can only use this if you have only a single screen, or are
61  *      in old-multi-screen mode.
62  *
63  *      Also, half the time it doesn't work at all: it tends to lie
64  *      about the size of the rectangle in use.
65  *
66  *   4) RANDR 1.0:
67  *
68  *      The first version of the "Resize and Rotate" extension let you
69  *      change the resolution of a screen on the fly.  The root window
70  *      would actually resize.  However, it was also incompatible with
71  *      Xinerama (did it crash, or just do nothing? I can't remember)
72  *      so you needed to be in single-screen or old multi-screen mode.
73  *      I believe RANDR could co-exist with Vidmode Viewports, but I'm
74  *      not sure.
75  *
76  *   5) RANDR 1.2:
77  *
78  *      Finally, RANDR added the functionality of Xinerama, plus some.
79  *      Each X screen (in the sense of #1, "multi-screen") can have a
80  *      number of sub-rectangles that are displayed on monitors, and
81  *      each of those sub-rectangles can be displayed on more than one
82  *      monitor.  So it's possible (I think) to have a hybrid of
83  *      multi-screen and Xinerama (e.g., to have two monitors running
84  *      in one depth, and three monitors running in another?)
85  *      Typically though, there will be a single X screen, with
86  *      Xinerama-like division of that large root window onto multiple
87  *      monitors.  Also everything's dynamic: monitors can be added,
88  *      removed, and resized at runtime.
89  *
90  *      I believe that as of RANDR 1.2, the Xinerama extension still
91  *      exists but only as a compatiblity layer: it's actually
92  *      returning data from the RANDR extension.
93  *
94  *      Though RANDR 1.2 allows the same image to be cloned onto more
95  *      than one monitor, and also allows one monitor to show a
96  *      subsection of something on another monitor (e.g., the
97  *      rectangles can be enclosed or overlap).  Since there's no way
98  *      to put seperate savers on those duplicated-or-overlapping
99  *      monitors, xscreensaver just ignores them (which allows them to
100  *      display duplicates or overlaps).
101  *
102  *  5a) Nvidia fucks it up:
103  *
104  *      Nvidia drivers as of Aug 2008 running in "TwinView" mode
105  *      apparently report correct screen geometry via Xinerama, but
106  *      report one giant screen via RANDR.  The response from the
107  *      nvidia developers is, "we don't support RANDR, use Xinerama
108  *      instead."  Which is a seriously lame answer.  So, xscreensaver
109  *      has to query *both* extensions, and make a guess as to which
110  *      is to be believed.
111  *
112  *  5b) Also sometimes RANDR says stupid shit like, "You have one
113  *      screen, and it has no available orientations or sizes."
114  *
115  */
116
117 #ifdef HAVE_CONFIG_H
118 # include "config.h"
119 #endif
120
121 #include <X11/Xlib.h>
122
123 #ifdef HAVE_RANDR
124 # include <X11/extensions/Xrandr.h>
125 #endif /* HAVE_RANDR */
126
127 #ifdef HAVE_XINERAMA
128 # include <X11/extensions/Xinerama.h>
129 #endif /* HAVE_XINERAMA */
130
131 #ifdef HAVE_XF86VMODE
132 # include <X11/extensions/xf86vmode.h>
133 #endif /* HAVE_XF86VMODE */
134
135 /* This file doesn't need the Xt headers, so stub these types out... */
136 #undef XtPointer
137 #define XtAppContext void*
138 #define XrmDatabase  void*
139 #define XtIntervalId void*
140 #define XtPointer    void*
141 #define Widget       void*
142
143 #include "xscreensaver.h"
144 #include "visual.h"
145
146
147 typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP, 
148                S_OFFSCREEN, S_DISABLED } monitor_sanity;
149
150 /* 'typedef monitor' is in types.h */
151 struct _monitor {
152   int id;
153   char *desc;
154   Screen *screen;
155   int x, y, width, height;
156   monitor_sanity sanity;        /* I'm not crazy you're the one who's crazy */
157   int enemy;                    /* which monitor it overlaps or duplicates */
158   char *err;                    /* msg to print at appropriate later time;
159                                    exists only on monitor #0. */
160 };
161
162 static Bool layouts_differ_p (monitor **a, monitor **b);
163
164
165 static void
166 free_monitors (monitor **monitors)
167 {
168   monitor **m2 = monitors;
169   if (! monitors) return;
170   while (*m2) 
171     {
172       if ((*m2)->desc) free ((*m2)->desc);
173       if ((*m2)->err) free ((*m2)->err);
174       free (*m2);
175       m2++;
176     }
177   free (monitors);
178 }
179
180
181 static char *
182 append (char *s1, const char *s2)
183 {
184   char *s = (char *) malloc ((s1 ? strlen(s1) : 0) +
185                              (s2 ? strlen(s2) : 0) + 3);
186   *s = 0;
187   if (s1) strcat (s, s1);
188   if (s1 && s2) strcat (s, "\n");
189   if (s2) strcat (s, s2);
190   if (s1) free (s1);
191   return s;
192 }
193
194
195 #ifdef HAVE_XINERAMA
196
197 static monitor **
198 xinerama_scan_monitors (Display *dpy, char **errP)
199 {
200   Screen *screen = DefaultScreenOfDisplay (dpy);
201   int event, error, nscreens, i;
202   XineramaScreenInfo *xsi;
203   monitor **monitors;
204
205   if (! XineramaQueryExtension (dpy, &event, &error))
206     return 0;
207
208   if (! XineramaIsActive (dpy)) 
209     return 0;
210
211   xsi = XineramaQueryScreens (dpy, &nscreens);
212   if (!xsi) return 0;
213
214   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
215   if (!monitors) return 0;
216
217   for (i = 0; i < nscreens; i++)
218     {
219       monitor *m = (monitor *) calloc (1, sizeof (monitor));
220       monitors[i] = m;
221       m->id       = i;
222       m->screen   = screen;
223       m->x        = xsi[i].x_org;
224       m->y        = xsi[i].y_org;
225       m->width    = xsi[i].width;
226       m->height   = xsi[i].height;
227     }
228   return monitors;
229 }
230
231 #endif /* HAVE_XINERAMA */
232
233
234 #ifdef HAVE_XF86VMODE
235
236 static monitor **
237 vidmode_scan_monitors (Display *dpy, char **errP)
238 {
239   int event, error, nscreens, i;
240   monitor **monitors;
241
242   /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops
243      that have a docking station or external monitor that runs in a different
244      resolution than the laptop's screen:
245
246          http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593
247          http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417
248          http://bugs.xfree86.org/show_bug.cgi?id=421
249
250      Presumably this is fixed by using RANDR instead of VidMode.
251    */
252
253 # ifdef HAVE_XINERAMA
254   /* Attempts to use the VidMode extension when the Xinerama extension is
255      active can result in a server crash! Yay! */
256   if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error))
257     return 0;
258 #  endif /* !HAVE_XINERAMA */
259
260   if (! XF86VidModeQueryExtension (dpy, &event, &error))
261     return 0;
262
263   nscreens = ScreenCount (dpy);
264   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
265   if (!monitors) return 0;
266
267   for (i = 0; i < nscreens; i++)
268     {
269       monitor *m = (monitor *) calloc (1, sizeof (monitor));
270       XF86VidModeModeLine ml;
271       int dot;
272       Screen *screen = ScreenOfDisplay (dpy, i);
273
274       monitors[i] = m;
275       m->id       = i;
276       m->screen   = screen;
277
278       if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y))
279         m->x = m->y = -1;
280
281       if (XF86VidModeGetModeLine (dpy, i, &dot, &ml))
282         {
283           m->width  = ml.hdisplay;
284           m->height = ml.vdisplay;
285         }
286
287       /* On a system that has VidMode but does not have RANDR, and that has
288          "Option Rotate" set, WidthOfScreen/HeightOfScreen are the rotated
289          size, but XF86VidModeModeLine contains the unrotated size.
290          Maybe there's something in 'flags' that indicates this?
291          Or, we can just notice that the aspect ratios are inverted:
292        */
293       if (m->width > 0 &&
294           m->height > 0 &&
295           ((m->width > m->height) != 
296            (WidthOfScreen(screen) > HeightOfScreen(screen))))
297         {
298           int swap = m->width;
299           m->width = m->height;
300           m->height = swap;
301         }
302
303
304       /* Apparently, though the server stores the X position in increments of
305          1 pixel, it will only make changes to the *display* in some other
306          increment.  With XF86_SVGA on a Thinkpad, the display only updates
307          in multiples of 8 pixels when in 8-bit mode, and in multiples of 4
308          pixels in 16-bit mode.  I don't know what it does in 24- and 32-bit
309          mode, because I don't have enough video memory to find out.
310
311          I consider it a bug that XF86VidModeGetViewPort() is telling me the
312          server's *target* scroll position rather than the server's *actual*
313          scroll position.  David Dawes agrees, and says they may fix this in
314          XFree86 4.0, but it's nontrivial.
315
316          He also confirms that this behavior is server-dependent, so the
317          actual scroll position cannot be reliably determined by the client.
318          So... that means the only solution is to provide a ``sandbox''
319          around the blackout window -- we make the window be up to N pixels
320          larger than the viewport on both the left and right sides.  That
321          means some part of the outer edges of each hack might not be
322          visible, but screw it.
323
324          I'm going to guess that 16 pixels is enough, and that the Y dimension
325          doesn't have this problem.
326
327          The drawback of doing this, of course, is that some of the screenhacks
328          will still look pretty stupid -- for example, "slidescreen" will cut
329          off the left and right edges of the grid, etc.
330       */
331 #  define FUDGE 16
332       if (m->x > 0 && m->x < m->width - ml.hdisplay)
333         {
334           /* Not at left edge or right edge:
335              Round X position down to next lower multiple of FUDGE.
336              Increase width by 2*FUDGE in case some server rounds up.
337            */
338           m->x = ((m->x - 1) / FUDGE) * FUDGE;
339           m->width += (FUDGE * 2);
340         }
341 #  undef FUDGE
342     }
343
344   return monitors;
345 }
346
347 #endif /* HAVE_XF86VMODE */
348
349
350 #ifdef HAVE_RANDR
351
352 static monitor **
353 randr_scan_monitors (Display *dpy, char **errP)
354 {
355   int event, error, major, minor, nscreens, i, j;
356   monitor **monitors;
357   Bool new_randr_p = False;
358
359   if (! XRRQueryExtension (dpy, &event, &error))
360     return 0;
361
362   if (! XRRQueryVersion (dpy, &major, &minor))
363     return 0;
364
365   if (major <= 0)    /* Protocol was still in flux back then -- fuck it. */
366     return 0;
367
368 # ifdef HAVE_RANDR_12
369   new_randr_p = (major > 1 || (major == 1 && minor >= 2));
370 # endif
371
372   if (! new_randr_p)
373     /* RANDR 1.0 -- no Xinerama-like virtual screens. */
374     nscreens = ScreenCount (dpy);
375   else  /* RANDR 1.2 or newer -- built-in Xinerama */
376     {
377 # ifdef HAVE_RANDR_12
378       int xsc = ScreenCount (dpy);
379       nscreens = 0;
380       /* Add up the virtual screens on each X screen. */
381       for (i = 0; i < xsc; i++)
382         {
383           XRRScreenResources *res = 
384             XRRGetScreenResources (dpy, RootWindow (dpy, i));
385           nscreens += res->noutput;
386           XRRFreeScreenResources (res);
387         }
388 # endif /* HAVE_RANDR_12 */
389     }
390
391   if (nscreens <= 0)
392     {
393       *errP = append (*errP,
394                       "WARNING: RANDR reported no screens!  Ignoring it.");
395       return 0;
396     }
397
398   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
399   if (!monitors) return 0;
400
401   for (i = 0, j = 0; i < ScreenCount (dpy); i++)
402     {
403       Screen *screen = ScreenOfDisplay (dpy, i);
404
405       if (! new_randr_p)  /* RANDR 1.0 */
406         {
407           XRRScreenConfiguration *rrc;
408           monitor *m = (monitor *) calloc (1, sizeof (monitor));
409           monitors[i] = m;
410           m->screen   = screen;
411           m->id       = i;
412
413           rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
414           if (rrc)
415             {
416               SizeID size = -1;
417               Rotation rot = ~0;
418               XRRScreenSize *rrsizes;
419               int nsizes = 0;
420
421               size = XRRConfigCurrentConfiguration (rrc, &rot);
422               rrsizes = XRRConfigSizes (rrc, &nsizes);
423
424               if (nsizes <= 0)  /* WTF?  Shouldn't happen but does. */
425                 {
426                   m->width  = DisplayWidth (dpy, i);
427                   m->height = DisplayHeight (dpy, i);
428                 }
429               else if (rot & (RR_Rotate_90|RR_Rotate_270))
430                 {
431                   m->width  = rrsizes[size].height;
432                   m->height = rrsizes[size].width;
433                 }
434               else
435                 {
436                   m->width  = rrsizes[size].width;
437                   m->height = rrsizes[size].height;
438                 }
439
440               /* don't free 'rrsizes' */
441               XRRFreeScreenConfigInfo (rrc);
442             }
443         }
444       else   /* RANDR 1.2 or newer */
445         {
446 # ifdef HAVE_RANDR_12
447           int k;
448           XRRScreenResources *res = 
449             XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
450           for (k = 0; k < res->noutput; k++, j++)
451             {
452               monitor *m = (monitor *) calloc (1, sizeof (monitor));
453               XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, 
454                                                       res->outputs[k]);
455               RRCrtc crtc = (rroi->crtc  ? rroi->crtc :
456                              rroi->ncrtc ? rroi->crtcs[0] : 0);
457               XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0);
458
459               monitors[j] = m;
460               m->screen   = screen;
461               m->id       = (i * 1000) + j;
462               m->desc     = (rroi->name ? strdup (rroi->name) : 0);
463
464               if (crtci)
465                 {
466                   /* Note: if the screen is rotated, XRRConfigSizes contains
467                      the unrotated WxH, but XRRCrtcInfo contains rotated HxW.
468                    */
469                   m->x      = crtci->x;
470                   m->y      = crtci->y;
471                   m->width  = crtci->width;
472                   m->height = crtci->height;
473                 }
474
475               if (rroi->connection == RR_Disconnected)
476                 m->sanity = S_DISABLED;
477               /* #### do the same for RR_UnknownConnection? */
478
479               if (crtci) 
480                 XRRFreeCrtcInfo (crtci);
481               XRRFreeOutputInfo (rroi);
482             }
483           XRRFreeScreenResources (res);
484 # endif /* HAVE_RANDR_12 */
485         }
486     }
487
488   /* Work around more fucking brain damage. */
489   {
490     int ok = 0;
491     int i = 0;
492     while (monitors[i]) 
493       {
494         if (monitors[i]->width != 0 && monitors[i]->height != 0)
495           ok++;
496         i++;
497       }
498     if (! ok)
499       {
500         *errP = append (*errP,
501               "WARNING: RANDR says all screens are 0x0!  Ignoring it.");
502         free_monitors (monitors);
503         monitors = 0;
504       }
505   }
506
507   return monitors;
508 }
509
510 #endif /* HAVE_RANDR */
511
512
513 static monitor **
514 basic_scan_monitors (Display *dpy, char **errP)
515 {
516   int nscreens = ScreenCount (dpy);
517   int i;
518   monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
519   if (!monitors) return 0;
520
521   for (i = 0; i < nscreens; i++)
522     {
523       Screen *screen = ScreenOfDisplay (dpy, i);
524       monitor *m = (monitor *) calloc (1, sizeof (monitor));
525       monitors[i] = m;
526       m->id       = i;
527       m->screen   = screen;
528       m->x        = 0;
529       m->y        = 0;
530       m->width    = WidthOfScreen (screen);
531       m->height   = HeightOfScreen (screen);
532     }
533   return monitors;
534 }
535
536
537 #if defined(HAVE_RANDR) && defined(HAVE_XINERAMA)
538
539 /*   From: Aaron Plattner <aplattner@nvidia.com>
540      Date: August 7, 2008 10:21:25 AM PDT
541      To: linux-bugs@nvidia.com
542
543      The NVIDIA X driver does not yet support RandR 1.2.  The X server has
544      a compatibility layer in it that allows RandR 1.2 clients to talk to
545      RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default".
546      This reports the total combined resolution of the TwinView display,
547      since it doesn't have any visibility into TwinView metamodes.  There
548      is no way for the driver to prevent the server from turning on this
549      compatibility layer.
550
551      The intention is for X client applications to continue to use the
552      Xinerama extension to query the screen geometry.  RandR 1.2 reports
553      its own Xinerama info for this purpose.  I would recommend against
554      modifying xscreensaver to try to get this information from RandR.
555  */
556 static monitor **
557 randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors, 
558                              char **errP)
559 {
560   monitor **xinerama_monitors;
561
562   if (!randr_monitors) 
563     return 0;
564
565   xinerama_monitors = xinerama_scan_monitors (dpy, errP);
566   if (!xinerama_monitors)
567     return randr_monitors;
568
569   if (! layouts_differ_p (randr_monitors, xinerama_monitors))
570     {
571       free_monitors (xinerama_monitors);
572       return randr_monitors;
573     }
574   else if (   randr_monitors[0] &&   !randr_monitors[1] &&  /* 1 monitor */
575            xinerama_monitors[0] && xinerama_monitors[1])    /* >1 monitor */
576     {
577       *errP = append (*errP,
578                       "WARNING: RANDR reports 1 screen but Xinerama\n"
579                       "\t\treports multiple.  Believing Xinerama.");
580       free_monitors (randr_monitors);
581       return xinerama_monitors;
582     }
583   else
584     {
585       *errP = append (*errP,
586                       "WARNING: RANDR and Xinerama report different\n"
587                       "\t\tscreen layouts!  Believing RANDR.");
588       free_monitors (xinerama_monitors);
589       return randr_monitors;
590     }
591 }
592
593 #endif /* HAVE_RANDR && HAVE_XINERAMA */
594
595
596 #ifdef DEBUG_MULTISCREEN
597
598 /* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
599    will pretend that it is changing the number of connected monitors
600    every few seconds, using the geometries in the following list,
601    for stress-testing purposes.
602  */
603 static monitor **
604 debug_scan_monitors (Display *dpy, char **errP)
605 {
606   static const char * const geoms[] = {
607     "1600x1028+0+22",
608     "1024x768+0+22",
609     "800x600+0+22",
610     "800x600+0+22,800x600+800+22",
611     "800x600+0+22,800x600+800+22,800x600+300+622",
612     "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622",
613     "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502",
614     "640x480+240+22,640x480+0+502,640x480+640+502",
615     "640x480+0+200,640x480+640+200",
616     "800x600+400+22",
617     "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"
618   };
619   static int index = 0;
620   monitor **monitors = (monitor **) calloc (100, sizeof(*monitors));
621   int nscreens = 0;
622   Screen *screen = DefaultScreenOfDisplay (dpy);
623
624   char *s = strdup (geoms[index]);
625   char *token = strtok (s, ",");
626   while (token)
627     {
628       monitor *m = calloc (1, sizeof (monitor));
629       char c;
630       m->id = nscreens;
631       m->screen = screen;
632       if (4 != sscanf (token, "%dx%d+%d+%d%c", 
633                        &m->width, &m->height, &m->x, &m->y, &c))
634         abort();
635       m->width -= 2;
636       m->height -= 2;
637       monitors[nscreens++] = m;
638       token = strtok (0, ",");
639     }
640   free (s);
641   
642   index = (index+1) % countof(geoms);
643   return monitors;
644 }
645
646 #endif /* DEBUG_MULTISCREEN */
647
648
649 #ifdef QUAD_MODE
650 static monitor **
651 quadruple (monitor **monitors, Bool debug_p, char **errP)
652 {
653   int i, j, count = 0;
654   monitor **monitors2;
655   while (monitors[count])
656     count++;
657   monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors));
658   if (!monitors2) abort();
659
660   for (i = 0, j = 0; i < count; i++)
661     {
662       int k;
663       for (k = 0; k < 4; k++)
664         {
665           monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor));
666           *monitors2[j+k] = *monitors[i];
667           monitors2[j+k]->width  /= (debug_p ? 4 : 2);
668           monitors2[j+k]->height /= 2;
669           monitors2[j+k]->id = (monitors[i]->id * 4) + k;
670           monitors2[j+k]->name = (monitors[i]->name
671                                   ? strdup (monitors[i]->name) : 0);
672         }
673       monitors2[j+1]->x += monitors2[j]->width;
674       monitors2[j+2]->y += monitors2[j]->height;
675       monitors2[j+3]->x += monitors2[j]->width;
676       monitors2[j+3]->y += monitors2[j]->height;
677       j += 4;
678     }
679
680   free_monitors (monitors);
681   return monitors2;
682 }
683 #endif /* QUAD_MODE */
684
685
686 static monitor **
687 scan_monitors (saver_info *si)
688 {
689   saver_preferences *p = &si->prefs;
690   monitor **monitors = 0;
691   char *err = 0;
692
693 # ifdef DEBUG_MULTISCREEN
694     if (! monitors) monitors = debug_scan_monitors (si->dpy, &err);
695 # endif
696
697 # ifdef HAVE_RANDR
698   if (! p->getviewport_full_of_lies_p)
699     if (! monitors) monitors = randr_scan_monitors (si->dpy, &err);
700
701 #  ifdef HAVE_XINERAMA
702    monitors = randr_versus_xinerama_fight (si->dpy, monitors, &err);
703 #  endif
704 # endif /* HAVE_RANDR */
705
706 # ifdef HAVE_XF86VMODE
707   if (! monitors) monitors = vidmode_scan_monitors (si->dpy, &err);
708 # endif
709
710 # ifdef HAVE_XINERAMA
711   if (! monitors) monitors = xinerama_scan_monitors (si->dpy, &err);
712 # endif
713
714   if (! monitors) monitors = basic_scan_monitors (si->dpy, &err);
715
716 # ifdef QUAD_MODE
717   if (p->quad_p)
718     monitors = quadruple (monitors, p->debug_p, &err);
719 # endif
720
721   if (monitors && err) monitors[0]->err = err;
722
723   return monitors;
724 }
725
726
727 static Bool
728 monitors_overlap_p (monitor *a, monitor *b)
729 {
730   /* Two rectangles overlap if the max of the tops is less than the
731      min of the bottoms and the max of the lefts is less than the min
732      of the rights.
733    */
734 # undef MAX
735 # undef MIN
736 # define MAX(A,B) ((A)>(B)?(A):(B))
737 # define MIN(A,B) ((A)<(B)?(A):(B))
738
739   int maxleft  = MAX(a->x, b->x);
740   int maxtop   = MAX(a->y, b->y);
741   int minright = MIN(a->x + a->width  - 1, b->x + b->width);
742   int minbot   = MIN(a->y + a->height - 1, b->y + b->height);
743   return (maxtop < minbot && maxleft < minright);
744 }
745
746
747 static Bool
748 plausible_aspect_ratio_p (monitor **monitors)
749 {
750   /* Modern wide-screen monitors come in the following aspect ratios:
751
752             One monitor:        If you tack a 640x480 monitor
753                                 onto the right, the ratio is:
754          16 x 9    --> 1.78
755         852 x 480  --> 1.77        852+640 x 480  --> 3.11      "SD 480p"
756        1280 x 720  --> 1.78       1280+640 x 720  --> 2.67      "HD 720p"
757        1280 x 920  --> 1.39       1280+640 x 920  --> 2.09
758        1366 x 768  --> 1.78       1366+640 x 768  --> 2.61      "HD 768p"
759        1440 x 900  --> 1.60       1440+640 x 900  --> 2.31
760        1680 x 1050 --> 1.60       1680+640 x 1050 --> 2.21
761        1690 x 1050 --> 1.61       1690+640 x 1050 --> 2.22
762        1920 x 1080 --> 1.78       1920+640 x 1080 --> 2.37      "HD 1080p"
763        1920 x 1200 --> 1.60       1920+640 x 1200 --> 2.13
764        2560 x 1600 --> 1.60       2560+640 x 1600 --> 2.00
765
766      So that implies that if we ever see an aspect ratio >= 2.0,
767      we can be pretty sure that the X server is lying to us, and
768      that's actually two monitors, not one.
769    */
770   if (monitors[0] && !monitors[1] &&    /* exactly 1 monitor */
771       monitors[0]->height &&
772       monitors[0]->width / (double) monitors[0]->height >= 1.9)
773     return False;
774   else
775     return True;
776 }
777
778
779 /* Mark the ones that overlap, etc.
780  */
781 static void
782 check_monitor_sanity (monitor **monitors)
783 {
784   int i, j, count = 0;
785
786   while (monitors[count])
787     count++;
788
789 #  define X1 monitors[i]->x
790 #  define X2 monitors[j]->x
791 #  define Y1 monitors[i]->y
792 #  define Y2 monitors[j]->y
793 #  define W1 monitors[i]->width
794 #  define W2 monitors[j]->width
795 #  define H1 monitors[i]->height
796 #  define H2 monitors[j]->height
797
798   /* If a monitor is enclosed by any other monitor, that's insane.
799    */
800   for (i = 0; i < count; i++)
801     for (j = 0; j < count; j++)
802       if (i != j &&
803           monitors[i]->sanity == S_SANE &&
804           monitors[j]->sanity == S_SANE &&
805           monitors[i]->screen == monitors[j]->screen &&
806           X2 >= X1 &&
807           Y2 >= Y1 &&
808           (X2+W2) <= (X1+W1) &&
809           (Y2+H2) <= (Y1+H1))
810         {
811           if (X1 == X2 &&
812               Y1 == Y2 &&
813               W1 == W2 &&
814               H1 == H2)
815             monitors[j]->sanity = S_DUPLICATE;
816           else 
817             monitors[j]->sanity = S_ENCLOSED;
818           monitors[j]->enemy = i;
819         }
820
821   /* After checking for enclosure, check for other lossage against earlier
822      monitors.  We do enclosure first so that we make sure to pick the
823      larger one.
824    */
825   for (i = 0; i < count; i++)
826     for (j = 0; j < i; j++)
827       {
828         if (monitors[i]->sanity != S_SANE) continue; /* already marked */
829         if (monitors[j]->sanity != S_SANE) continue;
830         if (monitors[i]->screen != monitors[j]->screen) continue;
831
832         if (monitors_overlap_p (monitors[i], monitors[j]))
833           {
834             monitors[i]->sanity = S_OVERLAP;
835             monitors[i]->enemy = j;
836           }
837       }
838
839   /* Finally, make sure all monitors have sane positions and sizes.
840      Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
841    */
842   for (i = 0; i < count; i++)
843     {
844       if (monitors[i]->sanity != S_SANE) continue; /* already marked */
845       if (X1    <  0      || Y1    <  0 || 
846           W1    <= 0      || H1    <= 0 || 
847           X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF)
848         {
849           monitors[i]->sanity = S_OFFSCREEN;
850           monitors[i]->enemy = 0;
851         }
852     }
853
854 #  undef X1
855 #  undef X2
856 #  undef Y1
857 #  undef Y2
858 #  undef W1
859 #  undef W2
860 #  undef H1
861 #  undef H2
862 }
863
864
865 static Bool
866 layouts_differ_p (monitor **a, monitor **b)
867 {
868   if (!a || !b) return True;
869   while (1)
870     {
871       if (!*a) break;
872       if (!*b) break;
873       if ((*a)->screen != (*b)->screen ||
874           (*a)->x      != (*b)->x      ||
875           (*a)->y      != (*b)->y      ||
876           (*a)->width  != (*b)->width  ||
877           (*a)->height != (*b)->height)
878         return True;
879       a++;
880       b++;
881     }
882   if (*a) return True;
883   if (*b) return True;
884
885   return False;
886 }
887
888
889 void
890 describe_monitor_layout (saver_info *si)
891 {
892   monitor **monitors = si->monitor_layout;
893   int count = 0;
894   int good_count = 0;
895   int bad_count = 0;
896   int implausible_p = !plausible_aspect_ratio_p (monitors);
897
898   while (monitors[count])
899     {
900       if (monitors[count]->sanity == S_SANE)
901         good_count++;
902       else
903         bad_count++;
904       count++;
905     }
906
907   if (monitors[0]->err)         /* deferred error msg */
908     {
909       char *token = strtok (monitors[0]->err, "\n");
910       while (token)
911         {
912           fprintf (stderr, "%s: %s\n", blurb(), token);
913           token = strtok (0, "\n");
914         }
915       free (monitors[0]->err);
916       monitors[0]->err = 0;
917     }
918
919   if (count == 0)
920     fprintf (stderr, "%s: no screens!\n", blurb());
921   else
922     {
923       int i;
924       fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count);
925       for (i = 0; i < count; i++)
926         {
927           monitor *m = monitors[i];
928           if (m->sanity != S_SANE) continue;
929           fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
930                    blurb(), m->id, screen_number (m->screen),
931                    m->width, m->height, m->x, m->y);
932           if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
933           fprintf (stderr, "\n");
934         }
935       if (bad_count > 0)
936         {
937           fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count);
938           for (i = 0; i < count; i++)
939             {
940               monitor *m = monitors[i];
941               monitor *e = monitors[m->enemy];
942               if (m->sanity == S_SANE) continue;
943               fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
944                        blurb(), m->id, screen_number (m->screen),
945                        m->width, m->height, m->x, m->y);
946               if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
947               fprintf (stderr, " -- ");
948               switch (m->sanity)
949                 {
950                 case S_SANE: abort(); break;
951                 case S_ENCLOSED:
952                   fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n",
953                            e->id, e->width, e->height, e->x, e->y);
954                   break;
955                 case S_DUPLICATE:
956                   fprintf (stderr, "duplicate of %d\n", e->id);
957                   break;
958                 case S_OVERLAP:
959                   fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n",
960                            e->id, e->width, e->height, e->x, e->y);
961                   break;
962                 case S_OFFSCREEN:
963                   fprintf (stderr, "off screen (%dx%d)\n",
964                            WidthOfScreen (e->screen), 
965                            HeightOfScreen (e->screen));
966                   break;
967                 case S_DISABLED:
968                   fprintf (stderr, "output disabled\n");
969                   break;
970                 }
971             }
972         }
973
974       if (implausible_p)
975         fprintf (stderr,
976                  "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n"
977                  "%s:          probable X server bug in Xinerama/RANDR!\n",
978                  blurb(), monitors[0]->width, monitors[0]->height,
979                  monitors[0]->width / (double) monitors[0]->height,
980                  blurb());
981     }
982 }
983
984
985 /* Synchronize the contents of si->ssi to the current state of the monitors.
986    Doesn't change anything if nothing has changed; otherwise, alters and
987    reuses existing saver_screen_info structs as much as possible.
988    Returns True if anything changed.
989  */
990 Bool
991 update_screen_layout (saver_info *si)
992 {
993   monitor **monitors = scan_monitors (si);
994   int count = 0;
995   int good_count = 0;
996   int i, j;
997   int seen_screens[100] = { 0, };
998
999   if (! layouts_differ_p (monitors, si->monitor_layout))
1000     {
1001       free_monitors (monitors);
1002       return False;
1003     }
1004
1005   free_monitors (si->monitor_layout);
1006   si->monitor_layout = monitors;
1007   check_monitor_sanity (si->monitor_layout);
1008
1009   while (monitors[count])
1010     {
1011       if (monitors[count]->sanity == S_SANE)
1012         good_count++;
1013       count++;
1014     }
1015
1016   if (si->ssi_count == 0)
1017     {
1018       si->ssi_count = 10;
1019       si->screens = (saver_screen_info *)
1020         calloc (sizeof(*si->screens), si->ssi_count);
1021     }
1022
1023   if (si->ssi_count <= good_count)
1024     {
1025       si->ssi_count = good_count + 10;
1026       si->screens = (saver_screen_info *)
1027         realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
1028       memset (si->screens + si->nscreens, 0, 
1029               sizeof(*si->screens) * (si->ssi_count - si->nscreens));
1030     }
1031
1032   if (! si->screens) abort();
1033
1034   si->nscreens = good_count;
1035
1036   /* Regenerate the list of GL visuals as needed. */
1037   if (si->best_gl_visuals)
1038     free (si->best_gl_visuals);
1039   si->best_gl_visuals = 0;
1040
1041   for (i = 0, j = 0; i < count; i++)
1042     {
1043       monitor *m = monitors[i];
1044       saver_screen_info *ssi = &si->screens[j];
1045       Screen *old_screen = ssi->screen;
1046       int sn;
1047       if (monitors[i]->sanity != S_SANE) continue;
1048
1049       ssi->global = si;
1050       ssi->number = j;
1051
1052       sn = screen_number (m->screen);
1053       ssi->screen = m->screen;
1054       ssi->real_screen_number = sn;
1055       ssi->real_screen_p = (seen_screens[sn] == 0);
1056       seen_screens[sn]++;
1057
1058       ssi->default_visual =
1059         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
1060       ssi->current_visual = ssi->default_visual;
1061       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
1062
1063       /* If the screen changed (or if this is the first time) we need
1064          a new toplevel shell for this screen's depth.
1065        */
1066       if (ssi->screen != old_screen)
1067         initialize_screen_root_widget (ssi);
1068
1069       ssi->last_poll_mouse.root_x = -1;
1070       ssi->last_poll_mouse.root_y = -1;
1071
1072       ssi->x      = m->x;
1073       ssi->y      = m->y;
1074       ssi->width  = m->width;
1075       ssi->height = m->height;
1076
1077 # ifndef DEBUG_MULTISCREEN
1078       {
1079         saver_preferences *p = &si->prefs;
1080         if (p->debug_p
1081 #  ifdef QUAD_MODE
1082             && !p->quad_p
1083 #  endif
1084             )
1085           ssi->width /= 2;
1086       }
1087 # endif
1088
1089       j++;
1090     }
1091
1092   si->default_screen = &si->screens[0];
1093   return True;
1094 }