X11: Fixed XRandR display detection.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 07 Aug 2015 01:02:35 -0400
changeset 9834 d3fa6d0d3793
parent 9833 c3214bb33a1d
child 9835 5b266c6b0ac6
X11: Fixed XRandR display detection. Previously this only worked on X11 when Xinerama was carrying the weight. Fixes Bugzilla #3062.
src/video/x11/SDL_x11modes.c
src/video/x11/SDL_x11sym.h
--- a/src/video/x11/SDL_x11modes.c	Fri Aug 07 01:00:14 2015 -0400
+++ b/src/video/x11/SDL_x11modes.c	Fri Aug 07 01:02:35 2015 -0400
@@ -244,10 +244,12 @@
     }
 
     /* Query the extension version */
+    *major = 1; *minor = 3;  /* we want 1.3 */
     if (!X11_XRRQueryVersion(display, major, minor)) {
 #ifdef X11MODES_DEBUG
         printf("XRandR not active on the display\n");
 #endif
+        *major = *minor = 0;
         return SDL_FALSE;
     }
 #ifdef X11MODES_DEBUG
@@ -267,20 +269,20 @@
 }
 
 static SDL_bool
-SetXRandRModeInfo(Display *display, XRRScreenResources *res, XRROutputInfo *output_info,
+SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc,
                   RRMode modeID, SDL_DisplayMode *mode)
 {
     int i;
     for (i = 0; i < res->nmode; ++i) {
-        if (res->modes[i].id == modeID) {
-            XRRCrtcInfo *crtc;
+        const XRRModeInfo *info = &res->modes[i];
+        if (info->id == modeID) {
+            XRRCrtcInfo *crtcinfo;
             Rotation rotation = 0;
-            const XRRModeInfo *info = &res->modes[i];
 
-            crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc);
-            if (crtc) {
-                rotation = crtc->rotation;
-                X11_XRRFreeCrtcInfo(crtc);
+            crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc);
+            if (crtcinfo) {
+                rotation = crtcinfo->rotation;
+                X11_XRRFreeCrtcInfo(crtcinfo);
             }
 
             if (rotation & (XRANDR_ROTATION_LEFT|XRANDR_ROTATION_RIGHT)) {
@@ -300,6 +302,203 @@
     }
     return SDL_FALSE;
 }
+
+static void
+SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm)
+{
+    /* See if we can get the EDID data for the real monitor name */
+    int inches;
+    int nprop;
+    Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop);
+    int i;
+
+    for (i = 0; i < nprop; ++i) {
+        unsigned char *prop;
+        int actual_format;
+        unsigned long nitems, bytes_after;
+        Atom actual_type;
+
+        if (props[i] == EDID) {
+            if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False,
+                                         False, AnyPropertyType, &actual_type,
+                                         &actual_format, &nitems, &bytes_after,
+                                         &prop) == Success) {
+                MonitorInfo *info = decode_edid(prop);
+                if (info) {
+#ifdef X11MODES_DEBUG
+                    printf("Found EDID data for %s\n", name);
+                    dump_monitor_info(info);
+#endif
+                    SDL_strlcpy(name, info->dsc_product_name, namelen);
+                    free(info);
+                }
+                X11_XFree(prop);
+            }
+            break;
+        }
+    }
+
+    if (props) {
+        X11_XFree(props);
+    }
+
+    inches = (int)((SDL_sqrt(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f);
+    if (*name && inches) {
+        const size_t len = SDL_strlen(name);
+        SDL_snprintf(&name[len], namelen-len, " %d\"", inches);
+    }
+
+#ifdef X11MODES_DEBUG
+    printf("Display name: %s\n", name);
+#endif
+}
+
+
+int
+X11_InitModes_XRandR(_THIS)
+{
+    /* In theory, you _could_ have multiple screens (like DISPLAY=:0.0
+       and DISPLAY=:0.1) but no XRandR system we care about is like this,
+       as all the physical displays would be separate XRandR "outputs" on
+       the one X11 virtual "screen". So we don't use ScreenCount() here. */
+
+    SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
+    Display *dpy = data->display;
+    Atom EDID = X11_XInternAtom(dpy, "EDID", False);
+    const int screen = DefaultScreen(dpy);
+    RROutput primary;
+    XRRScreenResources *res = NULL;
+    Uint32 pixelformat;
+    XVisualInfo vinfo;
+    XPixmapFormatValues *pixmapformats;
+    int looking_for_primary;
+    int scanline_pad;
+    int output;
+    int i, n;
+
+    if (get_visualinfo(dpy, screen, &vinfo) < 0) {
+        return -1;
+    }
+
+    pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
+    if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
+        return SDL_SetError("Palettized video modes are no longer supported");
+    }
+
+    scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
+    pixmapformats = X11_XListPixmapFormats(dpy, &n);
+    if (pixmapformats) {
+        for (i = 0; i < n; ++i) {
+            if (pixmapformats[i].depth == vinfo.depth) {
+                scanline_pad = pixmapformats[i].scanline_pad;
+                break;
+            }
+        }
+        X11_XFree(pixmapformats);
+    }
+
+    res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen));
+    if (!res) {
+        return -1;
+    }
+
+    primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, screen));
+
+    for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) {
+        for (output = 0; output < res->noutput; output++) {
+            XRROutputInfo *output_info;
+            int display_x, display_y;
+            unsigned long display_mm_width, display_mm_height;
+            SDL_DisplayData *displaydata;
+            char display_name[128];
+            SDL_DisplayMode mode;
+            SDL_DisplayModeData *modedata;
+            SDL_VideoDisplay display;
+            RRMode modeID;
+            RRCrtc output_crtc;
+            XRRCrtcInfo *crtc;
+
+            /* The primary output _should_ always be sorted first, but just in case... */
+            if ((looking_for_primary && (res->outputs[output] != primary)) ||
+                (!looking_for_primary && (res->outputs[output] == primary))) {
+                continue;
+            }
+
+            output_info = X11_XRRGetOutputInfo(dpy, res, res->outputs[output]);
+            if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) {
+                X11_XRRFreeOutputInfo(output_info);
+                continue;
+            }
+
+            SDL_strlcpy(display_name, output_info->name, sizeof(display_name));
+            display_mm_width = output_info->mm_width;
+            display_mm_height = output_info->mm_height;
+            output_crtc = output_info->crtc;
+            X11_XRRFreeOutputInfo(output_info);
+
+            crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc);
+            if (!crtc) {
+                continue;
+            }
+
+            SDL_zero(mode);
+            modeID = crtc->mode;
+            mode.w = crtc->width;
+            mode.h = crtc->height;
+            mode.format = pixelformat;
+
+            display_x = crtc->x;
+            display_y = crtc->y;
+
+            X11_XRRFreeCrtcInfo(crtc);
+
+            displaydata = (SDL_DisplayData *) SDL_calloc(1, sizeof(*displaydata));
+            if (!displaydata) {
+                return SDL_OutOfMemory();
+            }
+
+            modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData));
+            if (!modedata) {
+                SDL_free(displaydata);
+                return SDL_OutOfMemory();
+            }
+            modedata->xrandr_mode = modeID;
+            mode.driverdata = modedata;
+
+            displaydata->screen = screen;
+            displaydata->visual = vinfo.visual;
+            displaydata->depth = vinfo.depth;
+            displaydata->hdpi = ((float) mode.w) * 25.4f / display_mm_width;
+            displaydata->vdpi = ((float) mode.h) * 25.4f / display_mm_height;
+            displaydata->ddpi = SDL_ComputeDiagonalDPI(mode.w, mode.h, ((float) display_mm_width) / 25.4f,((float) display_mm_height) / 25.4f);
+            displaydata->scanline_pad = scanline_pad;
+            displaydata->x = display_x;
+            displaydata->y = display_y;
+            displaydata->use_xrandr = 1;
+            displaydata->xrandr_output = res->outputs[output];
+
+            SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
+            SetXRandRDisplayName(dpy, EDID, display_name, sizeof (display_name), res->outputs[output], display_mm_width, display_mm_height);
+
+            SDL_zero(display);
+            if (*display_name) {
+                display.name = display_name;
+            }
+            display.desktop_mode = mode;
+            display.current_mode = mode;
+            display.driverdata = displaydata;
+            SDL_AddVideoDisplay(&display);
+        }
+    }
+
+    X11_XRRFreeScreenResources(res);
+
+    if (_this->num_displays == 0) {
+        return SDL_SetError("No available displays");
+    }
+
+    return 0;
+}
 #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
 
 #if SDL_VIDEO_DRIVER_X11_XVIDMODE
