]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
resolutionX11.c: Add support for autofit via RandR >= 1.2.
authorVMware, Inc <>
Wed, 20 Jan 2010 21:29:46 +0000 (13:29 -0800)
committerMarcelo Vanzin <mvanzin@vmware.com>
Wed, 20 Jan 2010 21:29:46 +0000 (13:29 -0800)
The new WIP Linux guest drivers no longer support the VMWARE_CTRL X11
protocol extension but instead expose multiple displays via the RandR
(Resize and Rotate) extension as outputs LVDS1..LVDS<n>.

The requested topology is implemented using modes called
'autofit-WWWWxHHHH' generated as needed for each output.

Signed-off-by: Marcelo Vanzin <mvanzin@vmware.com>
open-vm-tools/lib/resolution/resolutionX11.c
open-vm-tools/services/plugins/resolutionSet/resolutionX11.c

index 87785a3fd75b3cfb96c181a8a161a9c1a373f15d..12e49ce3e6c9e4a3571a06600cd2427c831d466f 100644 (file)
 #include "libvmwarectrl.h"
 #include "str.h"
 #include "strutil.h"
+#include "util.h"
 
 #define VMWAREDRV_PATH_64   "/usr/X11R6/lib64/modules/drivers/vmware_drv.o"
 #define VMWAREDRV_PATH      "/usr/X11R6/lib/modules/drivers/vmware_drv.o"
 #define VERSION_STRING      "VMware Guest X Server"
 
