]> source.dussan.org Git - tigervnc.git/commitdiff
Add RandR support for x0vncserver 621/head
authorPeter Åstrand (astrand) <astrand@cendio.se>
Wed, 7 Mar 2018 12:00:47 +0000 (13:00 +0100)
committerPeter Åstrand (astrand) <astrand@cendio.se>
Mon, 9 Apr 2018 18:31:38 +0000 (20:31 +0200)
Change Xserver screen through libXrandr. For complex configurations,
all outputs must have corresponding size modes. As a special case, if
the client requests a single screen with an odd size (for example when
adjusting the size of a non-fullscreen vncviewer), find a smaller
suitable mode, and reduce the framebuffer size as well.

cmake/StaticBuild.cmake
unix/x0vncserver/CMakeLists.txt
unix/x0vncserver/Geometry.cxx
unix/x0vncserver/Geometry.h
unix/x0vncserver/RandrGlue.c [new file with mode: 0644]
unix/x0vncserver/XDesktop.cxx
unix/x0vncserver/XDesktop.h

index 06883c6f2800feb976a76a0e7169c3382912df70..4b58b1deaf6ace8ba380d44bc2c25b91f8170766 100644 (file)
@@ -109,6 +109,9 @@ if(BUILD_STATIC)
     if(X11_Xdamage_LIB)
       set(X11_Xdamage_LIB "-Wl,-Bstatic -lXdamage -Wl,-Bdynamic")
     endif()
+    if(X11_Xrandr_LIB)
+      set(X11_Xrandr_LIB "-Wl,-Bstatic -lXrandr -lXrender -Wl,-Bdynamic")
+    endif()
   endif()
 endif()
 
index 5930e32ac549a9de10f1dda2c93951d83f169a10..8beade7e4c11ddc76d022f4c8c9fd19996468acf 100644 (file)
@@ -1,4 +1,5 @@
 include_directories(${X11_INCLUDE_DIR})
+include_directories(${CMAKE_SOURCE_DIR}/unix/common)
 include_directories(${CMAKE_SOURCE_DIR}/unix/tx)
 include_directories(${CMAKE_SOURCE_DIR}/unix)
 include_directories(${CMAKE_SOURCE_DIR}/common)
@@ -15,10 +16,11 @@ add_executable(x0vncserver
   x0vncserver.cxx
   XPixelBuffer.cxx
   XDesktop.cxx
+  RandrGlue.c
   ../vncconfig/QueryConnectDialog.cxx
 )
 
-target_link_libraries(x0vncserver tx rfb network rdr)
+target_link_libraries(x0vncserver tx rfb network rdr unixcommon)
 
 if(X11_FOUND AND X11_XTest_LIB)
   add_definitions(-DHAVE_XTEST)
@@ -41,6 +43,13 @@ else()
   message(WARNING "No XFIXES extension.  x0vncserver will not be able to show cursors.")
 endif()
 
+if(X11_FOUND AND X11_Xrandr_LIB)
+  add_definitions(-DHAVE_XRANDR)
+  target_link_libraries(x0vncserver ${X11_Xrandr_LIB})
+else()
+  message(WARNING "No Xrandr extension.  x0vncserver will not be able to resize session.")
+endif()
+
 target_link_libraries(x0vncserver ${X11_LIBRARIES})
 
 install(TARGETS x0vncserver DESTINATION ${BIN_DIR})
index 48c1842603c5da35ac553119d1c416f5ef4a4107..d911471333a46f2c99d9f1ece1e1c10b9bafefda 100644 (file)
@@ -35,10 +35,16 @@ StringParameter Geometry::m_geometryParam("Geometry",
   "");
 
 Geometry::Geometry(int fullWidth, int fullHeight)