@@ -399,14 +598,24 @@
 #endif
 #if SDL_VIDEO_DRIVER_X11_XRANDR
     int xrandr_major, xrandr_minor;
-    int use_xrandr = 0;
-    XRRScreenResources *res = NULL;
 #endif
 #if SDL_VIDEO_DRIVER_X11_XVIDMODE
     int vm_major, vm_minor;
     int use_vidmode = 0;
 #endif
 
+/* XRandR is the One True Modern Way to do this on X11. If it's enabled and
+   available, don't even look at other ways of doing things. */
+#if SDL_VIDEO_DRIVER_X11_XRANDR
+    /* require at least XRandR v1.3 */
+    if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) &&
+        (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3))) {
+        return X11_InitModes_XRandR(_this);
+    }
+#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
+
+/* !!! FIXME: eventually remove support for Xinerama and XVidMode (everything below here). */
+
 #if SDL_VIDEO_DRIVER_X11_XINERAMA
     /* Query Xinerama extention
      * NOTE: This works with Nvidia Twinview correctly, but you need version 302.17 (released on June 2012)
@@ -433,14 +642,6 @@
     screencount = ScreenCount(data->display);
 #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */
 
-#if SDL_VIDEO_DRIVER_X11_XRANDR
-    /* require at least XRandR v1.2 */
-    if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) &&
-        (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 2))) {
-        use_xrandr = xrandr_major * 100 + xrandr_minor;
-    }
-#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
-
 #if SDL_VIDEO_DRIVER_X11_XVIDMODE
     if (CheckVidMode(data->display, &vm_major, &vm_minor)) {
         use_vidmode = vm_major * 100 + vm_minor;
@@ -569,106 +770,6 @@
             displaydata->y = 0;
         }
 
