http://www.jwz.org/xscreensaver/xscreensaver-5.07.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
113 #ifdef HAVE_CONFIG_H
114 # include "config.h"
115 #endif
116
117 #include <X11/Xlib.h>
118
119 #ifdef HAVE_RANDR
120 # include <X11/extensions/Xrandr.h>
121 #endif /* HAVE_RANDR */
122
123 #ifdef HAVE_XINERAMA
124 # include <X11/extensions/Xinerama.h>
125 #endif /* HAVE_XINERAMA */
126
127 #ifdef HAVE_XF86VMODE
128 # include <X11/extensions/xf86vmode.h>
129 #endif /* HAVE_XF86VMODE */
130
131 /* This file doesn't need the Xt headers, so stub these types out... */
132 #undef XtPointer
133 #define XtAppContext void*
134 #define XrmDatabase  void*
135 #define XtIntervalId void*
136 #define XtPointer    void*
137 #define Widget       void*
138
139 #include "xscreensaver.h"
140 #include "visual.h"
141
142
143 typedef enum { S_SANE, S_ENCLOSED, S_DUPLICATE, S_OVERLAP, 
144                S_OFFSCREEN, S_DISABLED } monitor_sanity;
145
146 /* 'typedef monitor' is in types.h */
147 struct _monitor {
148   int id;
149   char *desc;
150   Screen *screen;
151   int x, y, width, height;
152   monitor_sanity sanity;        /* I'm not crazy you're the one who's crazy */
153   int enemy;                    /* which monitor it overlaps or duplicates */
154 };
155
156 static Bool layouts_differ_p (monitor **a, monitor **b);
157
158
159 static void
160 free_monitors (monitor **monitors)
161 {
162   monitor **m2 = monitors;
163   if (! monitors) return;
164   while (*m2) 
165     {
166       if ((*m2)->desc) free ((*m2)->desc);
167       free (*m2);
168       m2++;
169     }
170   free (monitors);
171 }
172
173
174 #ifdef HAVE_XINERAMA
175
176 static monitor **
177 xinerama_scan_monitors (Display *dpy)
178 {
179   Screen *screen = DefaultScreenOfDisplay (dpy);
180   int event, error, nscreens, i;
181   XineramaScreenInfo *xsi;
182   monitor **monitors;
183
184   if (! XineramaQueryExtension (dpy, &event, &error))
185     return 0;
186
187   if (! XineramaIsActive (dpy)) 
188     return 0;
189
190   xsi = XineramaQueryScreens (dpy, &nscreens);
191   if (!xsi) return 0;
192
193   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
194   if (!monitors) return 0;
195
196   for (i = 0; i < nscreens; i++)
197     {
198       monitor *m = (monitor *) calloc (1, sizeof (monitor));
199       monitors[i] = m;
200       m->id       = i;
201       m->screen   = screen;
202       m->x        = xsi[i].x_org;
203       m->y        = xsi[i].y_org;
204       m->width    = xsi[i].width;
205       m->height   = xsi[i].height;
206     }
207   return monitors;
208 }
209
210 #endif /* HAVE_XINERAMA */
211
212
213 #ifdef HAVE_XF86VMODE
214
215 static monitor **
216 vidmode_scan_monitors (Display *dpy)
217 {
218   int event, error, nscreens, i;
219   monitor **monitors;
220
221   /* Note that XF86VidModeGetViewPort() tends to be full of lies on laptops
222      that have a docking station or external monitor that runs in a different
223      resolution than the laptop's screen:
224
225          http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=81593
226          http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=208417
227          http://bugs.xfree86.org/show_bug.cgi?id=421
228
229      Presumably this is fixed by using RANDR instead of VidMode.
230    */
231
232 # ifdef HAVE_XINERAMA
233   /* Attempts to use the VidMode extension when the Xinerama extension is
234      active can result in a server crash! Yay! */
235   if (XQueryExtension (dpy, "XINERAMA", &error, &event, &error))
236     return 0;
237 #  endif /* !HAVE_XINERAMA */
238
239   if (! XF86VidModeQueryExtension (dpy, &event, &error))
240     return 0;
241
242   nscreens = ScreenCount (dpy);
243   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
244   if (!monitors) return 0;
245
246   for (i = 0; i < nscreens; i++)
247     {
248       monitor *m = (monitor *) calloc (1, sizeof (monitor));
249       XF86VidModeModeLine ml;
250       int dot;
251       Screen *screen = ScreenOfDisplay (dpy, i);
252
253       monitors[i] = m;
254       m->id       = i;
255       m->screen   = screen;
256
257       if (! safe_XF86VidModeGetViewPort (dpy, i, &m->x, &m->y))
258         m->x = m->y = -1;
259
260       if (XF86VidModeGetModeLine (dpy, i, &dot, &ml))
261         {
262           m->width  = ml.hdisplay;
263           m->height = ml.vdisplay;
264         }
265
266       /* Apparently, though the server stores the X position in increments of
267          1 pixel, it will only make changes to the *display* in some other
268          increment.  With XF86_SVGA on a Thinkpad, the display only updates
269          in multiples of 8 pixels when in 8-bit mode, and in multiples of 4
270          pixels in 16-bit mode.  I don't know what it does in 24- and 32-bit
271          mode, because I don't have enough video memory to find out.
272
273          I consider it a bug that XF86VidModeGetViewPort() is telling me the
274          server's *target* scroll position rather than the server's *actual*
275          scroll position.  David Dawes agrees, and says they may fix this in
276          XFree86 4.0, but it's notrivial.
277
278          He also confirms that this behavior is server-dependent, so the
279          actual scroll position cannot be reliably determined by the client.
280          So... that means the only solution is to provide a ``sandbox''
281          around the blackout window -- we make the window be up to N pixels
282          larger than the viewport on both the left and right sides.  That
283          means some part of the outer edges of each hack might not be
284          visible, but screw it.
285
286          I'm going to guess that 16 pixels is enough, and that the Y dimension
287          doesn't have this problem.
288
289          The drawback of doing this, of course, is that some of the screenhacks
290          will still look pretty stupid -- for example, "slidescreen" will cut
291          off the left and right edges of the grid, etc.
292       */
293 #  define FUDGE 16
294       if (m->x > 0 && m->x < m->width - ml.hdisplay)
295         {
296           /* Not at left edge or right edge:
297              Round X position down to next lower multiple of FUDGE.
298              Increase width by 2*FUDGE in case some server rounds up.
299            */
300           m->x = ((m->x - 1) / FUDGE) * FUDGE;
301           m->width += (FUDGE * 2);
302         }
303 #  undef FUDGE
304     }
305
306   return monitors;
307 }
308
309 #endif /* HAVE_XF86VMODE */
310
311
312 #ifdef HAVE_RANDR
313
314 static monitor **
315 randr_scan_monitors (Display *dpy)
316 {
317   int event, error, major, minor, nscreens, i, j;
318   monitor **monitors;
319   Bool new_randr_p = False;
320
321   if (! XRRQueryExtension (dpy, &event, &error))
322     return 0;
323
324   if (! XRRQueryVersion (dpy, &major, &minor))
325     return 0;
326
327   if (major <= 0)    /* Protocol was still in flux back then -- fuck it. */
328     return 0;
329
330 # ifdef HAVE_RANDR_12
331   new_randr_p = (major > 1 || (major == 1 && minor >= 2));
332 # endif
333
334   if (! new_randr_p)
335     /* RANDR 1.0 -- no Xinerama-like virtual screens. */
336     nscreens = ScreenCount (dpy);
337   else  /* RANDR 1.2 or newer -- built-in Xinerama */
338     {
339 # ifdef HAVE_RANDR_12
340       int xsc = ScreenCount (dpy);
341       nscreens = 0;
342       /* Add up the virtual screens on each X screen. */
343       for (i = 0; i < xsc; i++)
344         {
345           XRRScreenResources *res = 
346             XRRGetScreenResources (dpy, RootWindow (dpy, i));
347           nscreens += res->noutput;
348           XRRFreeScreenResources (res);
349         }
350 # endif /* HAVE_RANDR_12 */
351     }
352
353   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
354   if (!monitors) return 0;
355
356   for (i = 0, j = 0; i < ScreenCount (dpy); i++)
357     {
358       Screen *screen = ScreenOfDisplay (dpy, i);
359
360       if (! new_randr_p)  /* RANDR 1.0 */
361         {
362           XRRScreenConfiguration *rrc;
363           monitor *m = (monitor *) calloc (1, sizeof (monitor));
364           monitors[i] = m;
365           m->screen   = screen;
366           m->id       = i;
367
368           rrc = XRRGetScreenInfo (dpy, RootWindowOfScreen (screen));
369           if (rrc)
370             {
371               SizeID size = -1;
372               Rotation rot = ~0;
373               XRRScreenSize *rrsizes;
374               int nsizes = 0;
375
376               size = XRRConfigCurrentConfiguration (rrc, &rot);
377               rrsizes = XRRConfigSizes (rrc, &nsizes);
378
379               if (nsizes <= 0)  /* WTF?  Shouldn't happen but does. */
380                 {
381                   m->width  = DisplayWidth (dpy, i);
382                   m->height = DisplayHeight (dpy, i);
383                 }
384               else if (rot & (RR_Rotate_90|RR_Rotate_270))
385                 {
386                   m->width  = rrsizes[size].height;
387                   m->height = rrsizes[size].width;
388                 }
389               else
390                 {
391                   m->width  = rrsizes[size].width;
392                   m->height = rrsizes[size].height;
393                 }
394
395               /* don't free 'rrsizes' */
396               XRRFreeScreenConfigInfo (rrc);
397             }
398         }
399       else   /* RANDR 1.2 or newer */
400         {
401 # ifdef HAVE_RANDR_12
402           int k;
403           XRRScreenResources *res = 
404             XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
405           for (k = 0; k < res->noutput; k++)
406             {
407               monitor *m = (monitor *) calloc (1, sizeof (monitor));
408               XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, 
409                                                       res->outputs[k]);
410               RRCrtc crtc = (rroi->crtc ? rroi->crtc : rroi->crtcs[0]);
411               XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res, crtc);
412
413               monitors[j] = m;
414               m->screen   = screen;
415               m->id       = (i * 1000) + j;
416               m->desc     = (rroi->name ? strdup (rroi->name) : 0);
417               m->x        = crtci->x;
418               m->y        = crtci->y;
419
420               if (crtci->rotation & (RR_Rotate_90|RR_Rotate_270))
421                 {
422                   m->width  = crtci->height;
423                   m->height = crtci->width;
424                 }
425               else
426                 {
427                   m->width  = crtci->width;
428                   m->height = crtci->height;
429                 }
430
431               j++;
432
433               if (rroi->connection == RR_Disconnected)
434                 m->sanity = S_DISABLED;
435               /* #### do the same for RR_UnknownConnection? */
436
437               XRRFreeCrtcInfo (crtci);
438               XRRFreeOutputInfo (rroi);
439             }
440           XRRFreeScreenResources (res);
441 # endif /* HAVE_RANDR_12 */
442         }
443     }
444
445   return monitors;
446 }
447
448 #endif /* HAVE_RANDR */
449
450
451 static monitor **
452 basic_scan_monitors (Display *dpy)
453 {
454   int nscreens = ScreenCount (dpy);
455   int i;
456   monitor **monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
457   if (!monitors) return 0;
458
459   for (i = 0; i < nscreens; i++)
460     {
461       Screen *screen = ScreenOfDisplay (dpy, i);
462       monitor *m = (monitor *) calloc (1, sizeof (monitor));
463       monitors[i] = m;
464       m->id       = i;
465       m->screen   = screen;
466       m->x        = 0;
467       m->y        = 0;
468       m->width    = WidthOfScreen (screen);
469       m->height   = HeightOfScreen (screen);
470     }
471   return monitors;
472 }
473
474
475 #if defined(HAVE_RANDR) && defined(HAVE_XINERAMA)
476
477 /*   From: Aaron Plattner <aplattner@nvidia.com>
478      Date: August 7, 2008 10:21:25 AM PDT
479      To: linux-bugs@nvidia.com
480
481      The NVIDIA X driver does not yet support RandR 1.2.  The X server has
482      a compatibility layer in it that allows RandR 1.2 clients to talk to
483      RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default".
484      This reports the total combined resolution of the TwinView display,
485      since it doesn't have any visibility into TwinView metamodes.  There
486      is no way for the driver to prevent the server from turning on this
487      compatibility layer.
488
489      The intention is for X client applications to continue to use the
490      Xinerama extension to query the screen geometry.  RandR 1.2 reports
491      its own Xinerama info for this purpose.  I would recommend against
492      modifying xscreensaver to try to get this information from RandR.
493  */
494 static monitor **
495 randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors)
496 {
497   monitor **xinerama_monitors;
498
499   if (!randr_monitors) 
500     return 0;
501
502   xinerama_monitors = xinerama_scan_monitors (dpy);
503   if (!xinerama_monitors)
504     return randr_monitors;
505
506   if (! layouts_differ_p (randr_monitors, xinerama_monitors))
507     {
508       free_monitors (xinerama_monitors);
509       return randr_monitors;
510     }
511   else if (   randr_monitors[0] &&   !randr_monitors[1] &&  /* 1 monitor */
512            xinerama_monitors[0] && xinerama_monitors[1])    /* >1 monitor */
513     {
514       fprintf (stderr,
515                "%s: WARNING: RANDR reports 1 screen but Xinerama\n"
516                "%s:          reports multiple.  Believing Xinerama.\n",
517                blurb(), blurb());
518       free_monitors (randr_monitors);
519       return xinerama_monitors;
520     }
521   else
522     {
523       fprintf (stderr,
524                "%s: WARNING: RANDR and Xinerama report different\n"
525                "%s:          screen layouts!  Believing RANDR.\n",
526                blurb(), blurb());
527       free_monitors (xinerama_monitors);
528       return randr_monitors;
529     }
530 }
531
532 #endif /* HAVE_RANDR && HAVE_XINERAMA */
533
534
535 #ifdef DEBUG_MULTISCREEN
536
537 /* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
538    will pretend that it is changing the number of connected monitors
539    every few seconds, using the geometries in the following list,
540    for stress-testing purposes.
541  */
542 static monitor **
543 debug_scan_monitors (Display *dpy)
544 {
545   static const char * const geoms[] = {
546     "1600x1028+0+22",
547     "1024x768+0+22",
548     "800x600+0+22",
549     "800x600+0+22,800x600+800+22",
550     "800x600+0+22,800x600+800+22,800x600+300+622",
551     "800x600+0+22,800x600+800+22,800x600+0+622,800x600+800+622",
552     "640x480+0+22,640x480+640+22,640x480+0+502,640x480+640+502",
553     "640x480+240+22,640x480+0+502,640x480+640+502",
554     "640x480+0+200,640x480+640+200",
555     "800x600+400+22",
556     "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"
557   };
558   static int index = 0;
559   monitor **monitors = (monitor **) calloc (100, sizeof(*monitors));
560   int nscreens = 0;
561   Screen *screen = DefaultScreenOfDisplay (dpy);
562
563   char *s = strdup (geoms[index]);
564   char *token = strtok (s, ",");
565   while (token)
566     {
567       monitor *m = calloc (1, sizeof (monitor));
568       char c;
569       m->id = nscreens;
570       m->screen = screen;
571       if (4 != sscanf (token, "%dx%d+%d+%d%c", 
572                        &m->width, &m->height, &m->x, &m->y, &c))
573         abort();
574       m->width -= 2;
575       m->height -= 2;
576       monitors[nscreens++] = m;
577       token = strtok (0, ",");
578     }
579   free (s);
580   
581   index = (index+1) % countof(geoms);
582   return monitors;
583 }
584
585 #endif /* DEBUG_MULTISCREEN */
586
587
588 #ifdef QUAD_MODE
589 static monitor **
590 quadruple (monitor **monitors, Bool debug_p)
591 {
592   int i, j, count = 0;
593   monitor **monitors2;
594   while (monitors[count])
595     count++;
596   monitors2 = (monitor **) calloc (count * 4 + 1, sizeof(*monitors));
597   if (!monitors2) abort();
598
599   for (i = 0, j = 0; i < count; i++)
600     {
601       int k;
602       for (k = 0; k < 4; k++)
603         {
604           monitors2[j+k] = (monitor *) calloc (1, sizeof (monitor));
605           *monitors2[j+k] = *monitors[i];
606           monitors2[j+k]->width  /= (debug_p ? 4 : 2);
607           monitors2[j+k]->height /= 2;
608           monitors2[j+k]->id = (monitors[i]->id * 4) + k;
609           monitors2[j+k]->name = (monitors[i]->name
610                                   ? strdup (monitors[i]->name) : 0);
611         }
612       monitors2[j+1]->x += monitors2[j]->width;
613       monitors2[j+2]->y += monitors2[j]->height;
614       monitors2[j+3]->x += monitors2[j]->width;
615       monitors2[j+3]->y += monitors2[j]->height;
616       j += 4;
617     }
618
619   free_monitors (monitors);
620   return monitors2;
621 }
622 #endif /* QUAD_MODE */
623
624
625 static monitor **
626 scan_monitors (saver_info *si)
627 {
628   saver_preferences *p = &si->prefs;
629   monitor **monitors = 0;
630
631 # ifdef DEBUG_MULTISCREEN
632     if (! monitors) monitors = debug_scan_monitors (si->dpy);
633 # endif
634
635 # ifdef HAVE_RANDR
636   if (! p->getviewport_full_of_lies_p)
637     if (! monitors) monitors = randr_scan_monitors (si->dpy);
638
639 #  ifdef HAVE_XINERAMA
640    monitors = randr_versus_xinerama_fight (si->dpy, monitors);
641 #  endif
642 # endif /* HAVE_RANDR */
643
644 # ifdef HAVE_XF86VMODE
645   if (! monitors) monitors = vidmode_scan_monitors (si->dpy);
646 # endif
647
648 # ifdef HAVE_XINERAMA
649   if (! monitors) monitors = xinerama_scan_monitors (si->dpy);
650 # endif
651
652   if (! monitors) monitors = basic_scan_monitors (si->dpy);
653
654 # ifdef QUAD_MODE
655   if (p->quad_p)
656     monitors = quadruple (monitors, p->debug_p);
657 # endif
658
659   return monitors;
660 }
661
662
663 static Bool
664 monitors_overlap_p (monitor *a, monitor *b)
665 {
666   /* Two rectangles overlap if the max of the tops is less than the
667      min of the bottoms and the max of the lefts is less than the min
668      of the rights.
669    */
670 # undef MAX
671 # undef MIN
672 # define MAX(A,B) ((A)>(B)?(A):(B))
673 # define MIN(A,B) ((A)<(B)?(A):(B))
674
675   int maxleft  = MAX(a->x, b->x);
676   int maxtop   = MAX(a->y, b->y);
677   int minright = MIN(a->x + a->width  - 1, b->x + b->width);
678   int minbot   = MIN(a->y + a->height - 1, b->y + b->height);
679   return (maxtop < minbot && maxleft < minright);
680 }
681
682
683 static Bool
684 plausible_aspect_ratio_p (monitor **monitors)
685 {
686   /* Modern wide-screen monitors come in the following aspect ratios:
687
688             One monitor:        If you tack a 640x480 monitor
689                                 onto the right, the ratio is:
690          16 x 9    --> 1.78
691         852 x 480  --> 1.77        852+640 x 480  --> 3.11      "SD 480p"
692        1280 x 720  --> 1.78       1280+640 x 720  --> 2.67      "HD 720p"
693        1280 x 920  --> 1.39       1280+640 x 920  --> 2.09
694        1366 x 768  --> 1.78       1366+640 x 768  --> 2.61      "HD 768p"
695        1440 x 900  --> 1.60       1440+640 x 900  --> 2.31
696        1680 x 1050 --> 1.60       1680+640 x 1050 --> 2.21
697        1690 x 1050 --> 1.61       1690+640 x 1050 --> 2.22
698        1920 x 1080 --> 1.78       1920+640 x 1080 --> 2.37      "HD 1080p"
699        1920 x 1200 --> 1.60       1920+640 x 1200 --> 2.13
700        2560 x 1600 --> 1.60       2560+640 x 1600 --> 2.00
701
702      So that implies that if we ever see an aspect ratio >= 2.0,
703      we can be pretty sure that the X server is lying to us, and
704      that's actually two monitors, not one.
705    */
706   if (monitors[0] && !monitors[1] &&    /* exactly 1 monitor */
707       monitors[0]->height &&
708       monitors[0]->width / (double) monitors[0]->height >= 1.9)
709     return False;
710   else
711     return True;
712 }
713
714
715 /* Mark the ones that overlap, etc.
716  */
717 static void
718 check_monitor_sanity (monitor **monitors)
719 {
720   int i, j, count = 0;
721
722   while (monitors[count])
723     count++;
724
725 #  define X1 monitors[i]->x
726 #  define X2 monitors[j]->x
727 #  define Y1 monitors[i]->y
728 #  define Y2 monitors[j]->y
729 #  define W1 monitors[i]->width
730 #  define W2 monitors[j]->width
731 #  define H1 monitors[i]->height
732 #  define H2 monitors[j]->height
733
734   /* If a monitor is enclosed by any other monitor, that's insane.
735    */
736   for (i = 0; i < count; i++)
737     for (j = 0; j < count; j++)
738       if (i != j &&
739           monitors[i]->sanity == S_SANE &&
740           monitors[j]->sanity == S_SANE &&
741           monitors[i]->screen == monitors[j]->screen &&
742           X2 >= X1 &&
743           Y2 >= Y1 &&
744           (X2+W2) <= (X1+W1) &&
745           (Y2+H2) <= (Y1+H1))
746         {
747           if (X1 == X2 &&
748               Y1 == Y2 &&
749               W1 == W2 &&
750               H1 == H2)
751             monitors[j]->sanity = S_DUPLICATE;
752           else 
753             monitors[j]->sanity = S_ENCLOSED;
754           monitors[j]->enemy = i;
755         }
756
757   /* After checking for enclosure, check for other lossage against earlier
758      monitors.  We do enclosure first so that we make sure to pick the
759      larger one.
760    */
761   for (i = 0; i < count; i++)
762     for (j = 0; j < i; j++)
763       {
764         if (monitors[i]->sanity != S_SANE) continue; /* already marked */
765         if (monitors[j]->sanity != S_SANE) continue;
766         if (monitors[i]->screen != monitors[j]->screen) continue;
767
768         if (monitors_overlap_p (monitors[i], monitors[j]))
769           {
770             monitors[i]->sanity = S_OVERLAP;
771             monitors[i]->enemy = j;
772           }
773       }
774
775   /* Finally, make sure all monitors have sane positions and sizes.
776      Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
777    */
778   for (i = 0; i < count; i++)
779     {
780       if (monitors[i]->sanity != S_SANE) continue; /* already marked */
781       if (X1    <  0      || Y1    <  0 || 
782           W1    <= 0      || H1    <= 0 || 
783           X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF)
784         {
785           monitors[i]->sanity = S_OFFSCREEN;
786           monitors[i]->enemy = 0;
787         }
788     }
789
790 #  undef X1
791 #  undef X2
792 #  undef Y1
793 #  undef Y2
794 #  undef W1
795 #  undef W2
796 #  undef H1
797 #  undef H2
798 }
799
800
801 static Bool
802 layouts_differ_p (monitor **a, monitor **b)
803 {
804   if (!a || !b) return True;
805   while (1)
806     {
807       if (!*a) break;
808       if (!*b) break;
809       if ((*a)->screen != (*b)->screen ||
810           (*a)->x      != (*b)->x      ||
811           (*a)->y      != (*b)->y      ||
812           (*a)->width  != (*b)->width  ||
813           (*a)->height != (*b)->height)
814         return True;
815       a++;
816       b++;
817     }
818   if (*a) return True;
819   if (*b) return True;
820
821   return False;
822 }
823
824
825 void
826 describe_monitor_layout (saver_info *si)
827 {
828   monitor **monitors = si->monitor_layout;
829   int count = 0;
830   int good_count = 0;
831   int bad_count = 0;
832   int implausible_p = !plausible_aspect_ratio_p (monitors);
833
834   while (monitors[count])
835     {
836       if (monitors[count]->sanity == S_SANE)
837         good_count++;
838       else
839         bad_count++;
840       count++;
841     }
842
843   if (count == 0)
844     fprintf (stderr, "%s: no screens!\n", blurb());
845   else
846     {
847       int i;
848       fprintf (stderr, "%s: screens in use: %d\n", blurb(), good_count);
849       for (i = 0; i < count; i++)
850         {
851           monitor *m = monitors[i];
852           if (m->sanity != S_SANE) continue;
853           fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
854                    blurb(), m->id, screen_number (m->screen),
855                    m->width, m->height, m->x, m->y);
856           if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
857           fprintf (stderr, "\n");
858         }
859       if (bad_count > 0)
860         {
861           fprintf (stderr, "%s: rejected screens: %d\n", blurb(), bad_count);
862           for (i = 0; i < count; i++)
863             {
864               monitor *m = monitors[i];
865               monitor *e = monitors[m->enemy];
866               if (m->sanity == S_SANE) continue;
867               fprintf (stderr, "%s:  %3d/%d: %dx%d+%d+%d",
868                        blurb(), m->id, screen_number (m->screen),
869                        m->width, m->height, m->x, m->y);
870               if (m->desc && *m->desc) fprintf (stderr, " (%s)", m->desc);
871               fprintf (stderr, " -- ");
872               switch (m->sanity)
873                 {
874                 case S_SANE: abort(); break;
875                 case S_ENCLOSED:
876                   fprintf (stderr, "enclosed by %d (%dx%d+%d+%d)\n",
877                            e->id, e->width, e->height, e->x, e->y);
878                   break;
879                 case S_DUPLICATE:
880                   fprintf (stderr, "duplicate of %d\n", e->id);
881                   break;
882                 case S_OVERLAP:
883                   fprintf (stderr, "overlaps %d (%dx%d+%d+%d)\n",
884                            e->id, e->width, e->height, e->x, e->y);
885                   break;
886                 case S_OFFSCREEN:
887                   fprintf (stderr, "off screen (%dx%d)\n",
888                            WidthOfScreen (e->screen), 
889                            HeightOfScreen (e->screen));
890                   break;
891                 case S_DISABLED:
892                   fprintf (stderr, "output disabled\n");
893                   break;
894                 }
895             }
896         }
897
898       if (implausible_p)
899         fprintf (stderr,
900                  "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n"
901                  "%s:          probable X server bug in Xinerama/RANDR!\n",
902                  blurb(), monitors[0]->width, monitors[0]->height,
903                  monitors[0]->width / (double) monitors[0]->height,
904                  blurb());
905     }
906 }
907
908
909 /* Synchronize the contents of si->ssi to the current state of the monitors.
910    Doesn't change anything if nothing has changed; otherwise, alters and
911    reuses existing saver_screen_info structs as much as possible.
912    Returns True if anything changed.
913  */
914 Bool
915 update_screen_layout (saver_info *si)
916 {
917   monitor **monitors = scan_monitors (si);
918   int count = 0;
919   int good_count = 0;
920   int i, j;
921   int seen_screens[100] = { 0, };
922
923   if (! layouts_differ_p (monitors, si->monitor_layout))
924     {
925       free_monitors (monitors);
926       return False;
927     }
928
929   free_monitors (si->monitor_layout);
930   si->monitor_layout = monitors;
931   check_monitor_sanity (si->monitor_layout);
932
933   while (monitors[count])
934     {
935       if (monitors[count]->sanity == S_SANE)
936         good_count++;
937       count++;
938     }
939
940   if (si->ssi_count == 0)
941     {
942       si->ssi_count = 10;
943       si->screens = (saver_screen_info *)
944         calloc (sizeof(*si->screens), si->ssi_count);
945     }
946
947   if (si->ssi_count <= good_count)
948     {
949       si->ssi_count = good_count + 10;
950       si->screens = (saver_screen_info *)
951         realloc (si->screens, sizeof(*si->screens) * si->ssi_count);
952       memset (si->screens + si->nscreens, 0, 
953               sizeof(*si->screens) * (si->ssi_count - si->nscreens));
954     }
955
956   if (! si->screens) abort();
957
958   si->nscreens = good_count;
959
960   /* Regenerate the list of GL visuals as needed. */
961   if (si->best_gl_visuals)
962     free (si->best_gl_visuals);
963   si->best_gl_visuals = 0;
964
965   for (i = 0, j = 0; i < count; i++)
966     {
967       monitor *m = monitors[i];
968       saver_screen_info *ssi = &si->screens[j];
969       Screen *old_screen = ssi->screen;
970       int sn;
971       if (monitors[i]->sanity != S_SANE) continue;
972
973       ssi->global = si;
974       ssi->number = j;
975
976       sn = screen_number (m->screen);
977       ssi->screen = m->screen;
978       ssi->real_screen_number = sn;
979       ssi->real_screen_p = (seen_screens[sn] == 0);
980       seen_screens[sn]++;
981
982       ssi->default_visual =
983         get_visual_resource (ssi->screen, "visualID", "VisualID", False);
984       ssi->current_visual = ssi->default_visual;
985       ssi->current_depth = visual_depth (ssi->screen, ssi->current_visual);
986
987       /* If the screen changed (or if this is the first time) we need
988          a new toplevel shell for this screen's depth.
989        */
990       if (ssi->screen != old_screen)
991         initialize_screen_root_widget (ssi);
992
993       ssi->poll_mouse_last_root_x = -1;
994       ssi->poll_mouse_last_root_y = -1;
995
996       ssi->x      = m->x;
997       ssi->y      = m->y;
998       ssi->width  = m->width;
999       ssi->height = m->height;
1000
1001 # ifndef DEBUG_MULTISCREEN
1002       {
1003         saver_preferences *p = &si->prefs;
1004         if (p->debug_p
1005 #  ifdef QUAD_MODE
1006             && !p->quad_p
1007 #  endif
1008             )
1009           ssi->width /= 2;
1010       }
1011 # endif
1012
1013       j++;
1014     }
1015
1016   si->default_screen = &si->screens[0];
1017   return True;
1018 }