-  : m_fullWidth(fullWidth),
-    m_fullHeight(fullHeight),
-    m_rect(0, 0, fullWidth, fullHeight)
 {
+  recalc(fullWidth, fullHeight);
+}
+
+void Geometry::recalc(int fullWidth, int fullHeight)
+{
+  m_fullWidth = fullWidth;
+  m_fullHeight = fullHeight;
+  m_rect.setXYWH(0, 0, fullWidth, fullHeight);
+
   // Parse geometry specification and save the result in m_rect.
   const char *param = m_geometryParam.getData();
   bool geometrySpecified = (strlen(param) > 0);
index 98bafb248dcafe0ce02ac21e7df9d4fb45ea2654..d938d63fc926ee8cc8b92420d1ab44ac1827994c 100644 (file)
@@ -30,6 +30,7 @@ class Geometry
 {
 public:
   Geometry(int fullWidth, int fullHeight);
+  void recalc(int fullWidth, int fullHeight);
 
   // Return coordinates and dimensions that specify a rectangular part
   // of the desktop that would be shown to RFB clients. This
diff --git a/unix/x0vncserver/RandrGlue.c b/unix/x0vncserver/RandrGlue.c
new file mode 100644 (file)
index 0000000..2e47763
--- /dev/null
@@ -0,0 +1,511 @@
+/* Copyright 2018 Peter Astrand <astrand@cendio.se> for Cendio AB
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifdef HAVE_XRANDR
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "RandrGlue.h"
+
+typedef struct _vncGlueContext {
+  Display *dpy;
+  XRRScreenResources *res;
+} vncGlueContext;
+
+static vncGlueContext randrGlueContext;
+
+void vncSetGlueContext(Display *dpy, void *res)
+{
+  randrGlueContext.dpy = dpy;
+  randrGlueContext.res = (XRRScreenResources *)res;
+}
+
+static RRMode vncRandRGetMatchingMode(XRROutputInfo *output,
+                                       unsigned int width, unsigned int height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+
+  /*
+   * We're not going to change which modes are preferred, but let's
+   * see if we can at least find a mode with matching dimensions.
+   */
+
+  if (output->crtc) {
+    XRRCrtcInfo *crtc;
+    unsigned int swap;
+
+    crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtc);
+    if (!crtc)
+      return None;
+
+    switch (crtc->rotation) {
+    case RR_Rotate_90:
+    case RR_Rotate_270:
+      swap = width;
+      width = height;
+      height = swap;
+      break;
+    }
+
+    XRRFreeCrtcInfo(crtc);
+  }
+
+  for (int i = 0; i < ctx->res->nmode; i++) {
+    for (int j = 0; j < output->nmode; j++) {
+      if ((output->modes[j] == ctx->res->modes[i].id) &&
+          (ctx->res->modes[i].width == width) &&
+          (ctx->res->modes[i].height == height)) {
+        return ctx->res->modes[i].id;
+      }
+    }
+  }
+
+  return None;
+}
+
+int vncGetScreenWidth(void)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  return DisplayWidth(ctx->dpy, DefaultScreen(ctx->dpy));
+}
+
+int vncGetScreenHeight(void)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  return DisplayHeight(ctx->dpy, DefaultScreen(ctx->dpy));
+}
+
+int vncRandRIsValidScreenSize(int width, int height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  /* Assert size ranges */
+  int minwidth, minheight, maxwidth, maxheight;
+  int ret = XRRGetScreenSizeRange(ctx->dpy, DefaultRootWindow(ctx->dpy),
+                                  &minwidth, &minheight,
+                                  &maxwidth, &maxheight);
+  if (!ret) {
+    return 0;
+  }
+  if (width < minwidth || maxwidth < width) {
+    return 0;
+  }
+  if (height < minheight || maxheight < height) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int vncRandRResizeScreen(int width, int height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+
+  int xwidth = DisplayWidth(ctx->dpy, DefaultScreen(ctx->dpy));
+  int xheight = DisplayHeight(ctx->dpy, DefaultScreen(ctx->dpy));
+  int xwidthmm = DisplayWidthMM(ctx->dpy, DefaultScreen(ctx->dpy));
+  int xheightmm = DisplayHeightMM(ctx->dpy, DefaultScreen(ctx->dpy));
+
+  /* Try to retain DPI when we resize */
+  XRRSetScreenSize(ctx->dpy, DefaultRootWindow(ctx->dpy), width, height,
+                   xwidthmm * width / xwidth,
+                   xheightmm * height / xheight);
+
+  return 1;
+}
+
+void vncRandRUpdateSetTime(void)
+{
+
+}
+
+int vncRandRHasOutputClones(void)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  for (int i = 0; i < ctx->res->ncrtc; i++) {
+    XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, ctx->res->crtcs[i]);
+    if (!crtc) {
+      return 0;
+    }
+    if (crtc->noutput > 1) {
+      XRRFreeCrtcInfo (crtc);
+      return 1;
+    }
+    XRRFreeCrtcInfo (crtc);
+  }
+  return 0;
+}
+
+int vncRandRGetOutputCount(void)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  return ctx->res->noutput;
+}
+
+int vncRandRGetAvailableOutputs(void)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+
+  int availableOutputs;
+  RRCrtc *usedCrtcs;
+  int numUsed;
+
+  int i, j, k;
+
+  usedCrtcs = (RRCrtc*)malloc(sizeof(RRCrtc) * ctx->res->ncrtc);
+  if (usedCrtcs == NULL)
+    return 0;
+
+  /*
+   * This gets slightly complicated because we might need to hook a CRTC
+   * up to the output, but also check that we don't try to use the same
+   * CRTC for multiple outputs.
+   */
+  availableOutputs = 0;
+  numUsed = 0;
+  for (i = 0;i < ctx->res->noutput; i++) {
+    XRROutputInfo *output;
+
+    output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[i]);
+    if (!output) {
+      continue;
+    }
+
+    if (output->crtc != None)
+      availableOutputs++;
+    else {
+      for (j = 0;j < output->ncrtc;j++) {
+        XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtcs[j]);
+        if (!crtc) {
+          continue;
+        }
+        if (crtc->noutput != 0) {
+          XRRFreeCrtcInfo(crtc);
+          continue;
+        }
+        XRRFreeCrtcInfo(crtc);
+
+        for (k = 0;k < numUsed;k++) {
+          if (usedCrtcs[k] == output->crtcs[j])
+            break;
+        }
+        if (k != numUsed)
+          continue;
+
+        availableOutputs++;
+
+        usedCrtcs[numUsed] = output->crtcs[j];
+        numUsed++;
+
+        break;
+      }
+    }
+    XRRFreeOutputInfo(output);
+  }
+
+  free(usedCrtcs);
+
+  return availableOutputs;
+}
+
+char *vncRandRGetOutputName(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  XRROutputInfo *output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return strdup("");
+  }
+  char *ret = strdup(output->name);
+  XRRFreeOutputInfo(output);
+  return ret;
+}
+
+int vncRandRIsOutputEnabled(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  XRROutputInfo *output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 0;
+  }
+
+  if (output->crtc == None) {
+    XRRFreeOutputInfo(output);
+    return 0;
+  }
+  XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtc);
+  XRRFreeOutputInfo(output);
+  if (!crtc) {
+    return 0;
+  }
+  if (crtc->mode == None) {
+    XRRFreeCrtcInfo(crtc);
+    return 0;
+  }
+  XRRFreeCrtcInfo(crtc);
+  return 1;
+}
+
+int vncRandRIsOutputUsable(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+
+  XRROutputInfo *output;
+  int i;
+
+  output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 0;
+  }
+
+  if (output->crtc != None) {
+    XRRFreeOutputInfo(output);
+    return 1;
+  }
+
+  /* Any unused CRTCs? */
+  for (i = 0;i < output->ncrtc;i++) {
+    XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtcs[i]);
+    if (crtc->noutput == 0) {
+      XRRFreeOutputInfo(output);
+      XRRFreeCrtcInfo(crtc);
+      return 1;
+    }
+    XRRFreeCrtcInfo(crtc);
+  }
+
+  XRRFreeOutputInfo(output);
+  return 0;
+}
+
+int vncRandRIsOutputConnected(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  XRROutputInfo *output;
+
+  output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 0;
+  }
+
+  int ret = (output->connection == RR_Connected);
+  XRRFreeOutputInfo(output);
+  return ret;
+}
+
+int vncRandRCheckOutputMode(int outputIdx, int width, int height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  XRROutputInfo *output;
+  RRMode mode;
+
+  output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output)
+    return 0;
+
+  /* Make sure we have the mode we want */
+  mode = vncRandRGetMatchingMode(output, width, height);
+  XRRFreeOutputInfo(output);
+
+  if (mode == None)
+    return 0;
+
+  return 1;
+}
+
+int vncRandRDisableOutput(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  RRCrtc crtcid;
+  int i;
+  int move = 0;
+
+  XRROutputInfo *output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 0;
+  }
+
+  crtcid = output->crtc;
+  if (crtcid == 0) {
+    XRRFreeOutputInfo(output);
+    return 1;
+  }
+
+  XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtc);
+  XRRFreeOutputInfo(output);
+  if (!crtc) {
+    return 0;
+  }
+
+  /* Remove this output from the CRTC configuration */
+  for (i = 0; i < crtc->noutput; i++) {
+    if (ctx->res->outputs[outputIdx] == crtc->outputs[i]) {
+      crtc->noutput -= 1;
+      move = 1;
+    }
+    if (move && i < crtc->noutput) {
+      crtc->outputs[i] = crtc->outputs[i+1];
+    }
+  }
+  if (crtc->noutput == 0) {
+    crtc->mode = None;
+    crtc->outputs = NULL;
+  }
+
+  int ret = XRRSetCrtcConfig(ctx->dpy,
+                             ctx->res,
+                             crtcid,
+                             CurrentTime,
+                             crtc->x, crtc->y,
+                             crtc->mode, crtc->rotation,
+                             crtc->outputs, crtc->noutput);
+
+  XRRFreeCrtcInfo(crtc);
+
+  return (ret == RRSetConfigSuccess);
+}
+
+unsigned int vncRandRGetOutputId(int outputIdx)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  return ctx->res->outputs[outputIdx];
+}
+
+int vncRandRGetOutputDimensions(int outputIdx,
+                                 int *x, int *y, int *width, int *height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+  int swap;
+  *x = *y = *width = *height = 0;
+
+  XRROutputInfo *output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 1;
+  }
+
+  if (!output->crtc) {
+    XRRFreeOutputInfo(output);
+    return 1;
+  }
+
+  XRRCrtcInfo *crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtc);
+  XRRFreeOutputInfo(output);
+  if (!crtc) {
+    return 1;
+  }
+  if (crtc->mode == None) {
+    XRRFreeCrtcInfo(crtc);
+    return 1;
+  }
+
+  *x = crtc->x;
+  *y = crtc->y;
+  for (int m = 0; m < ctx->res->nmode; m++) {
+    if (crtc->mode == ctx->res->modes[m].id) {
+      *width = ctx->res->modes[m].width;
+      *height = ctx->res->modes[m].height;
+    }
+  }
+
+  switch (crtc->rotation) {
+  case RR_Rotate_90:
+  case RR_Rotate_270:
+    swap = *width;
+    *width = *height;
+    *height = swap;
+    break;
+  }
+
+  XRRFreeCrtcInfo(crtc);
+  return 0;
+}
+
+int vncRandRReconfigureOutput(int outputIdx, int x, int y,
+                              int width, int height)
+{
+  vncGlueContext *ctx = &randrGlueContext;
+
+  XRROutputInfo *output;
+  RRCrtc crtcid;
+  RRMode mode;
+  XRRCrtcInfo *crtc = NULL;
+
+  int i, ret;
+
+  output = XRRGetOutputInfo(ctx->dpy, ctx->res, ctx->res->outputs[outputIdx]);
+  if (!output) {
+    return 0;
+  }
+
+  crtcid = output->crtc;
+
+  /* Need a CRTC? */
+  if (crtcid == None) {
+    for (i = 0;i < output->ncrtc;i++) {
+      crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, output->crtcs[i]);
+      if (!crtc) {
+        continue;
+      }
+
+      if (crtc->noutput != 0) {
+        XRRFreeCrtcInfo(crtc);
+        continue;
+      }
+
+      crtcid = output->crtcs[i];
+      break;
+    }
+  } else {
+    crtc = XRRGetCrtcInfo(ctx->dpy, ctx->res, crtcid);
+  }
+
+  /* Couldn't find one... */
+  if (crtc == NULL) {
+    XRRFreeOutputInfo(output);
+    return 0;
+  }
+
+  /* Make sure we have the mode we want */
+  mode = vncRandRGetMatchingMode(output, width, height);
+  if (mode == None) {
+    XRRFreeCrtcInfo(crtc);
+    XRRFreeOutputInfo(output);
+    return 0;
+  }
+
+  /* Reconfigure new mode and position */
+  ret = XRRSetCrtcConfig (ctx->dpy, ctx->res, crtcid, CurrentTime, x, y,
+                    mode, crtc->rotation, ctx->res->outputs+outputIdx, 1);
+
+  XRRFreeCrtcInfo(crtc);
+  XRRFreeOutputInfo(output);
+
+  return (ret == RRSetConfigSuccess);
+}
+
+int vncRandRCanCreateOutputs(int extraOutputs)
+{
+  return 0;
+}
+
+int vncRandRCreateOutputs(int extraOutputs)
+{
+  return 0;
+}
+
+#endif /* HAVE_XRANDR */
index 748796bee3b7c36ebe9963077abd7a884fd0da3c..59e2532372358dd4fcc6c519fc3f063312d296a8 100644 (file)
 #ifdef HAVE_XFIXES
 #include <X11/extensions/Xfixes.h>
 #endif
