http://www.jwz.org/xscreensaver/xscreensaver-5.09.tar.gz
[xscreensaver] / driver / screens.c
index cef66b782850ac9dcbef843c3eccbb4df488d6b2..0a7eade847183601e61ffe5fbc754c9023c25f2d 100644 (file)
  *      to put seperate savers on those duplicated-or-overlapping
  *      monitors, xscreensaver just ignores them (which allows them to
  *      display duplicates or overlaps).
+ *
+ *  5a) Nvidia fucks it up:
+ *
+ *      Nvidia drivers as of Aug 2008 running in "TwinView" mode
+ *      apparently report correct screen geometry via Xinerama, but
+ *      report one giant screen via RANDR.  The response from the
+ *      nvidia developers is, "we don't support RANDR, use Xinerama
+ *      instead."  Which is a seriously lame answer.  So, xscreensaver
+ *      has to query *both* extensions, and make a guess as to which
+ *      is to be believed.
+ *
+ *  5b) Also sometimes RANDR says stupid shit like, "You have one
+ *      screen, and it has no available orientations or sizes."
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -141,8 +155,13 @@ struct _monitor {
   int x, y, width, height;
   monitor_sanity sanity;       /* I'm not crazy you're the one who's crazy */
   int enemy;                   /* which monitor it overlaps or duplicates */
+  char *err;                   /* msg to print at appropriate later time;
+                                   exists only on monitor #0. */
 };
 
+static Bool layouts_differ_p (monitor **a, monitor **b);
+
+
 static void
 free_monitors (monitor **monitors)
 {
@@ -151,6 +170,7 @@ free_monitors (monitor **monitors)
   while (*m2) 
     {
       if ((*m2)->desc) free ((*m2)->desc);
+      if ((*m2)->err) free ((*m2)->err);
       free (*m2);
       m2++;
     }
@@ -158,10 +178,24 @@ free_monitors (monitor **monitors)
 }
 
 
+static char *
+append (char *s1, const char *s2)
+{
+  char *s = (char *) malloc ((s1 ? strlen(s1) : 0) +
+                             (s2 ? strlen(s2) : 0) + 3);
+  *s = 0;
+  if (s1) strcat (s, s1);
+  if (s1 && s2) strcat (s, "\n");
+  if (s2) strcat (s, s2);
+  if (s1) free (s1);
+  return s;
+}
+
+
 #ifdef HAVE_XINERAMA
 
 static monitor **
