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/Image.cxx b/x0vncserver/Image.cxx
index 56a197e..880fa5f 100644
--- a/x0vncserver/Image.cxx
+++ b/x0vncserver/Image.cxx
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2003 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
@@ -19,24 +20,31 @@
// Image.cxx
//
-
#include <stdio.h>
#include <sys/types.h>
+
+#ifdef HAVE_MITSHM
#include <sys/ipc.h>
#include <sys/shm.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/extensions/XShm.h>
+#endif
+
#include "Image.h"
+
+//
+// ImageCleanup is used to delete Image instances automatically on
+// program shutdown. This is important for shared memory images.
+//
+
#include <list>
class ImageCleanup {
public:
- std::list<Image*> images;
+ std::list<Image *> images;
~ImageCleanup()
{
- fprintf(stderr,"~ImageCleanup called\n");
+ // DEBUG:
+ // fprintf(stderr, "~ImageCleanup() called\n");
while (!images.empty()) {
delete images.front();
@@ -46,6 +54,91 @@
ImageCleanup imageCleanup;
+//
+// Image class implementation.
+//
+
+Image::Image(Display *d)
+ : xim(NULL), dpy(d), trueColor(true)
+{
+ imageCleanup.images.push_back(this);
+}
+
+Image::Image(Display *d, int width, int height)
+ : xim(NULL), dpy(d), trueColor(true)
+{
+ imageCleanup.images.push_back(this);
+ Init(width, height);
+}
+
+void Image::Init(int width, int height)
+{
+ Visual* vis = DefaultVisual(dpy, DefaultScreen(dpy));
+ trueColor = (vis->c_class == TrueColor);
+
+ xim = XCreateImage(dpy, vis, DefaultDepth(dpy, DefaultScreen(dpy)),
+ ZPixmap, 0, 0, width, height, BitmapPad(dpy), 0);
+
+ xim->data = (char *)malloc(xim->bytes_per_line * xim->height);
+ if (xim->data == NULL) {
+ fprintf(stderr, "malloc() failed\n");
+ exit(1);
+ }
+}
+
+Image::~Image()
+{
+ // DEBUG:
+ // fprintf(stderr, "~Image() called\n");
+
+ imageCleanup.images.remove(this);
+ if (xim != NULL)
+ XDestroyImage(xim);
+}
+
+void Image::get(Window wnd, int x, int y)
+{
+ get(wnd, x, y, xim->width, xim->height);
+}
+
+void Image::get(Window wnd, int x, int y, int w, int h)
+{
+ XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, 0, 0);
+}
+
+void Image::updateRect(Image *src, int dst_x, int dst_y)
+{
+ updateRect(src->xim, dst_x, dst_y);
+}
+
+void Image::updateRect(XImage *src, int dst_x, int dst_y)
+{
+ // Limit width and height at destination image size
+ int w = src->width;
+ if (dst_x + w > xim->width)
+ w = xim->width - dst_x;
+ int h = src->height;
+ if (dst_y + h > xim->height)
+ h = xim->height - dst_y;
+
+ // Copy pixels
+ const char *srcOffset = src->data;
+ char *dstOffset = xim->data + (dst_y * xim->bytes_per_line +
+ dst_x * (xim->bits_per_pixel / 8));
+ int lineLength = w * (xim->bits_per_pixel / 8);
+ for (int i = 0; i < h ; i++) {
+ memcpy(dstOffset, srcOffset, lineLength);
+ srcOffset += src->bytes_per_line;
+ dstOffset += xim->bytes_per_line;
+ }
+}
+
+#ifdef HAVE_MITSHM
+
+//
+// ShmImage class implementation.
+//
+
static bool caughtShmError = false;
static int ShmCreationXErrorHandler(Display *dpy, XErrorEvent *error)
@@ -54,96 +147,360 @@
return 0;
}
-Image::Image(Display* d, int width, int height)
- : xim(0), dpy(d), shminfo(0), usingShm(false)
+ShmImage::ShmImage(Display *d)
+ : Image(d), shminfo(NULL)
{
- if (createShmImage(width, height)) return;
-
- xim = XCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)),
- DefaultDepth(dpy,DefaultScreen(dpy)), ZPixmap,
- 0, 0, width, height, BitmapPad(dpy), 0);
-
- xim->data = (char*)malloc(xim->bytes_per_line * xim->height);
- if (!xim->data) {
- fprintf(stderr,"malloc failed\n");
- exit(1);
- }
}
-Image::~Image()
+ShmImage::ShmImage(Display *d, int width, int height)
+ : Image(d), shminfo(NULL)
{
- fprintf(stderr,"~Image called - usingShm %d\n",usingShm);
- if (usingShm) {
- usingShm = false;
+ Init(width, height);
+}
+
+// FIXME: Remove duplication of cleanup operations.
+void ShmImage::Init(int width, int height, const XVisualInfo *vinfo)
+{
+ int major, minor;
+ Bool pixmaps;
+
+ if (!XShmQueryVersion(dpy, &major, &minor, &pixmaps)) {
+ fprintf(stderr, "XShmQueryVersion() failed\n");
+ return;
+ }
+
+ Visual *visual;
+ int depth;
+
+ if (vinfo == NULL) {
+ visual = DefaultVisual(dpy, DefaultScreen(dpy));
+ depth = DefaultDepth(dpy, DefaultScreen(dpy));
+ } else {
+ visual = vinfo->visual;
+ depth = vinfo->depth;
+ }
+
+ trueColor = (visual->c_class == TrueColor);
+
+ shminfo = new XShmSegmentInfo;
+
+ xim = XShmCreateImage(dpy, visual, depth, ZPixmap, 0, shminfo,
+ width, height);
+ if (xim == NULL) {
+ fprintf(stderr, "XShmCreateImage() failed\n");
+ delete shminfo;
+ shminfo = NULL;
+ return;
+ }
+
+ shminfo->shmid = shmget(IPC_PRIVATE,
+ xim->bytes_per_line * xim->height,
+ IPC_CREAT|0777);
+ if (shminfo->shmid == -1) {
+ perror("shmget");
+ fprintf(stderr,
+ "shmget() failed (%d bytes requested)\n",
+ int(xim->bytes_per_line * xim->height));
+ XDestroyImage(xim);
+ xim = NULL;
+ delete shminfo;
+ shminfo = NULL;
+ return;
+ }
+
+ shminfo->shmaddr = xim->data = (char *)shmat(shminfo->shmid, 0, 0);
+ if (shminfo->shmaddr == (char *)-1) {
+ perror("shmat");
+ fprintf(stderr,
+ "shmat() failed (%d bytes requested)\n",
+ int(xim->bytes_per_line * xim->height));
+ shmctl(shminfo->shmid, IPC_RMID, 0);
+ XDestroyImage(xim);
+ xim = NULL;
+ delete shminfo;
+ shminfo = NULL;
+ return;
+ }
+
+ shminfo->readOnly = False;
+
+ XErrorHandler oldHdlr = XSetErrorHandler(ShmCreationXErrorHandler);
+ XShmAttach(dpy, shminfo);
+ XSync(dpy, False);
+ XSetErrorHandler(oldHdlr);
+ if (caughtShmError) {
+ fprintf(stderr, "XShmAttach() failed\n");
shmdt(shminfo->shmaddr);
shmctl(shminfo->shmid, IPC_RMID, 0);
- imageCleanup.images.remove(this);
+ XDestroyImage(xim);
+ xim = NULL;
+ delete shminfo;
+ shminfo = NULL;
+ return;
}
- delete shminfo;
- if (xim) XDestroyImage(xim);
+
+ // DEBUG:
+ // fprintf(stderr,
+ // "Using shared memory XImage (%d bytes image data)\n",
+ // int(xim->bytes_per_line * xim->height));
}
-void Image::get(Window w)
+ShmImage::~ShmImage()
{
- if (usingShm) {
- XShmGetImage(dpy, w, xim, 0, 0, AllPlanes);
- } else {
- XGetSubImage(dpy, w, 0, 0, xim->width, xim->height,
- AllPlanes, ZPixmap, xim, 0, 0);
+ // DEBUG:
+ // fprintf(stderr,"~ShmImage called\n");
+
+ // FIXME: Destroy image as described in MIT-SHM documentation.
+ if (shminfo != NULL) {
+ shmdt(shminfo->shmaddr);
+ shmctl(shminfo->shmid, IPC_RMID, 0);
+ delete shminfo;
}
}
-bool Image::createShmImage(int width, int height)
+void ShmImage::get(Window wnd, int x, int y)
{
- if (XShmQueryExtension(dpy)) {
- shminfo = new XShmSegmentInfo;
+ XShmGetImage(dpy, wnd, xim, x, y, AllPlanes);
+}
- xim = XShmCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)),
- DefaultDepth(dpy,DefaultScreen(dpy)), ZPixmap,
- 0, shminfo, width, height);
+void ShmImage::get(Window wnd, int x, int y, int w, int h)
+{
+ // FIXME: Use SHM for this as well?
+ XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, 0, 0);
+}
- if (xim) {
- shminfo->shmid = shmget(IPC_PRIVATE,
- xim->bytes_per_line * xim->height,
- IPC_CREAT|0777);
+#ifdef HAVE_READDISPLAY
- if (shminfo->shmid != -1) {
- shminfo->shmaddr = xim->data = (char*)shmat(shminfo->shmid, 0, 0);
+//
+// IrixOverlayShmImage class implementation.
+//
- if (shminfo->shmaddr != (char *)-1) {
+IrixOverlayShmImage::IrixOverlayShmImage(Display *d)
+ : ShmImage(d), readDisplayBuf(NULL)
+{
+}
- shminfo->readOnly = False;
+IrixOverlayShmImage::IrixOverlayShmImage(Display *d, int width, int height)
+ : ShmImage(d), readDisplayBuf(NULL)
+{
+ Init(width, height);
+}
- XErrorHandler oldHdlr = XSetErrorHandler(ShmCreationXErrorHandler);
- XShmAttach(dpy, shminfo);
- XSync(dpy, False);
- XSetErrorHandler(oldHdlr);
+void IrixOverlayShmImage::Init(int width, int height)
+{
+ // First determine the pixel format used by XReadDisplay.
+ XVisualInfo vinfo;
+ if (!getOverlayVisualInfo(&vinfo))
+ return;
- if (!caughtShmError) {
- fprintf(stderr,"Using shared memory XImage\n");
- usingShm = true;
- imageCleanup.images.push_back(this);
- return true;
- }
+ // Create an SHM image of the same format.
+ ShmImage::Init(width, height, &vinfo);
+ if (xim == NULL)
+ return;
- shmdt(shminfo->shmaddr);
- } else {
- fprintf(stderr,"shmat failed\n");
- perror("shmat");
- }
+ // FIXME: Check if the extension is available at run time.
+ // FIXME: Does XShmCreateReadDisplayBuf() require some cleanup?
+ readDisplayBuf = XShmCreateReadDisplayBuf(dpy, NULL, shminfo, width, height);
+}
- shmctl(shminfo->shmid, IPC_RMID, 0);
- } else {
- fprintf(stderr,"shmget failed\n");
- perror("shmget");
+bool IrixOverlayShmImage::getOverlayVisualInfo(XVisualInfo *vinfo_ret)
+{
+ // First, get an image in the format returned by XReadDisplay.
+ unsigned long hints = 0, hints_ret;
+ XImage *testImage = XReadDisplay(dpy, DefaultRootWindow(dpy),
+ 0, 0, 8, 8, hints, &hints_ret);
+ if (testImage == NULL)
+ return false;
+
+ // Fill in a template for matching visuals.
+ XVisualInfo tmpl;
+ tmpl.c_class = TrueColor;
+ tmpl.depth = 24;
+ tmpl.red_mask = testImage->red_mask;
+ tmpl.green_mask = testImage->green_mask;
+ tmpl.blue_mask = testImage->blue_mask;
+
+ // List fields in template that make sense.
+ long mask = (VisualClassMask |
+ VisualRedMaskMask |
+ VisualGreenMaskMask |
+ VisualBlueMaskMask);
+
+ // We don't need that image any more.
+ XDestroyImage(testImage);
+
+ // Now, get a list of matching visuals available.
+ int nVisuals;
+ XVisualInfo *vinfo = XGetVisualInfo(dpy, mask, &tmpl, &nVisuals);
+ if (vinfo == NULL || nVisuals <= 0) {
+ if (vinfo != NULL) {
+ XFree(vinfo);
+ }
+ return false;
+ }
+
+ // Use first visual from the list.
+ *vinfo_ret = vinfo[0];
+
+ XFree(vinfo);
+
+ return true;
+}
+
+IrixOverlayShmImage::~IrixOverlayShmImage()
+{
+ // DEBUG:
+ // fprintf(stderr,"~IrixOverlayShmImage called\n");
+}
+
+void IrixOverlayShmImage::get(Window wnd, int x, int y)
+{
+ XRectangle rect;
+ unsigned long hints = XRD_TRANSPARENT | XRD_READ_POINTER;
+
+ rect.x = x;
+ rect.y = y;
+ rect.width = xim->width;
+ rect.height = xim->height;
+
+ XShmReadDisplayRects(dpy, wnd,
+ &rect, 1, readDisplayBuf, -x, -y,
+ hints, &hints);
+}
+
+void IrixOverlayShmImage::get(Window wnd, int x, int y, int w, int h)
+{
+ // FIXME: Use XReadDisplay extension here as well!
+ XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, 0, 0);
+}
+
+#endif // HAVE_READDISPLAY
+#endif // HAVE_MITSHM
+
+#ifdef HAVE_SUN_OVL
+
+//
+// SolarisOverlayImage class implementation
+//
+
+SolarisOverlayImage::SolarisOverlayImage(Display *d)
+ : Image(d)
+{
+}
+
+SolarisOverlayImage::SolarisOverlayImage(Display *d, int width, int height)
+ : Image(d)
+{
+ Init(width, height);
+}
+
+void SolarisOverlayImage::Init(int width, int height)
+{
+ // FIXME: Check if the extension is available at run time.
+ // FIXME: Maybe just read a small (e.g. 8x8) screen area then
+ // reallocate xim->data[] and correct width and height?
+ xim = XReadScreen(dpy, DefaultRootWindow(dpy), 0, 0, width, height, True);
+ if (xim == NULL) {
+ fprintf(stderr, "XReadScreen() failed\n");
+ return;
+ }
+}
+
+SolarisOverlayImage::~SolarisOverlayImage()
+{
+ // DEBUG:
+ // fprintf(stderr, "~SolarisOverlayImage() called\n");
+}
+
+void SolarisOverlayImage::get(Window wnd, int x, int y)
+{
+ get(wnd, x, y, xim->width, xim->height);
+}
+
+void SolarisOverlayImage::get(Window wnd, int x, int y, int w, int h)
+{
+ XImage *tmp_xim = XReadScreen(dpy, wnd, x, y, w, h, True);
+ if (tmp_xim == NULL)
+ return;
+
+ updateRect(tmp_xim, 0, 0);
+
+ XDestroyImage(tmp_xim);
+}
+
+#endif // HAVE_SUN_OVL
+
+//
+// ImageFactory class implementation
+//
+
+// Prepare useful shortcuts for compile-time options.
+#if defined(HAVE_READDISPLAY) && defined(HAVE_MITSHM)
+#define HAVE_SHM_READDISPLAY
+#endif
+#if defined(HAVE_SHM_READDISPLAY) || defined(HAVE_SUN_OVL)
+#define HAVE_OVERLAY_EXT
+#endif
+
+ImageFactory::ImageFactory(bool allowShm, bool allowOverlay)
+ : mayUseShm(allowShm), mayUseOverlay(allowOverlay)
+{
+}
+
+ImageFactory::~ImageFactory()
+{
+}
+
+Image *ImageFactory::newImage(Display *d, int width, int height)
+{
+ Image *image = NULL;
+
+ // First, try to create an image with overlay support.
+
+#ifdef HAVE_OVERLAY_EXT
+ if (mayUseOverlay) {
+#if defined(HAVE_SHM_READDISPLAY)
+ if (mayUseShm) {
+ image = new IrixOverlayShmImage(d, width, height);
+ if (image->xim != NULL) {
+ fprintf(stderr, "Using IRIX overlay image with SHM support\n");
+ return image;
}
-
- XDestroyImage(xim);
- xim = 0;
- } else {
- fprintf(stderr,"XShmCreateImage failed\n");
+ }
+#elif defined(HAVE_SUN_OVL)
+ image = new SolarisOverlayImage(d, width, height);
+ if (image->xim != NULL) {
+ fprintf(stderr, "Using Solaris overlay image\n");
+ return image;
+ }
+#endif
+ if (image != NULL) {
+ delete image;
+ fprintf(stderr,
+ "Failed to create overlay image, trying other options\n");
}
}
+#endif // HAVE_OVERLAY_EXT
- return false;
+ // Now, try to use shared memory image.
+
+#ifdef HAVE_MITSHM
+ if (mayUseShm) {
+ image = new ShmImage(d, width, height);
+ if (image->xim != NULL) {
+ fprintf(stderr, "Using shared memory image\n");
+ return image;
+ }
+
+ delete image;
+ fprintf(stderr,
+ "Failed to create SHM image, falling back to Xlib image\n");
+ }
+#endif // HAVE_MITSHM
+
+ // Fall back to Xlib image.
+
+ fprintf(stderr, "Using Xlib-based image\n");
+ image = new Image(d, width, height);
+ return image;
}
diff --git a/x0vncserver/Image.h b/x0vncserver/Image.h
index 1a9ff1c..032e24a 100644
--- a/x0vncserver/Image.h
+++ b/x0vncserver/Image.h
@@ -1,4 +1,5 @@
/* Copyright (C) 2002-2003 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
@@ -23,26 +24,159 @@
#define __IMAGE_H__
#include <X11/Xlib.h>
-#include <X11/extensions/XShm.h>
+#include <X11/Xutil.h>
+
+//
+// Image class is an Xlib-based implementation of screen image storage.
+//
class Image {
public:
- Image(Display* dpy, int width, int height);
- ~Image();
+ Image(Display *d);
+ Image(Display *d, int width, int height);
+ virtual ~Image();
- void get(Window w);
+ bool isTrueColor() { return trueColor; }
+ virtual void get(Window wnd, int x = 0, int y = 0);
+ virtual void get(Window wnd, int x, int y, int w, int h);
+
+ virtual void updateRect(Image *src, int dst_x = 0, int dst_y = 0);
+ virtual void updateRect(XImage *src, int dst_x = 0, int dst_y = 0);
+
+ // Pointer to corresponding XImage, made public for efficiency.
+ // NOTE: if this field is NULL, then no methods other than Init()
+ // may be called.
XImage* xim;
-private:
+protected:
- bool createShmImage(int width, int height);
+ void Init(int width, int height);
- Display* dpy;
- XShmSegmentInfo* shminfo;
- bool usingShm;
+ Display *dpy;
+ bool trueColor;
+
};
-#endif
+//
+// ShmImage uses MIT-SHM extension of an X server to get image data.
+//
+
+#ifdef HAVE_MITSHM
+
+#include <X11/extensions/XShm.h>
+
+class ShmImage : public Image {
+
+public:
+
+ ShmImage(Display *d);
+ ShmImage(Display *d, int width, int height);
+ virtual ~ShmImage();
+
+ virtual void get(Window wnd, int x = 0, int y = 0);
+ virtual void get(Window wnd, int x, int y, int w, int h);
+
+protected:
+
+ void Init(int width, int height, const XVisualInfo *vinfo = NULL);
+
+ XShmSegmentInfo *shminfo;
+
+};
+
+//
+// IrixOverlayShmImage uses ReadDisplay extension of an X server to
+// get truecolor image data, regardless of the default X visual type.
+// This method is available on Irix only.
+//
+
+#ifdef HAVE_READDISPLAY
+
+#include <X11/extensions/readdisplay.h>
+
+class IrixOverlayShmImage : public ShmImage {
+
+public:
+
+ IrixOverlayShmImage(Display *d);
+ IrixOverlayShmImage(Display *d, int width, int height);
+ virtual ~IrixOverlayShmImage();
+
+ virtual void get(Window wnd, int x = 0, int y = 0);
+ virtual void get(Window wnd, int x, int y, int w, int h);
+
+protected:
+
+ void Init(int width, int height);
+
+ // This method searches available X visuals for one that matches
+ // actual pixel format returned by XReadDisplay(). Returns true on
+ // success, false if there is no matching visual. On success, visual
+ // information is placed into the structure pointed by vinfo_ret.
+ bool getOverlayVisualInfo(XVisualInfo *vinfo_ret);
+
+ ShmReadDisplayBuf *readDisplayBuf;
+
+};
+
+#endif // HAVE_READDISPLAY
+#endif // HAVE_MITSHM
+
+//
+// SolarisOverlayImage uses SUN_OVL extension of an X server to get
+// truecolor image data, regardless of the default X visual type. This
+// method is available on Solaris only.
+//
+
+#ifdef HAVE_SUN_OVL
+
+#include <X11/extensions/transovl.h>
+
+class SolarisOverlayImage : public Image {
+
+public:
+
+ SolarisOverlayImage(Display *d);
+ SolarisOverlayImage(Display *d, int width, int height);
+ virtual ~SolarisOverlayImage();
+
+ virtual void get(Window wnd, int x = 0, int y = 0);
+ virtual void get(Window wnd, int x, int y, int w, int h);
+
+protected:
+
+ void Init(int width, int height);
+
+};
+
+#endif // HAVE_SUN_OVL
+
+//
+// ImageFactory class is used to produce instances of Image-derived
+// objects that are most appropriate for current X server and user
+// settings.
+//
+
+class ImageFactory {
+
+public:
+
+ ImageFactory(bool allowShm, bool allowOverlay);
+ virtual ~ImageFactory();
+
+ bool isShmAllowed() { return mayUseShm; }
+ bool isOverlayAllowed() { return mayUseOverlay; }
+
+ virtual Image *newImage(Display *d, int width, int height);
+
+protected:
+
+ bool mayUseShm;
+ bool mayUseOverlay;
+
+};
+
+#endif // __IMAGE_H__
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) {