-#if SDL_VIDEO_DRIVER_X11_XRANDR
-        if (use_xrandr) {
-            res = X11_XRRGetScreenResources(data->display, RootWindow(data->display, displaydata->screen));
-        }
-        if (res) {
-            XRROutputInfo *output_info;
-            XRRCrtcInfo *crtc;
-            int output;
-            Atom EDID = X11_XInternAtom(data->display, "EDID", False);
-            Atom *props;
-            int nprop;
-            unsigned long width_mm;
-            unsigned long height_mm;
-            int inches = 0;
-
-            for (output = 0; output < res->noutput; output++) {
-                output_info = X11_XRRGetOutputInfo(data->display, res, res->outputs[output]);
-                if (!output_info || !output_info->crtc ||
-                    output_info->connection == RR_Disconnected) {
-                    X11_XRRFreeOutputInfo(output_info);
-                    continue;
-                }
-
-                /* Is this the output that corresponds to the current screen?
-                   We're checking the crtc position, but that may not be a valid test
-                   in all cases.  Anybody want to give this some love?
-                 */
-                crtc = X11_XRRGetCrtcInfo(data->display, res, output_info->crtc);
-                if (!crtc || crtc->x != displaydata->x || crtc->y != displaydata->y ||
-                    crtc->width != mode.w || crtc->height != mode.h) {
-                    X11_XRRFreeOutputInfo(output_info);
-                    X11_XRRFreeCrtcInfo(crtc);
-                    continue;
-                }
-
-                displaydata->use_xrandr = use_xrandr;
-                displaydata->xrandr_output = res->outputs[output];
-                SetXRandRModeInfo(data->display, res, output_info, crtc->mode, &mode);
-
-                /* Get the name of this display */
-                width_mm = output_info->mm_width;
-                height_mm = output_info->mm_height;
-                inches = (int)((sqrt(width_mm * width_mm +
-                                     height_mm * height_mm) / 25.4f) + 0.5f);
-                SDL_strlcpy(display_name, output_info->name, sizeof(display_name));
-
-                /* See if we can get the EDID data for the real monitor name */
-                props = X11_XRRListOutputProperties(data->display, res->outputs[output], &nprop);
-                for (i = 0; i < nprop; ++i) {
-                    unsigned char *prop;
-                    int actual_format;
-                    unsigned long nitems, bytes_after;
-                    Atom actual_type;
-
-                    if (props[i] == EDID) {
-                        if (X11_XRRGetOutputProperty(data->display,
-                                                 res->outputs[output], props[i],
-                                                 0, 100, False, False,
-                                                 AnyPropertyType,
-                                                 &actual_type, &actual_format,
-                                                 &nitems, &bytes_after, &prop) == Success ) {
-                            MonitorInfo *info = decode_edid(prop);
-                            if (info) {
-    #ifdef X11MODES_DEBUG
-                                printf("Found EDID data for %s\n", output_info->name);
-                                dump_monitor_info(info);
-    #endif
-                                SDL_strlcpy(display_name, info->dsc_product_name, sizeof(display_name));
-                                free(info);
-                            }
-                            X11_XFree(prop);
-                        }
-                        break;
-                    }
-                }
-                if (props) {
-                    X11_XFree(props);
-                }
-
-                if (*display_name && inches) {
-                    size_t len = SDL_strlen(display_name);
-                    SDL_snprintf(&display_name[len], sizeof(display_name)-len, " %d\"", inches);
-                }
-#ifdef X11MODES_DEBUG
-                printf("Display name: %s\n", display_name);
-#endif
-
-                X11_XRRFreeOutputInfo(output_info);
-                X11_XRRFreeCrtcInfo(crtc);
-                break;
-            }
-#ifdef X11MODES_DEBUG
-            if (output == res->noutput) {
-                printf("Couldn't find XRandR CRTC at %d,%d\n", displaydata->x, displaydata->y);
-            }
-#endif
-            X11_XRRFreeScreenResources(res);
-        }
-#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
-
 #if SDL_VIDEO_DRIVER_X11_XVIDMODE
         if (!displaydata->use_xrandr &&
 #if SDL_VIDEO_DRIVER_X11_XINERAMA
@@ -791,7 +892,7 @@
                     }
                     mode.driverdata = modedata;
 
