Updated x0vncserver to the latest version from the "tightvnc-unix"
module. It includes support for overlay extensions under IRIX and
Solaris, MIT-SHM support, much improved polling algorithm and support
for IP filtering on accepting connections. However, current version
does not seem stable when linked with this codebase -- it is not
compatible with 1.3dev5 viewers for some reasons, and crashes on
"select: interrupted system call" from time to time. Also, the
"configure" script is not yet updated to enable certain features of
this version.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@309 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/x0vncserver/x0vncserver.cxx b/x0vncserver/x0vncserver.cxx
index df40c33..e0d05f0 100644
--- a/x0vncserver/x0vncserver.cxx
+++ b/x0vncserver/x0vncserver.cxx
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,9 +16,14 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
+
+// FIXME: Check cases when screen width/height is not a multiply of 32.
+// e.g. 800x600.
+
#include <strings.h>
#include <sys/time.h>
#include <sys/types.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <rfb/Logger_stdio.h>
@@ -33,8 +39,9 @@
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#ifdef HAVE_XTEST
#include <X11/extensions/XTest.h>
-
+#endif
#include <rfb/Encoder.h>
@@ -44,8 +51,14 @@
LogWriter vlog("main");
+IntParameter pollingCycle("PollingCycle", "Milliseconds per one "
+ "polling cycle", 50);
+BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
+BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
+ "IRIX or Solaris", true);
StringParameter displayname("display", "The X display", "");
IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
+StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
VncAuthPasswdFileParameter vncAuthPasswdFile;
static void CleanupSignalHandler(int sig)
@@ -61,8 +74,10 @@
{
public:
XDesktop(Display* dpy_)
- : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false)
+ : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false),
+ maxButtons(0), pollingStep(0)
{
+#ifdef HAVE_XTEST
int xtestEventBase;
int xtestErrorBase;
int major, minor;
@@ -73,27 +88,41 @@
vlog.info("XTest extension present - version %d.%d",major,minor);
haveXtest = true;
} else {
+#endif
vlog.info("XTest extension not present");
vlog.info("unable to inject events or display while server is grabbed");
+#ifdef HAVE_XTEST
}
+#endif
+
+ // Determine actual number of buttons of the X pointer device.
+ unsigned char btnMap[8];
+ int numButtons = XGetPointerMapping(dpy, btnMap, 8);
+ maxButtons = (numButtons > 8) ? 8 : numButtons;
+ vlog.info("Enabling %d button%s of X pointer device",
+ maxButtons, (maxButtons != 1) ? "s" : "");
int dpyWidth = DisplayWidth(dpy, DefaultScreen(dpy));
int dpyHeight = DisplayHeight(dpy, DefaultScreen(dpy));
- Visual* vis = DefaultVisual(dpy, DefaultScreen(dpy));
- image = new Image(dpy, dpyWidth, dpyHeight);
+ // FIXME: verify that all three images use the same pixel format.
+ ImageFactory factory((bool)useShm, (bool)useOverlay);
+ image = factory.newImage(dpy, dpyWidth, dpyHeight);
+ rowImage = factory.newImage(dpy, dpyWidth, 1);
+ tileImage = factory.newImage(dpy, 32, 32);
+
image->get(DefaultRootWindow(dpy));
pf.bpp = image->xim->bits_per_pixel;
pf.depth = image->xim->depth;
pf.bigEndian = (image->xim->byte_order == MSBFirst);
- pf.trueColour = (vis->c_class == TrueColor);
- pf.redShift = ffs(vis->red_mask) - 1;
- pf.greenShift = ffs(vis->green_mask) - 1;
- pf.blueShift = ffs(vis->blue_mask) - 1;
- pf.redMax = vis->red_mask >> pf.redShift;
- pf.greenMax = vis->green_mask >> pf.greenShift;
- pf.blueMax = vis->blue_mask >> pf.blueShift;
+ pf.trueColour = image->isTrueColor();
+ pf.redShift = ffs(image->xim->red_mask) - 1;
+ pf.greenShift = ffs(image->xim->green_mask) - 1;
+ pf.blueShift = ffs(image->xim->blue_mask) - 1;
+ pf.redMax = image->xim->red_mask >> pf.redShift;
+ pf.greenMax = image->xim->green_mask >> pf.greenShift;
+ pf.blueMax = image->xim->blue_mask >> pf.blueShift;
pb = new FullFramePixelBuffer(pf, dpyWidth, dpyHeight,
(rdr::U8*)image->xim->data, this);
@@ -110,10 +139,11 @@
// -=- SDesktop interface
virtual void pointerEvent(const Point& pos, rdr::U8 buttonMask) {
+#ifdef HAVE_XTEST
if (!haveXtest) return;
XTestFakeMotionEvent(dpy, DefaultScreen(dpy), pos.x, pos.y, CurrentTime);
if (buttonMask != oldButtonMask) {
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < maxButtons; i++) {
if ((buttonMask ^ oldButtonMask) & (1<<i)) {
if (buttonMask & (1<<i)) {
XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
@@ -124,13 +154,16 @@
}
}
oldButtonMask = buttonMask;
+#endif
}
virtual void keyEvent(rdr::U32 key, bool down) {
+#ifdef HAVE_XTEST
if (!haveXtest) return;
int keycode = XKeysymToKeycode(dpy, key);
if (keycode)
XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
+#endif
}
virtual void clientCutText(const char* str, int len) {
@@ -154,12 +187,73 @@
*b = xc.blue;
}
- virtual void poll() {
- if (server && server->clientsReadyForUpdate()) {
- image->get(DefaultRootWindow(dpy));
- server->add_changed(pb->getRect());
- server->tryUpdate();
+ //
+ // DEBUG: a version of poll() measuring time spent in the function.
+ //
+
+ virtual void pollDebug()
+ {
+ struct timeval timeSaved, timeNow;
+ struct timezone tz;
+ timeSaved.tv_sec = 0;
+ timeSaved.tv_usec = 0;
+ gettimeofday(&timeSaved, &tz);
+
+ poll();
+
+ gettimeofday(&timeNow, &tz);
+ int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
+ (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
+ if (diff != 0)
+ fprintf(stderr, "DEBUG: poll(): %4d ms\n", diff);
+ }
+
+ //
+ // Search for changed rectangles on the screen.
+ //
+
+ virtual void poll()
+ {
+ if (server == NULL)
+ return;
+
+ int nTilesChanged = 0;
+ int scanLine = XDesktop::pollingOrder[pollingStep++ % 32];
+ int bytesPerPixel = image->xim->bits_per_pixel / 8;
+ int bytesPerLine = image->xim->bytes_per_line;
+ int w = image->xim->width, h = image->xim->height;
+ Rect rect;
+
+ for (int y = 0; y * 32 < h; y++) {
+ int tile_h = (h - y * 32 >= 32) ? 32 : h - y * 32;
+ if (scanLine >= tile_h)
+ continue;
+ int scan_y = y * 32 + scanLine;
+ rowImage->get(DefaultRootWindow(dpy), 0, scan_y);
+ char *ptr_old = image->xim->data + scan_y * bytesPerLine;
+ char *ptr_new = rowImage->xim->data;
+ for (int x = 0; x * 32 < w; x++) {
+ int tile_w = (w - x * 32 >= 32) ? 32 : w - x * 32;
+ int nBytes = tile_w * bytesPerPixel;
+ if (memcmp(ptr_old, ptr_new, nBytes)) {
+ if (tile_w == 32 && tile_h == 32) {
+ tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32);
+ } else {
+ tileImage->get(DefaultRootWindow(dpy), x * 32, y * 32,
+ tile_w, tile_h);
+ }
+ image->updateRect(tileImage, x * 32, y * 32);
+ rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
+ server->add_changed(rect);
+ nTilesChanged++;
+ }
+ ptr_old += nBytes;
+ ptr_new += nBytes;
+ }
}
+
+ if (nTilesChanged)
+ server->tryUpdate();
}
protected:
@@ -168,10 +262,123 @@
PixelBuffer* pb;
VNCServer* server;
Image* image;
+ Image* rowImage;
+ Image* tileImage;
int oldButtonMask;
bool haveXtest;
+ int maxButtons;
+ unsigned int pollingStep;
+ static const int pollingOrder[];
};
+const int XDesktop::pollingOrder[32] = {
+ 0, 16, 8, 24, 4, 20, 12, 28,
+ 10, 26, 18, 2, 22, 6, 30, 14,
+ 1, 17, 9, 25, 7, 23, 15, 31,
+ 19, 3, 27, 11, 29, 13, 5, 21
+};
+
+
+class FileTcpFilter : public TcpFilter
+{
+
+public:
+
+ FileTcpFilter(const char *fname)
+ : TcpFilter("-"), fileName(NULL), lastModTime(0)
+ {
+ if (fname != NULL)
+ fileName = strdup((char *)fname);
+ }
+
+ virtual ~FileTcpFilter()
+ {
+ if (fileName != NULL)
+ free(fileName);
+ }
+
+ virtual bool verifyConnection(Socket* s)
+ {
+ if (!reloadRules()) {
+ vlog.error("Could not read IP filtering rules: rejecting all clients");
+ filter.clear();
+ filter.push_back(parsePattern("-"));
+ return false;
+ }
+
+ return TcpFilter::verifyConnection(s);
+ }
+
+protected:
+
+ bool reloadRules()
+ {
+ if (fileName == NULL)
+ return true;
+
+ struct stat st;
+ if (stat(fileName, &st) != 0)
+ return false;
+
+ if (st.st_mtime != lastModTime) {
+ // Actually reload only if the file was modified
+ FILE *fp = fopen(fileName, "r");
+ if (fp == NULL)
+ return false;
+
+ // Remove all the rules from the parent class
+ filter.clear();
+
+ // Parse the file contents adding rules to the parent class
+ char buf[32];
+ while (readLine(buf, 32, fp)) {
+ if (buf[0] && strchr("+-?", buf[0])) {
+ filter.push_back(parsePattern(buf));
+ }
+ }
+
+ fclose(fp);
+ lastModTime = st.st_mtime;
+ }
+ return true;
+ }
+
+protected:
+
+ char *fileName;
+ time_t lastModTime;
+
+private:
+
+ //
+ // NOTE: we silently truncate long lines in this function.
+ //
+
+ bool readLine(char *buf, int bufSize, FILE *fp)
+ {
+ if (fp == NULL || buf == NULL || bufSize == 0)
+ return false;
+
+ if (fgets(buf, bufSize, fp) == NULL)
+ return false;
+
+ char *ptr = strchr(buf, '\n');
+ if (ptr != NULL) {
+ *ptr = '\0'; // remove newline at the end
+ } else {
+ if (!feof(fp)) {
+ int c;
+ do { // skip the rest of a long line
+ c = getc(fp);
+ } while (c != '\n' && c != EOF);
+ }
+ }
+ return true;
+ }
+
+};
+
+
char* programName;
static void usage()
@@ -233,12 +440,21 @@
TcpListener listener((int)rfbport);
vlog.info("Listening on port %d", (int)rfbport);
+ FileTcpFilter fileTcpFilter(hostsFile.getData());
+ if (strlen(hostsFile.getData()) != 0)
+ listener.setFilter(&fileTcpFilter);
+
+ struct timeval timeSaved, timeNow;
+ struct timezone tz;
+ gettimeofday(&timeSaved, &tz);
+ timeSaved.tv_sec -= 60;
+
while (true) {
fd_set rfds;
struct timeval tv;
tv.tv_sec = 0;
- tv.tv_usec = 50*1000;
+ tv.tv_usec = (int)pollingCycle * 1000;
FD_ZERO(&rfds);
FD_SET(listener.getFd(), &rfds);
@@ -255,10 +471,19 @@
if (FD_ISSET(listener.getFd(), &rfds)) {
Socket* sock = listener.accept();
- server.addClient(sock);
+ if (sock) {
+ server.addClient(sock);
+ } else {
+ vlog.status("Client connection rejected");
+ }
}
server.getSockets(&sockets);
+
+ // Nothing more to do if there are no client connections.
+ if (sockets.empty())
+ continue;
+
for (i = sockets.begin(); i != sockets.end(); i++) {
if (FD_ISSET((*i)->getFd(), &rfds)) {
server.processSocketEvent(*i);
@@ -266,7 +491,19 @@
}
server.checkTimeouts();
- desktop.poll();
+
+ if (gettimeofday(&timeNow, &tz) == 0) {
+ int diff = (int)((timeNow.tv_usec - timeSaved.tv_usec + 500) / 1000 +
+ (timeNow.tv_sec - timeSaved.tv_sec) * 1000);
+ if (diff >= (int)pollingCycle) {
+ timeSaved = timeNow;
+ desktop.poll();
+ }
+ } else {
+ // Something strange has happened -- gettimeofday(2) failed.
+ // Poll after each select(), as in the original VNC4 code.
+ desktop.poll();
+ }
}
} catch (rdr::Exception &e) {