From 242c5b2c8af6d89a34ff83089e9e724e32dcb279 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Peter=20=C3=85strand=20=28astrand=29?= Date: Wed, 7 Mar 2018 13:00:47 +0100 Subject: [PATCH] Add RandR support for x0vncserver 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 | 3 + unix/x0vncserver/CMakeLists.txt | 11 +- unix/x0vncserver/Geometry.cxx | 12 +- unix/x0vncserver/Geometry.h | 1 + unix/x0vncserver/RandrGlue.c | 511 ++++++++++++++++++++++++++++++++ unix/x0vncserver/XDesktop.cxx | 289 +++++++++++++++++- unix/x0vncserver/XDesktop.h | 10 + 7 files changed, 831 insertions(+), 6 deletions(-) create mode 100644 unix/x0vncserver/RandrGlue.c diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 06883c6f..4b58b1de 100644 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -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() diff --git a/unix/x0vncserver/CMakeLists.txt b/unix/x0vncserver/CMakeLists.txt index 5930e32a..8beade7e 100644 --- a/unix/x0vncserver/CMakeLists.txt +++ b/unix/x0vncserver/CMakeLists.txt @@ -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}) diff --git a/unix/x0vncserver/Geometry.cxx b/unix/x0vncserver/Geometry.cxx index 48c18426..d9114713 100644 --- a/unix/x0vncserver/Geometry.cxx +++ b/unix/x0vncserver/Geometry.cxx @@ -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); diff --git a/unix/x0vncserver/Geometry.h b/unix/x0vncserver/Geometry.h index 98bafb24..d938d63f 100644 --- a/unix/x0vncserver/Geometry.h +++ b/unix/x0vncserver/Geometry.h @@ -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 index 00000000..2e477630 --- /dev/null +++ b/unix/x0vncserver/RandrGlue.c @@ -0,0 +1,511 @@ +/* Copyright 2018 Peter Astrand 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 +#include +#include +#include + +#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 */ diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx index 748796be..59e25323 100644 --- a/unix/x0vncserver/XDesktop.cxx +++ b/unix/x0vncserver/XDesktop.cxx @@ -30,7 +30,13 @@ #ifdef HAVE_XFIXES #include #endif - +#ifdef HAVE_XRANDR +#include +#include +extern "C" { +void vncSetGlueContext(Display *dpy, void *res); +} +#endif #include #include @@ -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()); + 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 } diff --git a/unix/x0vncserver/XDesktop.h b/unix/x0vncserver/XDesktop.h index c9106f8b..ff52c014 100644 --- a/unix/x0vncserver/XDesktop.h +++ b/unix/x0vncserver/XDesktop.h @@ -23,6 +23,7 @@ #include #include +#include #include #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__ -- 2.39.5