-                    if (!SetXRandRModeInfo(display, res, output_info, output_info->modes[i], &mode) ||
+                    if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) ||
                         !SDL_AddDisplayMode(sdl_display, &mode)) {
                         SDL_free(modedata);
                     }
--- a/src/video/x11/SDL_x11sym.h	Fri Aug 07 01:00:14 2015 -0400
+++ b/src/video/x11/SDL_x11sym.h	Fri Aug 07 01:02:35 2015 -0400
@@ -283,6 +283,7 @@
 SDL_X11_SYM(Atom*,XRRListOutputProperties,(Display *dpy, RROutput output, int *nprop),(dpy,output,nprop),return)
 SDL_X11_SYM(XRRPropertyInfo*,XRRQueryOutputProperty,(Display *dpy,RROutput output, Atom property),(dpy,output,property),return)
 SDL_X11_SYM(int,XRRGetOutputProperty,(Display *dpy,RROutput output, Atom property, long offset, long length, Bool _delete, Bool pending, Atom req_type, Atom *actual_type, int *actual_format, unsigned long *nitems, unsigned long *bytes_after, unsigned char **prop),(dpy,output,property,offset,length, _delete, pending, req_type, actual_type, actual_format, nitems, bytes_after, prop),return)
+SDL_X11_SYM(RROutput,XRRGetOutputPrimary,(Display *dpy,Window window),(dpy,window),return)
 #endif
 
 /* MIT-SCREEN-SAVER support */