-
+#ifdef HAVE_XRANDR
+#include <X11/extensions/Xrandr.h>
+#include <RandrGlue.h>
+extern "C" {
+void vncSetGlueContext(Display *dpy, void *res);
+}
+#endif
 #include <x0vncserver/Geometry.h>
 #include <x0vncserver/XPixelBuffer.h>
 
@@ -162,6 +168,24 @@ XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
   }
 #endif
 
+#ifdef HAVE_XRANDR
+  int xrandrErrorBase;
+
+  randrSyncSerial = 0;
+  if (XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase)) {
+    XRRSelectInput(dpy, DefaultRootWindow(dpy),
+                   RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask);
+    /* Override TXWindow::init input mask */
+    XSelectInput(dpy, DefaultRootWindow(dpy),
+                 PropertyChangeMask | StructureNotifyMask | ExposureMask);
+  } else {
+#endif
+    vlog.info("RANDR extension not present");
+    vlog.info("Will not be able to handle session resize");
+#ifdef HAVE_XRANDR
+  }
+#endif
+
   TXWindow::setGlobalEventHandler(this);
 }
 
@@ -202,7 +226,7 @@ void XDesktop::start(VNCServer* vs) {
   vlog.info("Allocated %s", pb->getImage()->classDesc());
 
   server = (VNCServerST *)vs;
-  server->setPixelBuffer(pb);
+  server->setPixelBuffer(pb, computeScreenLayout());
 
 #ifdef HAVE_XDAMAGE
   if (haveDamage) {
@@ -331,6 +355,217 @@ void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
 void XDesktop::clientCutText(const char* str, int len) {
 }
 
+ScreenSet XDesktop::computeScreenLayout()
+{
+  ScreenSet layout;
+
+#ifdef HAVE_XRANDR
+  XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
+  if (!res) {
+    vlog.error("XRRGetScreenResources failed");
+    return layout;
+  }
+  vncSetGlueContext(dpy, res);
+
+  layout = ::computeScreenLayout(&outputIdMap);
+  XRRFreeScreenResources(res);
+#endif
+
+  return layout;
+}
+
+#ifdef HAVE_XRANDR
+/* Get the biggest mode which is equal or smaller to requested
+   size. If no such mode exists, return the smallest. */
+static void GetSmallerMode(XRRScreenResources *res,
+                    XRROutputInfo *output,
+                    unsigned int *width, unsigned int *height)
+{
+  XRRModeInfo best = {};
+  XRRModeInfo smallest = {};
+  smallest.width = -1;
+  smallest.height = -1;
+
+  for (int i = 0; i < res->nmode; i++) {
+    for (int j = 0; j < output->nmode; j++) {
+      if (output->modes[j] == res->modes[i].id) {
+        if ((res->modes[i].width > best.width && res->modes[i].width <= *width) &&
+            (res->modes[i].height > best.height && res->modes[i].height <= *height)) {
+          best = res->modes[i];
+        }
+        if ((res->modes[i].width < smallest.width) && res->modes[i].height < smallest.height) {
+          smallest = res->modes[i];
+        }
+      }
+    }
+  }
+
+  if (best.id == 0 && smallest.id != 0) {
+    best = smallest;
+  }
+
+  *width = best.width;
+  *height = best.height;
+}
+#endif /* HAVE_XRANDR */
+
+unsigned int XDesktop::setScreenLayout(int fb_width, int fb_height,
+                                       const rfb::ScreenSet& layout)
+{
+#ifdef HAVE_XRANDR
+  char buffer[2048];
+  vlog.debug("Got request for framebuffer resize to %dx%d",
+             fb_width, fb_height);
+  layout.print(buffer, sizeof(buffer));
+  vlog.debug("%s", buffer);
+
+  XRRScreenResources *res = XRRGetScreenResources(dpy, DefaultRootWindow(dpy));
+  if (!res) {
+    vlog.error("XRRGetScreenResources failed");
+    return rfb::resultProhibited;
+  }
+  vncSetGlueContext(dpy, res);
+
+  /* The client may request a screen layout which is not supported by
+     the Xserver. This happens, for example, when adjusting the size
+     of a non-fullscreen vncviewer window. To handle this and other
+     cases, we first call tryScreenLayout. If this fails, we try to
+     adjust the request to one screen with a smaller mode. */
+  vlog.debug("Testing screen layout");
+  unsigned int tryresult = ::tryScreenLayout(fb_width, fb_height, layout, &outputIdMap);
+  rfb::ScreenSet adjustedLayout;
+  if (tryresult == rfb::resultSuccess) {
+    adjustedLayout = layout;
+  } else {
+    vlog.debug("Impossible layout - trying to adjust");
+
+    ScreenSet::const_iterator firstscreen = layout.begin();
+    adjustedLayout.add_screen(*firstscreen);
+    ScreenSet::iterator iter = adjustedLayout.begin();
+    RROutput outputId = None;
+
+    for (int i = 0;i < vncRandRGetOutputCount();i++) {
+      unsigned int oi = vncRandRGetOutputId(i);
+
+      /* Known? */
+      if (outputIdMap.count(oi) == 0)
+        continue;
+
+      /* Find the corresponding screen... */
+      if (iter->id == outputIdMap[oi]) {
+        outputId = oi;
+      } else {
+        outputIdMap.erase(oi);
+      }
+    }
+
+    /* New screen */
+    if (outputId == None) {
+      int i = getPreferredScreenOutput(&outputIdMap, std::set<unsigned int>());
+      if (i != -1) {
+        outputId = vncRandRGetOutputId(i);
+      }
+    }
+    if (outputId == None) {
+      vlog.debug("Resize adjust: Could not find corresponding screen");
+      XRRFreeScreenResources(res);
+      return rfb::resultInvalid;
+    }
+    XRROutputInfo *output = XRRGetOutputInfo(dpy, res, outputId);
+    if (!output) {
+      vlog.debug("Resize adjust: XRRGetOutputInfo failed");
+      XRRFreeScreenResources(res);
+      return rfb::resultInvalid;
+    }
+    if (!output->crtc) {
+      vlog.debug("Resize adjust: Selected output has no CRTC");
+      XRRFreeScreenResources(res);
+      XRRFreeOutputInfo(output);
+      return rfb::resultInvalid;
+    }
+    XRRCrtcInfo *crtc = XRRGetCrtcInfo(dpy, res, output->crtc);
+    if (!crtc) {
+      vlog.debug("Resize adjust: XRRGetCrtcInfo failed");
+      XRRFreeScreenResources(res);
+      XRRFreeOutputInfo(output);
+      return rfb::resultInvalid;
+    }
+
+    unsigned int swidth = iter->dimensions.width();
+    unsigned int sheight = iter->dimensions.height();
+
+    switch (crtc->rotation) {
+    case RR_Rotate_90:
+    case RR_Rotate_270:
+      unsigned int swap = swidth;
+      swidth = sheight;
+      sheight = swap;
+      break;
+    }
+
+    GetSmallerMode(res, output, &swidth, &sheight);
+    XRRFreeOutputInfo(output);
+
+    switch (crtc->rotation) {
+    case RR_Rotate_90:
+    case RR_Rotate_270:
+      unsigned int swap = swidth;
+      swidth = sheight;
+      sheight = swap;
+      break;
+    }
+
+    XRRFreeCrtcInfo(crtc);
+
+    if (sheight != 0 && swidth != 0) {
+      vlog.debug("Adjusted resize request to %dx%d", swidth, sheight);
+      iter->dimensions.setXYWH(0, 0, swidth, sheight);
+      fb_width = swidth;
+      fb_height = sheight;
+    } else {
+      vlog.error("Failed to find smaller or equal screen size");
+      XRRFreeScreenResources(res);
+      return rfb::resultInvalid;
+    }
+  }
+
+  vlog.debug("Changing screen layout");
+  unsigned int ret = ::setScreenLayout(fb_width, fb_height, adjustedLayout, &outputIdMap);
+  XRRFreeScreenResources(res);
+
+  /* Send a dummy event to the root window. When this event is seen,
+     earlier change events (ConfigureNotify and/or CrtcChange) have
+     been processed. An Expose event is used for simplicity; does not
+     require any Atoms, and will not affect other applications. */
+  unsigned long serial = XNextRequest(dpy);
+  XExposeEvent ev = {}; /* zero x, y, width, height, count */
+  ev.type = Expose;
+  ev.display = dpy;
+  ev.window = DefaultRootWindow(dpy);
+  if (XSendEvent(dpy, DefaultRootWindow(dpy), False, ExposureMask, (XEvent*)&ev)) {
+    while (randrSyncSerial < serial) {
+      TXWindow::handleXEvents(dpy);
+    }
+  } else {
+    vlog.error("XSendEvent failed");
+  }
+
+  /* The protocol requires that an error is returned if the requested
+     layout could not be set. This is checked by
+     VNCSConnectionST::setDesktopSize. Another ExtendedDesktopSize
+     with reason=0 will be sent in response to the changes seen by the
+     event handler. */
+  if (adjustedLayout != layout) {
+    return rfb::resultInvalid;
+  } else {
+    return ret;
+  }
+
+#else
+  return rfb::resultProhibited;
+#endif /* HAVE_XRANDR */
+}
+
 
 bool XDesktop::handleGlobalEvent(XEvent* ev) {
   if (ev->type == xkbEventBase + XkbEventCode) {
@@ -378,6 +613,56 @@ bool XDesktop::handleGlobalEvent(XEvent* ev) {
       return false;
 
     return setCursor();
+#endif
+#ifdef HAVE_XRANDR
+  } else if (ev->type == Expose) {
+    XExposeEvent* eev = (XExposeEvent*)ev;
+    randrSyncSerial = eev->serial;
+
+    return false;
+
+  } else if (ev->type == ConfigureNotify) {
+    XConfigureEvent* cev = (XConfigureEvent*)ev;
+
+    if (cev->window != DefaultRootWindow(dpy)) {
+      return false;
+    }
+
+    XRRUpdateConfiguration(ev);
+    geometry->recalc(cev->width, cev->height);
+
+    if (!running) {
+      return false;
+    }
+
+    if ((cev->width != pb->width() || (cev->height != pb->height()))) {
+      // Recreate pixel buffer
+      ImageFactory factory((bool)useShm);
+      delete pb;
+      pb = new XPixelBuffer(dpy, factory, geometry->getRect());
+      server->setPixelBuffer(pb, computeScreenLayout());
+
+      // Mark entire screen as changed
+      server->add_changed(rfb::Region(Rect(0, 0, cev->width, cev->height)));
+    }
+
+    return true;
+
+  } else if (ev->type == xrandrEventBase + RRNotify) {
+    XRRNotifyEvent* rev = (XRRNotifyEvent*)ev;
+
+    if (rev->window != DefaultRootWindow(dpy)) {
+      return false;
+    }
+
+    if (!running)
+      return false;
+
+    if (rev->subtype == RRNotify_CrtcChange) {
+      server->setScreenLayout(computeScreenLayout());
+    }
+
+    return true;
 #endif
   }
 
index c9106f8b5d7d69ca62a1b21e798b281865035752..ff52c0149d7f2f82f177059783c11722ab38f51b 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <rfb/VNCServerST.h>
 #include <tx/TXWindow.h>
+#include <unixcommon.h>
 
 #include <X11/XKBlib.h>
 #ifdef HAVE_XDAMAGE
@@ -49,6 +50,9 @@ public:
   KeyCode XkbKeysymToKeycode(Display* dpy, KeySym keysym);
   virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down);
   virtual void clientCutText(const char* str, int len);
+  virtual unsigned int setScreenLayout(int fb_width, int fb_height,
+                                       const rfb::ScreenSet& layout);
+
   // -=- TXGlobalEventHandler interface
   virtual bool handleGlobalEvent(XEvent* ev);
 
@@ -70,12 +74,18 @@ protected:
   int xkbEventBase;
 #ifdef HAVE_XFIXES
   int xfixesEventBase;
+#endif
+#ifdef HAVE_XRANDR
+  int xrandrEventBase;
+  OutputIdMap outputIdMap;
+  unsigned long randrSyncSerial;
 #endif
   int ledMasks[XDESKTOP_N_LEDS];
   unsigned ledState;
   const unsigned short *codeMap;
   unsigned codeMapLen;
   bool setCursor();
+  rfb::ScreenSet computeScreenLayout();
 };
 
 #endif // __XDESKTOP_H__