Fixed XRandR code to change resolution on the correct monitor in a multi-monitor setup.
authorSam Lantinga <slouken@libsdl.org>
Wed, 03 Oct 2012 11:33:09 -0700
changeset 6537 85cf169db434
parent 6536 d761a7490c4a
child 6538 05cf19d1eb53
Fixed XRandR code to change resolution on the correct monitor in a multi-monitor setup.
src/video/x11/SDL_x11modes.c
src/video/x11/SDL_x11sym.h
--- a/src/video/x11/SDL_x11modes.c	Tue Oct 02 23:23:44 2012 -0700
+++ b/src/video/x11/SDL_x11modes.c	Wed Oct 03 11:33:09 2012 -0700
@@ -25,7 +25,7 @@
 #include "SDL_hints.h"
 #include "SDL_x11video.h"
 
-/*#define X11MODES_DEBUG*/
+#define X11MODES_DEBUG
 
 static int
 get_visualinfo(Display * display, int screen, XVisualInfo * vinfo)
@@ -339,6 +339,21 @@
 #endif
     return SDL_TRUE;
 }
+
+#define XRANDR_ROTATION_LEFT    (1 << 1)
+#define XRANDR_ROTATION_RIGHT   (1 << 3)
+
+static void
+get_xrandr_mode_size(XRRModeInfo *mode, Rotation rotation, int *w, int *h)
+{
+    if (rotation & (XRANDR_ROTATION_LEFT|XRANDR_ROTATION_RIGHT)) {
+        *w = mode->height;
+        *h = mode->width;
+    } else {
+        *w = mode->width;
+        *h = mode->height;
+    }
+}
 #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */
 
 #if SDL_VIDEO_DRIVER_X11_XVIDMODE