+#define RR12_OUTPUT_FORMAT "LVDS%u"
+#define RR12_MODE_FORMAT "autofit-%ux%u"
+#define RR12_MODE_MAXLEN (sizeof RR12_MODE_FORMAT + 2 * (10 - 2) + 1)
+#define RR12_DEFAULT_DPI 96.0
+#define MILLIS_PER_INCH 25.4
+
 
 /*
  * Describes the state of the X11 back-end of lib/resolution.
@@ -60,6 +67,11 @@ typedef struct {
                                 // TRUE if VMwareCtrl extension available
    Bool         canUseVMwareCtrlTopologySet;
                                 // TRUE if VMwareCtrl extension supports topology set
+   Bool         canUseRandR12;  // TRUE if RandR extension >= 1.2 available
+   unsigned int topologyDisplays;
+                                // Number of displays in current topology
+   unsigned int topologyWidth;  // Total width of current topology
+   unsigned int topologyHeight; // Total height of current topology
 } ResolutionInfoX11Type;
 
 
@@ -76,6 +88,11 @@ ResolutionInfoX11Type   resolutionInfoX11;
 
 static Bool ResolutionCanSet(void);
 static Bool TopologyCanSet(void);
+#ifndef NO_MULTIMON
+static Bool RandR12_SetTopology(unsigned int ndisplays,
+                                xXineramaScreenInfo *displays,
+                                unsigned int width, unsigned int height);
+#endif
 static Bool SelectResolution(uint32 width, uint32 height);
 
 
@@ -123,6 +140,7 @@ ResolutionBackendInit(InitHandle handle) // IN: User's X11 display.
    resInfoX->canUseVMwareCtrl = VMwareCtrl_QueryVersion(resInfoX->display, &dummy1,
                                                         &dummy2);
    resInfoX->canUseVMwareCtrlTopologySet = FALSE;
+   resInfoX->canUseRandR12 = FALSE;
 
    resInfo->canSetResolution = ResolutionCanSet();
    resInfo->canSetTopology = TopologyCanSet();
@@ -280,18 +298,23 @@ ResolutionSetTopology(unsigned int ndisplays,
       displays[i].y_org -= minY;
    }
 
-   if (!VMwareCtrl_SetTopology(resInfoX->display, DefaultScreen(resInfoX->display), displays,
-                               ndisplays)) {
-      Debug("Failed to set topology in the driver.\n");
-      goto out;
-   }
+   if (resInfoX->canUseVMwareCtrlTopologySet) {
+      if (!VMwareCtrl_SetTopology(resInfoX->display, DefaultScreen(resInfoX->display),
+                                  displays, ndisplays)) {
+         Debug("Failed to set topology in the driver.\n");
+         goto out;
+      }
 
-   if (!SelectResolution(maxX - minX, maxY - minY)) {
-      Debug("Failed to set new resolution.\n");
-      goto out;
-   }
+      if (!SelectResolution(maxX - minX, maxY - minY)) {
+         Debug("Failed to set new resolution.\n");
+         goto out;
+      }
 
-   success = TRUE;
+      success = TRUE;
+   } else if (resInfoX->canUseRandR12) {
+      success = RandR12_SetTopology(ndisplays, displays,
+                                    maxX - minX, maxY - minY);
+   }
 
 out:
    free(displays);
@@ -347,6 +370,54 @@ ResolutionCanSet(void)
       return TRUE;
    }
 
+#ifndef NO_MULTIMON
+   /* See if RandR >= 1.2 can be used: The extension version is high enough and
+    * all output names match the expected format.
+    */
+   if (major > 1 || (major == 1 && minor >= 2)) {
+      XRRScreenResources* xrrRes;
+      XRROutputInfo* xrrOutput;
+      unsigned int num;
+      int i;
+
+      XGrabServer(resInfoX->display);
+
+      xrrRes = XRRGetScreenResources(resInfoX->display, resInfoX->rootWindow);
+
+      if (xrrRes) {
+         for (i = 0; i < xrrRes->noutput; i++) {
+            xrrOutput = XRRGetOutputInfo(resInfoX->display, xrrRes,
+                                         xrrRes->outputs[i]);
+            if (!xrrOutput) {
+               break;
+            }
+
+            if (sscanf(xrrOutput->name, RR12_OUTPUT_FORMAT, &num) != 1 ||
+                num < 1) {
+               XRRFreeOutputInfo(xrrOutput);
+               break;
+            }
+
+            XRRFreeOutputInfo(xrrOutput);
+         }
+
+         if (i == xrrRes->noutput) {
+            resInfoX->canUseRandR12 = TRUE;
+         } else {
+            Debug("RandR >= 1.2 not usable\n");
+         }
+
+         XRRFreeScreenResources(xrrRes);
+      }
+
+      XUngrabServer(resInfoX->display);
+
+      if (resInfoX->canUseRandR12) {
+         return TRUE;
+      }
+   }
+#endif // ifndef NO_MULTIMON
+
    /*
     * XXX: This check does not work with XOrg 6.9/7.0 for two reasons: Both
     * versions now use .so for the driver extension and 7.0 moves the drivers
@@ -430,10 +501,15 @@ TopologyCanSet(void)
    ResolutionInfoX11Type *resInfoX = &resolutionInfoX11;
 #ifdef NO_MULTIMON
    resInfoX->canUseVMwareCtrlTopologySet = FALSE;
+   return FALSE;
 #else
    int major;
    int minor;
 
+   if (resInfoX->canUseRandR12) {
+      return TRUE;
+   }
+
    if (resInfoX->canUseVMwareCtrl && XineramaQueryVersion(resInfoX->display, &major,
                                                           &minor)) {
       /*
@@ -443,12 +519,288 @@ TopologyCanSet(void)
    } else {
       resInfoX->canUseVMwareCtrlTopologySet = FALSE;
    }
-#endif
 
    return resInfoX->canUseVMwareCtrlTopologySet;
+#endif
 }
 
 
+#ifndef NO_MULTIMON
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * RandR12_SetTopology --
+ *
+ *      Employs the RandR 1.2 extension to set a new display topology.
+ *
+ * Results:
+ *      TRUE if operation succeeded, FALSE otherwise.
+ *
+ * Side effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static Bool
+RandR12_SetTopology(unsigned int ndisplays,
+                    // IN:  number of elements in topology
+                    xXineramaScreenInfo *displays,
+                    // IN: array of display geometries
+                    unsigned int width,
+                    // IN: total width of topology
+                    unsigned int height)
+                    // IN: total height of topology
+{
+   ResolutionInfoX11Type *resInfoX = &resolutionInfoX11;
+   int minWidth, minHeight, maxWidth, maxHeight;
+   XRRScreenResources* xrrRes = NULL;
+   XRROutputInfo** xrrOutputs;
+   unsigned int numOutputs;
+   unsigned int* outputMap;
+   XRRCrtcInfo** xrrCrtcs;
+   XRRScreenConfiguration* xrrConfig = NULL;
+   XRRScreenSize* xrrSizes;
+   Rotation xrrCurRotation;
+   uint32 xrrNumSizes;
+   SizeID currentSize;
+   XRRModeInfo xrrModes[ndisplays];
+   char name[RR12_MODE_MAXLEN];
+   float dpi;
+   int i, j, k;
+   Bool success = FALSE;
+
+   if (!XRRGetScreenSizeRange(resInfoX->display, resInfoX->rootWindow,
+                              &minWidth, &minHeight, &maxWidth, &maxHeight) ||
+       width < minWidth || height < minHeight ||
+       width > maxWidth || height > maxHeight) {
+      return FALSE;
+   }
+
+   /* Grab the server for two reasons:
+    * - Avoid race conditions with other clients changing RandR configuration.
+    * - Make our changes appear as atomic as possible to other clients.
+    */
+   XGrabServer(resInfoX->display);
+
+   xrrRes = XRRGetScreenResources(resInfoX->display, resInfoX->rootWindow);
+   if (!xrrRes) {
+      goto error;
+   }
+
+   xrrCrtcs = Util_SafeCalloc(sizeof *xrrCrtcs, xrrRes->ncrtc);
+   xrrOutputs = Util_SafeCalloc(sizeof *xrrOutputs, xrrRes->noutput);
+   outputMap = Util_SafeCalloc(sizeof *outputMap, xrrRes->noutput);
+
+   /* RandR may enumerate outputs differently from the host. Apply the nth
+    * topology rectangle to the output called LVDS<n>.
+    */
+   for (i = 0; i < xrrRes->noutput; i++) {
+      outputMap[i] = i;
+   }
+
+   numOutputs = 0;
+   for (i = 0; i < xrrRes->noutput; i++) {
+      unsigned int num;
+
+      xrrOutputs[i] = XRRGetOutputInfo(resInfoX->display, xrrRes,
+                                       xrrRes->outputs[i]);
+
+      if (!xrrOutputs[i]) {
+         goto error;
+      }
+
+      if (sscanf(xrrOutputs[i]->name, RR12_OUTPUT_FORMAT, &num) != 1 ||
+          num > ndisplays) {
+         continue;
+      }
+
+      outputMap[num - 1] = i;
+
+      if (num > numOutputs) {
+         numOutputs = num;
+      }
+   }
+
+   /* Disable any CRTCs which won't be used or wont't fit in new screen size. */
+   for (i = 0; i < xrrRes->ncrtc; i++) {
+      xrrCrtcs[i] = XRRGetCrtcInfo(resInfoX->display, xrrRes,
+                                   xrrRes->crtcs[i]);
+
+      if (!xrrCrtcs[i]) {
+         goto error;
+      }
+
+      for (j = 0; j < numOutputs; j++) {
+         if (xrrOutputs[outputMap[j]]->crtc == xrrRes->crtcs[i]) {
+            break;
+         }
+      }
+
+      if (xrrCrtcs[i]->mode == None ||
+          (j < numOutputs &&
+           (xrrCrtcs[i]->x + xrrCrtcs[i]->width) <= width &&
+           (xrrCrtcs[i]->y + xrrCrtcs[i]->height) <= height)) {
+         continue;
+      }
+
+      if (XRRSetCrtcConfig(resInfoX->display, xrrRes, xrrRes->crtcs[i],
+                           xrrCrtcs[i]->timestamp, 0, 0, None, RR_Rotate_0, NULL, 0)
+          != Success) {
+         goto error;
+      }
+   }
+
+   /* Set new screen size. */
+   xrrConfig = XRRGetScreenInfo(resInfoX->display, resInfoX->rootWindow);
+   xrrSizes = XRRConfigSizes(xrrConfig, &xrrNumSizes);
+   currentSize = XRRConfigCurrentConfiguration(xrrConfig, &xrrCurRotation);
+
+   if (xrrSizes[currentSize].mheight > 0) {
+      dpi = MILLIS_PER_INCH * xrrSizes[currentSize].height / xrrSizes[currentSize].mheight;
+
+      if (!dpi) {
+         dpi = RR12_DEFAULT_DPI;
+      }
+   } else {
+      dpi = RR12_DEFAULT_DPI;
+   }
+
+   XRRSetScreenSize(resInfoX->display, resInfoX->rootWindow, width, height,
+                    (MILLIS_PER_INCH * width) / dpi,
+                    (MILLIS_PER_INCH * height) / dpi);
+
+   /* Set new topology. */
+   for (i = 0; i < numOutputs; i++) {
+      memset(xrrModes + i, 0, sizeof xrrModes[0]);
+
+      xrrModes[i].width = displays[i].width;
+      xrrModes[i].height = displays[i].height;
+
+      /* Look for existing matching autofit mode. */
+      for (j = 0; j < i && !xrrModes[i].id; j++) {
+         if (xrrModes[j].id &&
+             xrrModes[j].width == displays[i].width &&
+             xrrModes[j].height == displays[i].height) {
+            xrrModes[i].id = xrrModes[j].id;
+            break;
+         }
+      }
+
+      for (j = 0; j < xrrRes->nmode && !xrrModes[i].id; j++) {
+         unsigned int w, h;
+
+         if (sscanf(xrrRes->modes[j].name, RR12_MODE_FORMAT, &w, &h) == 2 &&
+             w == displays[i].width && h == displays[i].height) {
+            xrrModes[i].id = xrrRes->modes[j].id;
+            break;
+         }
+      }
+
+      /* If no luck, create new autofit mode. */
+      if (!xrrModes[i].id) {
+         sprintf(name, RR12_MODE_FORMAT, displays[i].width,
+                 displays[i].height);
+         xrrModes[i].name = name;
+         xrrModes[i].nameLength = strlen(xrrModes[i].name);
+         xrrModes[i].id = XRRCreateMode(resInfoX->display, resInfoX->rootWindow,
+                                        xrrModes + i);
+      }
+
+      if (xrrModes[i].id == None) {
+         continue;
+      }
+
+      /* Set autofit mode. */
+      if (xrrOutputs[outputMap[i]]->crtc == None) {
+         xrrOutputs[outputMap[i]]->crtc = xrrOutputs[outputMap[i]]->crtcs[0];
+      }
+
+      for (j = 0; j < xrrOutputs[outputMap[i]]->nmode; j++) {
+         if (xrrModes[i].id == xrrOutputs[outputMap[i]]->modes[j]) {
+            break;
+         }
+      }
+      if (j == xrrOutputs[outputMap[i]]->nmode) {
+         XRRAddOutputMode(resInfoX->display, xrrRes->outputs[outputMap[i]],
+                          xrrModes[i].id);
+      }
+      if (XRRSetCrtcConfig(resInfoX->display, xrrRes,
+                           xrrOutputs[outputMap[i]]->crtc, xrrCrtcs[i]->timestamp,
+                           displays[i].x_org, displays[i].y_org, xrrModes[i].id,
+                           RR_Rotate_0, xrrRes->outputs + outputMap[i], 1)
+          != Success) {
+         goto error;
+      }
+   }
+
+   /* Delete unused autofit modes. */
+   for (i = 0; i < xrrRes->nmode; i++) {
+      unsigned int w, h;
+      Bool destroy = TRUE;
+
+      if (sscanf(xrrRes->modes[i].name, RR12_MODE_FORMAT, &w, &h) != 2) {
+         continue;
+      }
+
+      for (j = 0; j < xrrRes->noutput; j++) {
+         if (j < numOutputs &&
+             w == displays[j].width && h == displays[j].height) {
+            destroy = FALSE;
+            continue;
+         }
+
+         for (k = 0; k < xrrOutputs[outputMap[j]]->nmode; k++) {
+            if (xrrOutputs[outputMap[j]]->modes[k] == xrrRes->modes[i].id) {
+               XRRDeleteOutputMode(resInfoX->display,
+                                   xrrRes->outputs[outputMap[j]],
+                                   xrrOutputs[outputMap[j]]->modes[k]);
+               break;
+            }
+         }
+      }
+
+      if (destroy) {
+         XRRDestroyMode(resInfoX->display, xrrRes->modes[i].id);
+      }
+   }
+
+   resInfoX->topologyDisplays = ndisplays;
+   resInfoX->topologyWidth = width;
+   resInfoX->topologyHeight = height;
+
+   success = TRUE;
+
+error:
+   XUngrabServer(resInfoX->display);
+
+   if (xrrConfig) {
+      XRRFreeScreenConfigInfo(xrrConfig);
+   }
+   if (xrrRes) {
+      for (i = 0; i < xrrRes->noutput; i++) {
+         if (xrrOutputs[i]) {
+            XRRFreeOutputInfo(xrrOutputs[i]);
+         }
+      }
+      for (i = 0; i < xrrRes->ncrtc; i++) {
+         if (xrrCrtcs[i]) {
+            XRRFreeCrtcInfo(xrrCrtcs[i]);
+         }
+      }
+      free(outputMap);
+      free(xrrOutputs);
+      free(xrrCrtcs);
+      XRRFreeScreenResources(xrrRes);
+   }
+
+   return success;
+}
+
+#endif // ifndef NO_MULTIMON
+
+
 /*-----------------------------------------------------------------------------
  *
  * SelectResolution --
@@ -479,6 +831,25 @@ SelectResolution(uint32 width,  // IN
    uint64 bestFitSize = 0;
    uint64 potentialSize;
 
+#ifndef NO_MULTIMON
+   if (resInfoX->canUseRandR12) {
+      if (resInfoX->topologyDisplays != 1 ||
+          resInfoX->topologyWidth != width ||
+          resInfoX->topologyHeight != height) {
+         xXineramaScreenInfo display;
+
+         display.x_org = 0;
+         display.y_org = 0;
+         display.width = width;
+         display.height = height;
+
+         return RandR12_SetTopology(1, &display, width, height);
+      }
+
+      return TRUE;
+   }
+#endif
+
    xrrConfig = XRRGetScreenInfo(resInfoX->display, resInfoX->rootWindow);
    xrrSizes = XRRConfigSizes(xrrConfig, &xrrNumSizes);
    XRRConfigCurrentConfiguration(xrrConfig, &xrrCurRotation);
index c4ed0fde822debf9f0ff462e6b8348ebc16e42ae..7452e9168d6de12ef1c9e7d0c3b013261d92f88b 100644 (file)
 #include "libvmwarectrl.h"
 #include "str.h"
 #include "strutil.h"
+#include "util.h"
 
 #define VMWAREDRV_PATH_64   "/usr/X11R6/lib64/modules/drivers/vmware_drv.o"
 #define VMWAREDRV_PATH      "/usr/X11R6/lib/modules/drivers/vmware_drv.o"
 #define VERSION_STRING      "VMware Guest X Server"
 
+#define RR12_OUTPUT_FORMAT "LVDS%u"
+#define RR12_MODE_FORMAT "autofit-%ux%u"
+#define RR12_MODE_MAXLEN (sizeof RR12_MODE_FORMAT + 2 * (10 - 2) + 1)
+#define RR12_DEFAULT_DPI 96.0
+#define MILLIS_PER_INCH 25.4
+
 
 /**
  * Describes the state of the X11 back-end of lib/resolution.
@@ -62,6 +69,11 @@ typedef struct {
                                 // TRUE if VMwareCtrl extension available
    Bool         canUseVMwareCtrlTopologySet;
                                 // TRUE if VMwareCtrl extension supports topology set
+   Bool         canUseRandR12;  // TRUE if RandR extension >= 1.2 available
+   unsigned int topologyDisplays;
+                                // Number of displays in current topology
+   unsigned int topologyWidth;  // Total width of current topology
+   unsigned int topologyHeight; // Total height of current topology
 } ResolutionInfoX11Type;
 
 
@@ -77,6 +89,11 @@ ResolutionInfoX11Type resolutionInfoX11;
 
 static Bool ResolutionCanSet(void);
 static Bool TopologyCanSet(void);
+#ifndef NO_MULTIMON
+static Bool RandR12_SetTopology(unsigned int ndisplays,
+                                xXineramaScreenInfo *displays,
+                                unsigned int width, unsigned int height);
+#endif
 static Bool SelectResolution(uint32 width, uint32 height);
 
 
@@ -115,6 +132,7 @@ ResolutionBackendInit(InitHandle handle)
    resInfoX->canUseVMwareCtrl = VMwareCtrl_QueryVersion(resInfoX->display, &dummy1,
                                                         &dummy2);
    resInfoX->canUseVMwareCtrlTopologySet = FALSE;
+   resInfoX->canUseRandR12 = FALSE;
 
    resInfo->canSetResolution = ResolutionCanSet();
    resInfo->canSetTopology = TopologyCanSet();
@@ -241,18 +259,23 @@ ResolutionSetTopology(unsigned int ndisplays,
       displays[i].y_org -= minY;
    }
 
-   if (!VMwareCtrl_SetTopology(resInfoX->display, DefaultScreen(resInfoX->display), displays,
-                               ndisplays)) {
-      Debug("Failed to set topology in the driver.\n");
-      goto out;
-   }
+   if (resInfoX->canUseVMwareCtrlTopologySet) {
+      if (!VMwareCtrl_SetTopology(resInfoX->display, DefaultScreen(resInfoX->display),
+                                  displays, ndisplays)) {
+         Debug("Failed to set topology in the driver.\n");
+         goto out;
+      }
 
-   if (!SelectResolution(maxX - minX, maxY - minY)) {
-      Debug("Failed to set new resolution.\n");
-      goto out;
-   }
+      if (!SelectResolution(maxX - minX, maxY - minY)) {
+         Debug("Failed to set new resolution.\n");
+         goto out;
+      }
 
-   success = TRUE;
+      success = TRUE;
+   } else if (resInfoX->canUseRandR12) {
+      success = RandR12_SetTopology(ndisplays, displays,
+                                    maxX - minX, maxY - minY);
+   }
 
 out:
    free(displays);
@@ -299,6 +322,54 @@ ResolutionCanSet(void)
       return TRUE;
    }
 
+#ifndef NO_MULTIMON
+   /* See if RandR >= 1.2 can be used: The extension version is high enough and
+    * all output names match the expected format.
+    */
+   if (major > 1 || (major == 1 && minor >= 2)) {
+      XRRScreenResources* xrrRes;
+      XRROutputInfo* xrrOutput;
+      unsigned int num;
+      int i;
+
+      XGrabServer(resInfoX->display);
+
+      xrrRes = XRRGetScreenResources(resInfoX->display, resInfoX->rootWindow);
+
+      if (xrrRes) {
+         for (i = 0; i < xrrRes->noutput; i++) {
+            xrrOutput = XRRGetOutputInfo(resInfoX->display, xrrRes,
+                                         xrrRes->outputs[i]);
+            if (!xrrOutput) {
+               break;
+            }
+
+            if (sscanf(xrrOutput->name, RR12_OUTPUT_FORMAT, &num) != 1 ||
+                num < 1) {
+               XRRFreeOutputInfo(xrrOutput);
+               break;
+            }
+
+            XRRFreeOutputInfo(xrrOutput);
+         }
+
+         if (i == xrrRes->noutput) {
+            resInfoX->canUseRandR12 = TRUE;
+         } else {
+            Debug("RandR >= 1.2 not usable\n");
+         }
+
+         XRRFreeScreenResources(xrrRes);
+      }
+
+      XUngrabServer(resInfoX->display);
+
+      if (resInfoX->canUseRandR12) {
+         return TRUE;
+      }
+   }
+#endif // ifndef NO_MULTIMON
+
    /*
     * XXX: This check does not work with XOrg 6.9/7.0 for two reasons: Both
     * versions now use .so for the driver extension and 7.0 moves the drivers
@@ -373,10 +444,15 @@ TopologyCanSet(void)
    ResolutionInfoX11Type *resInfoX = &resolutionInfoX11;
 #ifdef NO_MULTIMON
    resInfoX->canUseVMwareCtrlTopologySet = FALSE;
+   return FALSE;
 #else
    int major;
    int minor;
 
+   if (resInfoX->canUseRandR12) {
+      return TRUE;
+   }
+
    if (resInfoX->canUseVMwareCtrl && XineramaQueryVersion(resInfoX->display, &major,
                                                           &minor)) {
       /*
@@ -386,11 +462,277 @@ TopologyCanSet(void)
    } else {
       resInfoX->canUseVMwareCtrlTopologySet = FALSE;
    }
-#endif
 
    return resInfoX->canUseVMwareCtrlTopologySet;
+#endif
+}
+
+
+#ifndef NO_MULTIMON
+
+/**
+ * Employs the RandR 1.2 extension to set a new display topology.
+ *
+ * @return TRUE if operation succeeded, FALSE otherwise.
+ */
+
+static Bool
+RandR12_SetTopology(unsigned int ndisplays,
+                    // IN:  number of elements in topology
+                    xXineramaScreenInfo *displays,
+                    // IN: array of display geometries
+                    unsigned int width,
+                    // IN: total width of topology
+                    unsigned int height)
+                    // IN: total height of topology
+{
+   ResolutionInfoX11Type *resInfoX = &resolutionInfoX11;
+   int minWidth, minHeight, maxWidth, maxHeight;
+   XRRScreenResources* xrrRes = NULL;
+   XRROutputInfo** xrrOutputs;
+   unsigned int numOutputs;
+   unsigned int* outputMap;
+   XRRCrtcInfo** xrrCrtcs;
+   XRRScreenConfiguration* xrrConfig = NULL;
+   XRRScreenSize* xrrSizes;
+   Rotation xrrCurRotation;
+   uint32 xrrNumSizes;
+   SizeID currentSize;
+   XRRModeInfo xrrModes[ndisplays];
+   char name[RR12_MODE_MAXLEN];
+   float dpi;
+   int i, j, k;
+   Bool success = FALSE;
+
+   if (!XRRGetScreenSizeRange(resInfoX->display, resInfoX->rootWindow,
+                              &minWidth, &minHeight, &maxWidth, &maxHeight) ||
+       width < minWidth || height < minHeight ||
+       width > maxWidth || height > maxHeight) {
+      return FALSE;
+   }
+
+   /* Grab the server for two reasons:
+    * - Avoid race conditions with other clients changing RandR configuration.
+    * - Make our changes appear as atomic as possible to other clients.
+    */
+   XGrabServer(resInfoX->display);
+
+   xrrRes = XRRGetScreenResources(resInfoX->display, resInfoX->rootWindow);
+   if (!xrrRes) {
+      goto error;
+   }
+
+   xrrCrtcs = Util_SafeCalloc(sizeof *xrrCrtcs, xrrRes->ncrtc);
+   xrrOutputs = Util_SafeCalloc(sizeof *xrrOutputs, xrrRes->noutput);
+   outputMap = Util_SafeCalloc(sizeof *outputMap, xrrRes->noutput);
+
+   /* RandR may enumerate outputs differently from the host. Apply the nth
+    * topology rectangle to the output called LVDS<n>.
+    */
+   for (i = 0; i < xrrRes->noutput; i++) {
+      outputMap[i] = i;
+   }
+
+   numOutputs = 0;
+   for (i = 0; i < xrrRes->noutput; i++) {
+      unsigned int num;
+
+      xrrOutputs[i] = XRRGetOutputInfo(resInfoX->display, xrrRes,
+                                       xrrRes->outputs[i]);
+
+      if (!xrrOutputs[i]) {
+         goto error;
+      }
+
+      if (sscanf(xrrOutputs[i]->name, RR12_OUTPUT_FORMAT, &num) != 1 ||
+          num > ndisplays) {
+         continue;
+      }
+
+      outputMap[num - 1] = i;
+
+      if (num > numOutputs) {
+         numOutputs = num;
+      }
+   }
+
+   /* Disable any CRTCs which won't be used or wont't fit in new screen size. */
+   for (i = 0; i < xrrRes->ncrtc; i++) {
+      xrrCrtcs[i] = XRRGetCrtcInfo(resInfoX->display, xrrRes,
+                                   xrrRes->crtcs[i]);
+
+      if (!xrrCrtcs[i]) {
+         goto error;
+      }
+
+      for (j = 0; j < numOutputs; j++) {
+         if (xrrOutputs[outputMap[j]]->crtc == xrrRes->crtcs[i]) {
+            break;
+         }
+      }
+
+      if (xrrCrtcs[i]->mode == None ||
+          (j < numOutputs &&
+           (xrrCrtcs[i]->x + xrrCrtcs[i]->width) <= width &&
+           (xrrCrtcs[i]->y + xrrCrtcs[i]->height) <= height)) {
+         continue;
+      }
+
+      if (XRRSetCrtcConfig(resInfoX->display, xrrRes, xrrRes->crtcs[i],
+                           xrrCrtcs[i]->timestamp, 0, 0, None, RR_Rotate_0, NULL, 0)
+          != Success) {
+         goto error;
+      }
+   }
+
+   /* Set new screen size. */
+   xrrConfig = XRRGetScreenInfo(resInfoX->display, resInfoX->rootWindow);
+   xrrSizes = XRRConfigSizes(xrrConfig, &xrrNumSizes);
+   currentSize = XRRConfigCurrentConfiguration(xrrConfig, &xrrCurRotation);
+
+   if (xrrSizes[currentSize].mheight > 0) {
+      dpi = MILLIS_PER_INCH * xrrSizes[currentSize].height / xrrSizes[currentSize].mheight;
+
+      if (!dpi) {
+         dpi = RR12_DEFAULT_DPI;
+      }
+   } else {
+      dpi = RR12_DEFAULT_DPI;
+   }
+
+   XRRSetScreenSize(resInfoX->display, resInfoX->rootWindow, width, height,
+                    (MILLIS_PER_INCH * width) / dpi,
+                    (MILLIS_PER_INCH * height) / dpi);
+
+   /* Set new topology. */
+   for (i = 0; i < numOutputs; i++) {
+      memset(xrrModes + i, 0, sizeof xrrModes[0]);
+
+      xrrModes[i].width = displays[i].width;
+      xrrModes[i].height = displays[i].height;
+
+      /* Look for existing matching autofit mode. */
+      for (j = 0; j < i && !xrrModes[i].id; j++) {
+         if (xrrModes[j].id &&
+             xrrModes[j].width == displays[i].width &&
+             xrrModes[j].height == displays[i].height) {
+            xrrModes[i].id = xrrModes[j].id;
+            break;
+         }
+      }
+
+      for (j = 0; j < xrrRes->nmode && !xrrModes[i].id; j++) {
+         unsigned int w, h;
+
+         if (sscanf(xrrRes->modes[j].name, RR12_MODE_FORMAT, &w, &h) == 2 &&
+             w == displays[i].width && h == displays[i].height) {
+            xrrModes[i].id = xrrRes->modes[j].id;
+            break;
+         }
+      }
+
+      /* If no luck, create new autofit mode. */
+      if (!xrrModes[i].id) {
+         Str_Sprintf(name, sizeof name, RR12_MODE_FORMAT, displays[i].width,
+                     displays[i].height);
+         xrrModes[i].name = name;
+         xrrModes[i].nameLength = strlen(xrrModes[i].name);
+         xrrModes[i].id = XRRCreateMode(resInfoX->display, resInfoX->rootWindow,
+                                        xrrModes + i);
+      }
+
+      if (xrrModes[i].id == None) {
+         continue;
+      }
+
+      /* Set autofit mode. */
+      if (xrrOutputs[outputMap[i]]->crtc == None) {
+         xrrOutputs[outputMap[i]]->crtc = xrrOutputs[outputMap[i]]->crtcs[0];
+      }
+
+      for (j = 0; j < xrrOutputs[outputMap[i]]->nmode; j++) {
+         if (xrrModes[i].id == xrrOutputs[outputMap[i]]->modes[j]) {
+            break;
+         }
+      }
+      if (j == xrrOutputs[outputMap[i]]->nmode) {
+         XRRAddOutputMode(resInfoX->display, xrrRes->outputs[outputMap[i]],
+                          xrrModes[i].id);
+      }
+      if (XRRSetCrtcConfig(resInfoX->display, xrrRes,
+                           xrrOutputs[outputMap[i]]->crtc, xrrCrtcs[i]->timestamp,
+                           displays[i].x_org, displays[i].y_org, xrrModes[i].id,
+                           RR_Rotate_0, xrrRes->outputs + outputMap[i], 1)
+          != Success) {
+         goto error;
+      }
+   }
+
+   /* Delete unused autofit modes. */
+   for (i = 0; i < xrrRes->nmode; i++) {
+      unsigned int w, h;
+      Bool destroy = TRUE;
+
+      if (sscanf(xrrRes->modes[i].name, RR12_MODE_FORMAT, &w, &h) != 2) {
+         continue;
+      }
+
+      for (j = 0; j < xrrRes->noutput; j++) {
+         if (j < numOutputs &&
+             w == displays[j].width && h == displays[j].height) {
+            destroy = FALSE;
+            continue;
+         }
+
+         for (k = 0; k < xrrOutputs[outputMap[j]]->nmode; k++) {
+            if (xrrOutputs[outputMap[j]]->modes[k] == xrrRes->modes[i].id) {
+               XRRDeleteOutputMode(resInfoX->display,
+                                   xrrRes->outputs[outputMap[j]],
+                                   xrrOutputs[outputMap[j]]->modes[k]);
+               break;
+            }
+         }
+      }
+
+      if (destroy) {
+         XRRDestroyMode(resInfoX->display, xrrRes->modes[i].id);
+      }
+   }
+
+   resInfoX->topologyDisplays = ndisplays;
+   resInfoX->topologyWidth = width;
+   resInfoX->topologyHeight = height;
+
+   success = TRUE;
+
+error:
+   XUngrabServer(resInfoX->display);
+
+   if (xrrConfig) {
+      XRRFreeScreenConfigInfo(xrrConfig);
+   }
+   if (xrrRes) {
+      for (i = 0; i < xrrRes->noutput; i++) {
+         if (xrrOutputs[i]) {
+            XRRFreeOutputInfo(xrrOutputs[i]);
+         }
+      }
+      for (i = 0; i < xrrRes->ncrtc; i++) {
+         if (xrrCrtcs[i]) {
+            XRRFreeCrtcInfo(xrrCrtcs[i]);
+         }
+      }
+      free(outputMap);
+      free(xrrOutputs);
+      free(xrrCrtcs);
+      XRRFreeScreenResources(xrrRes);
+   }
+
+   return success;
 }
 
+#endif // ifndef NO_MULTIMON
+
 
 /**
  * Given a width and height, find the biggest resolution that will "fit".
@@ -415,6 +757,25 @@ SelectResolution(uint32 width,
    uint64 bestFitSize = 0;
    uint64 potentialSize;
 
+#ifndef NO_MULTIMON
+   if (resInfoX->canUseRandR12) {
+      if (resInfoX->topologyDisplays != 1 ||
+          resInfoX->topologyWidth != width ||
+          resInfoX->topologyHeight != height) {
+         xXineramaScreenInfo display;
+
+         display.x_org = 0;
+         display.y_org = 0;
+         display.width = width;
+         display.height = height;
+
+         return RandR12_SetTopology(1, &display, width, height);
+      }
+
+      return TRUE;
+   }
+#endif
+
    xrrConfig = XRRGetScreenInfo(resInfoX->display, resInfoX->rootWindow);
    xrrSizes = XRRConfigSizes(xrrConfig, &xrrNumSizes);
    XRRConfigCurrentConfiguration(xrrConfig, &xrrCurRotation);