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.tags/v1.8.90
@@ -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() | |||
@@ -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}) |
@@ -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); |
@@ -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 |
@@ -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 */ |
@@ -30,7 +30,13 @@ | |||
#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 | |||
} | |||
@@ -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__ |