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