@@ -517,6 +532,7 @@
         if (nsizes > 0) {
             int i, j;
             for (i = 0; i < nsizes; i++) {
+                get_xrandr_mode_size(XRRModeInfo *mode, Rotation rotation, int *w, int *h)
                 mode.w = sizes[i].width;
                 mode.h = sizes[i].height;
 
@@ -685,48 +701,150 @@
         XRRScreenSize *sizes;
         short *rates;
 
-        /* find the smallest resolution that is at least as big as the user requested */
-        best = -1;
-        sizes = XRRConfigSizes(data->screen_config, &nsizes);
-        for (i = 0; i < nsizes; ++i) {
-            if (sizes[i].width < w || sizes[i].height < h) {
-                continue;
+#if SDL_VIDEO_DRIVER_X11_XINERAMA
+        if (data->use_xrandr >= 102 && data->use_xinerama) {
+            /* See http://cgit.freedesktop.org/xorg/app/xrandr/tree/xrandr.c for the rationale behind this */
+            /* Note by Gabriel: the refresh rate is ignored in this code, and seeing that both xrandr
+             * and nvidia-settings don't provide a way to set it when Xinerama is enabled, it may not be possible to set it */
+            int screencount;
+            XineramaScreenInfo * xinerama = NULL;
+            XRRScreenResources  *res;
+
+            /* Update the current screen layout information */
+            xinerama = XineramaQueryScreens(display, &screencount);
+            if (xinerama && data->xinerama_screen < screencount) {
+                data->xinerama_info = xinerama[data->xinerama_screen];
             }
-            if (sizes[i].width == w && sizes[i].height == h) {
-                best = i;
-                break;
-            }
-            if (best == -1 ||
-                (sizes[i].width < sizes[best].width) ||
-                (sizes[i].width == sizes[best].width
-                 && sizes[i].height < sizes[best].height)) {
-                best = i;
+            if (xinerama) XFree(xinerama);
+
+            res = XRRGetScreenResources (display, RootWindow(display, data->screen));
+            if (res) {
+                XRROutputInfo *output_info = NULL;
+                XRRCrtcInfo *crtc = NULL;
+                XRRModeInfo *mode_info, *best_mode_info = NULL;
+                int mode_index = 0, output_mode_index = 0, output;
+                int mode_w, mode_h, best_w = 0, best_h = 0;
+
+                for ( output = 0; output < res->noutput; output++) {
+                    output_info = XRRGetOutputInfo (display, res, res->outputs[output]);
+                    if (!output_info || !output_info->crtc) {
+                        XRRFreeOutputInfo(output_info);
+                        continue;
+                    }
+
+                    crtc = XRRGetCrtcInfo (display, res, output_info->crtc);
+                    if (!crtc || data->xinerama_info.x_org != crtc->x || data->xinerama_info.y_org != crtc->y) {
+                        XRRFreeCrtcInfo(crtc);
+                        continue;
+                    }
+
+                    /* The CRT offset matches the Xinerama mode, let's hope it's the right one! */
+#ifdef X11MODES_DEBUG
+                    fprintf(stderr, "XRANDR: set_best_resolution, matched Xinerama screen %d to CRT %d at %d,%d\n",
+                        data->xinerama_info.screen_number, output_info->crtc, crtc->x, crtc->y);
+#endif
+                    /* Find out the best mode we can use */
+                    for (mode_index = 0; mode_index < res->nmode; mode_index++) {
+                        mode_info = &res->modes[mode_index];
+                        get_xrandr_mode_size(mode_info, crtc->rotation, &mode_w, &mode_h);
+                        if (mode_w >= w && mode_h >= h) {
+                            /* This may be a useful mode, check out if it belongs to the correct output */
+#ifdef X11MODES_DEBUG
+                            fprintf(stderr, "Evaluating valid mode %d, w: %d, h: %d\n", mode_index, mode_info->width, mode_info->height);
+#endif
+                            for ( output_mode_index = 0; output_mode_index < output_info->nmode; output_mode_index++) {
+                                if (output_info->modes[output_mode_index] == mode_info->id) {
+                                    break;
+                                }
+                            }
+                            if (output_mode_index < output_info->nmode) {
+#ifdef X11MODES_DEBUG
+                                fprintf(stderr, "Mode belongs to the desired output %d w: %d, h: %d, rotation: %x\n", mode_index, mode_info->width, mode_info->height, crtc->rotation);
+#endif
+                                /* We have a mode that belongs to the right output and that can contain the w,h required, see if it's smaller than the current one */
+                                if ( !best_mode_info || mode_w < best_w || (mode_w == best_w && mode_h < best_h) ) {
+                                    best_mode_info = mode_info;
+                                    best_w = mode_w;
+                                    best_h = mode_h;
+                                }
+                            }
+#ifdef X11MODES_DEBUG
+                            else {
+                                fprintf(stderr, "Discarding mode because it does not belong to the desired output %d, w: %d, h: %d, rotation: %d\n", mode_index, mode_info->width, mode_info->height, crtc->rotation);
+                            }
+#endif
+                        }
+                    }
+
+                    if (best_mode_info) {
+#ifdef X11MODES_DEBUG
+                            fprintf(stderr, "XRANDR: set_best_resolution, setting mode w = %d, h = %d on output: %d, CRT with offsets: %d,%d for Xinerama screen: %d\n",
+                                    best_mode_info->width, best_mode_info->height, output, crtc->x, crtc->y, data->xinerama_info.screen_number);
+#endif
+                        XGrabServer (display);
+                        XRRSetCrtcConfig (display, res, output_info->crtc, CurrentTime,
+                          crtc->x, crtc->y, best_mode_info->id, crtc->rotation,
+                          &res->outputs[output], 1);
+                        /* TODO: Handle screen rotations, should we call XRRSetCrtcTransform if Xrandr >=1.3 ? */
+                        XUngrabServer (display);
+                    } else {
+                        /* If we reach here, we have found the right screen but no valid best mode */
+                        SDL_SetError("The selected screen can't support a resolution of the size desired");
+                    }
+                    XRRFreeCrtcInfo(crtc);
+                    XRRFreeOutputInfo(output_info);
+                    break;
+                }
+                XRRFreeScreenResources(res);
             }
         }
-
-        if (best >= 0) {
-            best_rate = 0;
-            rates = XRRConfigRates(data->screen_config, best, &nrates);
-            for (i = 0; i < nrates; ++i) {
-                if (rates[i] == rate) {
-                    best_rate = rate;
+        else
+#endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */
+        {
+            /* Use older xrandr functions that don't play along very nicely with multi monitors setups */
+            /* find the smallest resolution that is at least as big as the user requested */
+            best = -1;
+            sizes = XRRConfigSizes(data->screen_config, &nsizes);
+            for (i = 0; i < nsizes; ++i) {
+                if (sizes[i].width < w || sizes[i].height < h) {
+                    continue;
+                }
+                if (sizes[i].width == w && sizes[i].height == h) {
+                    best = i;
                     break;
                 }
-                if (!rate) {
-                    /* Higher is better, right? */
-                    if (rates[i] > best_rate) {
-                        best_rate = rates[i];
+                if (best == -1 ||
+                    (sizes[i].width < sizes[best].width) ||
+                    (sizes[i].width == sizes[best].width
+                     && sizes[i].height < sizes[best].height)) {
+                    best = i;
+                }
+            }
+
+            if (best >= 0) {
+                best_rate = 0;
+                rates = XRRConfigRates(data->screen_config, best, &nrates);
+                for (i = 0; i < nrates; ++i) {
+                    if (rates[i] == rate) {
+                        best_rate = rate;
+                        break;
                     }
-                } else {
-                    if (SDL_abs(rates[i] - rate) < SDL_abs(best_rate - rate)) {
-                        best_rate = rates[i];
+                    if (!rate) {
+                        /* Higher is better, right? */
+                        if (rates[i] > best_rate) {
+                            best_rate = rates[i];
+                        }
+                    } else {
+                        if (SDL_abs(rates[i] - rate) < SDL_abs(best_rate - rate)) {
+                            best_rate = rates[i];
+                        }
                     }
                 }
+                XRRSetScreenConfigAndRate(display, data->screen_config,
+                                          RootWindow(display, data->screen), best,
+                                          data->saved_rotation, best_rate,
+                                          CurrentTime);
             }
-            XRRSetScreenConfigAndRate(display, data->screen_config,
-                                      RootWindow(display, data->screen), best,
-                                      data->saved_rotation, best_rate,
-                                      CurrentTime);
         }
         return;
     }
--- a/src/video/x11/SDL_x11sym.h	Tue Oct 02 23:23:44 2012 -0700
+++ b/src/video/x11/SDL_x11sym.h	Wed Oct 03 11:33:09 2012 -0700
@@ -237,6 +237,15 @@
 SDL_X11_SYM(XRRScreenSize *,XRRConfigSizes,(XRRScreenConfiguration *config,int *nsizes),(config,nsizes),return)
 SDL_X11_SYM(Status,XRRSetScreenConfigAndRate,(Display *dpy,XRRScreenConfiguration *config,Drawable draw,int size_index,Rotation rotation,short rate,Time timestamp),(dpy,config,draw,size_index,rotation,rate,timestamp),return)
 SDL_X11_SYM(void,XRRFreeScreenConfigInfo,(XRRScreenConfiguration *config),(config),)
+SDL_X11_SYM(void,XRRSetScreenSize,(Display *dpy, Window window,int width, int height,int mmWidth, int mmHeight),(dpy,window,width,height,mmWidth,mmHeight),)
+SDL_X11_SYM(Status,XRRGetScreenSizeRange,(Display *dpy, Window window,int *minWidth, int *minHeight, int *maxWidth, int *maxHeight),(dpy,window,minWidth,minHeight,maxWidth,maxHeight),return)
+SDL_X11_SYM(XRRScreenResources *,XRRGetScreenResources,(Display *dpy, Window window),(dpy, window),return)
+SDL_X11_SYM(void,XRRFreeScreenResources,(XRRScreenResources *resources),(resources),)
+SDL_X11_SYM(XRROutputInfo *,XRRGetOutputInfo,(Display *dpy, XRRScreenResources *resources, RROutput output),(dpy,resources,output),return)
+SDL_X11_SYM(void,XRRFreeOutputInfo,(XRROutputInfo *outputInfo),(outputInfo),)
+SDL_X11_SYM(XRRCrtcInfo *,XRRGetCrtcInfo,(Display *dpy, XRRScreenResources *resources, RRCrtc crtc),(dpy,resources,crtc),return)
+SDL_X11_SYM(void,XRRFreeCrtcInfo,(XRRCrtcInfo *crtcInfo),(crtcInfo),)
+SDL_X11_SYM(Status,XRRSetCrtcConfig,(Display *dpy, XRRScreenResources *resources, RRCrtc crtc, Time timestamp, int x, int y, RRMode mode, Rotation rotation, RROutput *outputs, int noutputs),(dpy,resources,crtc,timestamp,x,y,mode,rotation,outputs,noutputs),return)
 #endif
 
 /* MIT-SCREEN-SAVER support */