-xinerama_scan_monitors (Display *dpy)
+xinerama_scan_monitors (Display *dpy, char **errP)
 {
   Screen *screen = DefaultScreenOfDisplay (dpy);
   int event, error, nscreens, i;
@@ -200,7 +234,7 @@ xinerama_scan_monitors (Display *dpy)
 #ifdef HAVE_XF86VMODE
 
 static monitor **
-vidmode_scan_monitors (Display *dpy)
+vidmode_scan_monitors (Display *dpy, char **errP)
 {
   int event, error, nscreens, i;
   monitor **monitors;
@@ -299,7 +333,7 @@ vidmode_scan_monitors (Display *dpy)
 #ifdef HAVE_RANDR
 
 static monitor **
-randr_scan_monitors (Display *dpy)
+randr_scan_monitors (Display *dpy, char **errP)
 {
   int event, error, major, minor, nscreens, i, j;
   monitor **monitors;
@@ -337,12 +371,19 @@ randr_scan_monitors (Display *dpy)
 # endif /* HAVE_RANDR_12 */
     }
 
+  if (nscreens <= 0)
+    {
+      *errP = append (*errP,
+                      "WARNING: RANDR reported no screens!  Ignoring it.");
+      return 0;
+    }
+
   monitors = (monitor **) calloc (nscreens + 1, sizeof(*monitors));
   if (!monitors) return 0;
 
   for (i = 0, j = 0; i < ScreenCount (dpy); i++)
     {
-      Screen *screen = ScreenOfDisplay (dpy, j);
+      Screen *screen = ScreenOfDisplay (dpy, i);
 
       if (! new_randr_p)  /* RANDR 1.0 */
         {
@@ -358,12 +399,17 @@ randr_scan_monitors (Display *dpy)
               SizeID size = -1;
               Rotation rot = ~0;
               XRRScreenSize *rrsizes;
-              int nsizes;
+              int nsizes = 0;
 
               size = XRRConfigCurrentConfiguration (rrc, &rot);
               rrsizes = XRRConfigSizes (rrc, &nsizes);
 
-              if (rot & (RR_Rotate_90|RR_Rotate_270))
+              if (nsizes <= 0)  /* WTF?  Shouldn't happen but does. */
+                {
+                  m->width  = DisplayWidth (dpy, i);
+                  m->height = DisplayHeight (dpy, i);
+                }
+              else if (rot & (RR_Rotate_90|RR_Rotate_270))
                 {
                   m->width  = rrsizes[size].height;
                   m->height = rrsizes[size].width;
@@ -384,39 +430,37 @@ randr_scan_monitors (Display *dpy)
           int k;
           XRRScreenResources *res = 
             XRRGetScreenResources (dpy, RootWindowOfScreen (screen));
-          for (k = 0; k < res->noutput; k++)
+          for (k = 0; k < res->noutput; k++, j++)
             {
               monitor *m = (monitor *) calloc (1, sizeof (monitor));
               XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, 
                                                       res->outputs[k]);
-              RRCrtc crtc = (rroi->crtc ? rroi->crtc : rroi->crtcs[0]);
-              XRRCrtcInfo *crtci = XRRGetCrtcInfo (dpy, res, crtc);
+              RRCrtc crtc = (rroi->crtc  ? rroi->crtc :
+                             rroi->ncrtc ? rroi->crtcs[0] : 0);
+              XRRCrtcInfo *crtci = (crtc ? XRRGetCrtcInfo(dpy, res, crtc) : 0);
 
               monitors[j] = m;
               m->screen   = screen;
               m->id       = (i * 1000) + j;
               m->desc     = (rroi->name ? strdup (rroi->name) : 0);
-              m->x        = crtci->x;
-              m->y        = crtci->y;
 
-              if (crtci->rotation & (RR_Rotate_90|RR_Rotate_270))
-                {
-                  m->width  = crtci->height;
-                  m->height = crtci->width;
-                }
-              else
+              if (crtci)
                 {
+                  /* Note: if the screen is rotated, XRRConfigSizes contains
+                     the unrotated WxH, but XRRCrtcInfo contains rotated HxW.
+                   */
+                  m->x      = crtci->x;
+                  m->y      = crtci->y;
                   m->width  = crtci->width;
                   m->height = crtci->height;
                 }
 
-              j++;
-
               if (rroi->connection == RR_Disconnected)
                 m->sanity = S_DISABLED;
               /* #### do the same for RR_UnknownConnection? */
 
-              XRRFreeCrtcInfo (crtci);
+              if (crtci) 
+                XRRFreeCrtcInfo (crtci);
               XRRFreeOutputInfo (rroi);
             }
           XRRFreeScreenResources (res);
@@ -424,6 +468,25 @@ randr_scan_monitors (Display *dpy)
         }
     }
 
+  /* Work around more fucking brain damage. */
+  {
+    int ok = 0;
+    int i = 0;
+    while (monitors[i]) 
+      {
+        if (monitors[i]->width != 0 && monitors[i]->height != 0)
+          ok++;
+        i++;
+      }
+    if (! ok)
+      {
+        *errP = append (*errP,
+              "WARNING: RANDR says all screens are 0x0!  Ignoring it.");
+        free_monitors (monitors);
+        monitors = 0;
+      }
+  }
+
   return monitors;
 }
 
@@ -431,7 +494,7 @@ randr_scan_monitors (Display *dpy)
 
 
 static monitor **
-basic_scan_monitors (Display *dpy)
+basic_scan_monitors (Display *dpy, char **errP)
 {
   int nscreens = ScreenCount (dpy);
   int i;
@@ -454,6 +517,65 @@ basic_scan_monitors (Display *dpy)
 }
 
 
+#if defined(HAVE_RANDR) && defined(HAVE_XINERAMA)
+
+/*   From: Aaron Plattner <aplattner@nvidia.com>
+     Date: August 7, 2008 10:21:25 AM PDT
+     To: linux-bugs@nvidia.com
+
+     The NVIDIA X driver does not yet support RandR 1.2.  The X server has
+     a compatibility layer in it that allows RandR 1.2 clients to talk to
+     RandR 1.1 drivers through an RandR 1.2 pseudo-output called "default".
+     This reports the total combined resolution of the TwinView display,
+     since it doesn't have any visibility into TwinView metamodes.  There
+     is no way for the driver to prevent the server from turning on this
+     compatibility layer.
+
+     The intention is for X client applications to continue to use the
+     Xinerama extension to query the screen geometry.  RandR 1.2 reports
+     its own Xinerama info for this purpose.  I would recommend against
+     modifying xscreensaver to try to get this information from RandR.
+ */
+static monitor **
+randr_versus_xinerama_fight (Display *dpy, monitor **randr_monitors, 
+                             char **errP)
+{
+  monitor **xinerama_monitors;
+
+  if (!randr_monitors) 
+    return 0;
+
+  xinerama_monitors = xinerama_scan_monitors (dpy, errP);
+  if (!xinerama_monitors)
+    return randr_monitors;
+
+  if (! layouts_differ_p (randr_monitors, xinerama_monitors))
+    {
+      free_monitors (xinerama_monitors);
+      return randr_monitors;
+    }
+  else if (   randr_monitors[0] &&   !randr_monitors[1] &&  /* 1 monitor */
+           xinerama_monitors[0] && xinerama_monitors[1])    /* >1 monitor */
+    {
+      *errP = append (*errP,
+                      "WARNING: RANDR reports 1 screen but Xinerama\n"
+                      "\t\treports multiple.  Believing Xinerama.");
+      free_monitors (randr_monitors);
+      return xinerama_monitors;
+    }
+  else
+    {
+      *errP = append (*errP,
+                      "WARNING: RANDR and Xinerama report different\n"
+                      "\t\tscreen layouts!  Believing RANDR.");
+      free_monitors (xinerama_monitors);
+      return randr_monitors;
+    }
+}
+
+#endif /* HAVE_RANDR && HAVE_XINERAMA */
+
+
 #ifdef DEBUG_MULTISCREEN
 
 /* If DEBUG_MULTISCREEN is defined, then in "-debug" mode, xscreensaver
@@ -462,7 +584,7 @@ basic_scan_monitors (Display *dpy)
    for stress-testing purposes.
  */
 static monitor **
-debug_scan_monitors (Display *dpy)
+debug_scan_monitors (Display *dpy, char **errP)
 {
   static const char * const geoms[] = {
     "1600x1028+0+22",
@@ -509,7 +631,7 @@ debug_scan_monitors (Display *dpy)
 
 #ifdef QUAD_MODE
 static monitor **
-quadruple (monitor **monitors, Bool debug_p)
+quadruple (monitor **monitors, Bool debug_p, char **errP)
 {
   int i, j, count = 0;
   monitor **monitors2;
@@ -549,31 +671,38 @@ scan_monitors (saver_info *si)
 {
   saver_preferences *p = &si->prefs;
   monitor **monitors = 0;
+  char *err = 0;
 
 # ifdef DEBUG_MULTISCREEN
-    if (! monitors) monitors = debug_scan_monitors (si->dpy);
+    if (! monitors) monitors = debug_scan_monitors (si->dpy, &err);
 # endif
 
 # ifdef HAVE_RANDR
   if (! p->getviewport_full_of_lies_p)
-    if (! monitors) monitors = randr_scan_monitors (si->dpy);
-# endif
+    if (! monitors) monitors = randr_scan_monitors (si->dpy, &err);
+
+#  ifdef HAVE_XINERAMA
+   monitors = randr_versus_xinerama_fight (si->dpy, monitors, &err);
+#  endif
+# endif /* HAVE_RANDR */
 
 # ifdef HAVE_XF86VMODE
-  if (! monitors) monitors = vidmode_scan_monitors (si->dpy);
+  if (! monitors) monitors = vidmode_scan_monitors (si->dpy, &err);
 # endif
 
-# ifdef HAVE_XF86VMODE
-  if (! monitors) monitors = xinerama_scan_monitors (si->dpy);
+# ifdef HAVE_XINERAMA
+  if (! monitors) monitors = xinerama_scan_monitors (si->dpy, &err);
 # endif
 
-  if (! monitors) monitors = basic_scan_monitors (si->dpy);
+  if (! monitors) monitors = basic_scan_monitors (si->dpy, &err);
 
 # ifdef QUAD_MODE
   if (p->quad_p)
-    monitors = quadruple (monitors, p->debug_p);
+    monitors = quadruple (monitors, p->debug_p, &err);
 # endif
 
+  if (monitors && err) monitors[0]->err = err;
+
   return monitors;
 }
 
@@ -598,6 +727,38 @@ monitors_overlap_p (monitor *a, monitor *b)
 }
 
 
+static Bool
+plausible_aspect_ratio_p (monitor **monitors)
+{
+  /* Modern wide-screen monitors come in the following aspect ratios:
+
+            One monitor:        If you tack a 640x480 monitor
+                                onto the right, the ratio is:
+         16 x 9    --> 1.78
+        852 x 480  --> 1.77        852+640 x 480  --> 3.11      "SD 480p"
+       1280 x 720  --> 1.78       1280+640 x 720  --> 2.67      "HD 720p"
+       1280 x 920  --> 1.39       1280+640 x 920  --> 2.09
+       1366 x 768  --> 1.78       1366+640 x 768  --> 2.61      "HD 768p"
+       1440 x 900  --> 1.60       1440+640 x 900  --> 2.31
+       1680 x 1050 --> 1.60       1680+640 x 1050 --> 2.21
+       1690 x 1050 --> 1.61       1690+640 x 1050 --> 2.22
+       1920 x 1080 --> 1.78       1920+640 x 1080 --> 2.37      "HD 1080p"
+       1920 x 1200 --> 1.60       1920+640 x 1200 --> 2.13
+       2560 x 1600 --> 1.60       2560+640 x 1600 --> 2.00
+
+     So that implies that if we ever see an aspect ratio >= 2.0,
+     we can be pretty sure that the X server is lying to us, and
+     that's actually two monitors, not one.
+   */
+  if (monitors[0] && !monitors[1] &&    /* exactly 1 monitor */
+      monitors[0]->height &&
+      monitors[0]->width / (double) monitors[0]->height >= 1.9)
+    return False;
+  else
+    return True;
+}
+
+
 /* Mark the ones that overlap, etc.
  */
 static void
@@ -624,6 +785,7 @@ check_monitor_sanity (monitor **monitors)
       if (i != j &&
           monitors[i]->sanity == S_SANE &&
           monitors[j]->sanity == S_SANE &&
+          monitors[i]->screen == monitors[j]->screen &&
           X2 >= X1 &&
           Y2 >= Y1 &&
           (X2+W2) <= (X1+W1) &&
@@ -648,6 +810,7 @@ check_monitor_sanity (monitor **monitors)
       {
         if (monitors[i]->sanity != S_SANE) continue; /* already marked */
         if (monitors[j]->sanity != S_SANE) continue;
+        if (monitors[i]->screen != monitors[j]->screen) continue;
 
         if (monitors_overlap_p (monitors[i], monitors[j]))
           {
@@ -656,17 +819,15 @@ check_monitor_sanity (monitor **monitors)
           }
       }
 
-  /* Finally, make sure all monitors are enclosed by their X screen.
+  /* Finally, make sure all monitors have sane positions and sizes.
      Xinerama sometimes reports 1024x768 VPs at -1936862040, -1953705044.
    */
   for (i = 0; i < count; i++)
     {
-      int sw = WidthOfScreen (monitors[i]->screen)  * 2;
-      int sh = HeightOfScreen (monitors[i]->screen) * 2;
       if (monitors[i]->sanity != S_SANE) continue; /* already marked */
-      if (X1    <  0 || Y1    <  0 || 
-          W1    <= 0 || H1    <= 0 || 
-          X1+W1 > sw || Y1+H1 > sh)
+      if (X1    <  0      || Y1    <  0 || 
+          W1    <= 0      || H1    <= 0 || 
+          X1+W1 >= 0x7FFF || Y1+H1 >= 0x7FFF)
         {
           monitors[i]->sanity = S_OFFSCREEN;
           monitors[i]->enemy = 0;
@@ -715,6 +876,8 @@ describe_monitor_layout (saver_info *si)
   int count = 0;
   int good_count = 0;
   int bad_count = 0;
+  int implausible_p = !plausible_aspect_ratio_p (monitors);
+
   while (monitors[count])
     {
       if (monitors[count]->sanity == S_SANE)
@@ -724,6 +887,18 @@ describe_monitor_layout (saver_info *si)
       count++;
     }
 
+  if (monitors[0]->err)                /* deferred error msg */
+    {
+      char *token = strtok (monitors[0]->err, "\n");
+      while (token)
+        {
+          fprintf (stderr, "%s: %s\n", blurb(), token);
+          token = strtok (0, "\n");
+        }
+      free (monitors[0]->err);
+      monitors[0]->err = 0;
+    }
+
   if (count == 0)
     fprintf (stderr, "%s: no screens!\n", blurb());
   else
@@ -778,6 +953,14 @@ describe_monitor_layout (saver_info *si)
                 }
             }
         }
+
+      if (implausible_p)
+        fprintf (stderr,
+                 "%s: WARNING: single screen aspect ratio is %dx%d = %.2f\n"
+                 "%s:          probable X server bug in Xinerama/RANDR!\n",
+                 blurb(), monitors[0]->width, monitors[0]->height,
+                 monitors[0]->width / (double) monitors[0]->height,
+                 blurb());
     }
 }