diff options
author | Constantin Kaplinsky <const@tightvnc.com> | 2006-05-25 05:04:46 +0000 |
---|---|---|
committer | Constantin Kaplinsky <const@tightvnc.com> | 2006-05-25 05:04:46 +0000 |
commit | b30ae7facbdf8273f34f5d67d3d2e9c81db75576 (patch) | |
tree | 5091b0a7b991672b19c17b86b263e5ff4f173a3e /unix/x0vncserver | |
parent | a2adc8d4cfdf7336ce9192414c5e775224742a97 (diff) | |
download | tigervnc-b30ae7facbdf8273f34f5d67d3d2e9c81db75576.tar.gz tigervnc-b30ae7facbdf8273f34f5d67d3d2e9c81db75576.zip |
Migrating to new directory structure adopted from the RealVNC's source tree. More changes will follow.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@590 3789f03b-4d11-0410-bbf8-ca57d06f2519
Diffstat (limited to 'unix/x0vncserver')
-rw-r--r-- | unix/x0vncserver/Geometry.cxx | 71 | ||||
-rw-r--r-- | unix/x0vncserver/Geometry.h | 50 | ||||
-rw-r--r-- | unix/x0vncserver/Image.cxx | 549 | ||||
-rw-r--r-- | unix/x0vncserver/Image.h | 223 | ||||
-rw-r--r-- | unix/x0vncserver/Makefile.in | 30 | ||||
-rw-r--r-- | unix/x0vncserver/PollingManager.cxx | 600 | ||||
-rw-r--r-- | unix/x0vncserver/PollingManager.h | 148 | ||||
-rw-r--r-- | unix/x0vncserver/PollingScheduler.cxx | 211 | ||||
-rw-r--r-- | unix/x0vncserver/PollingScheduler.h | 98 | ||||
-rw-r--r-- | unix/x0vncserver/TimeMillis.cxx | 49 | ||||
-rw-r--r-- | unix/x0vncserver/TimeMillis.h | 47 | ||||
-rw-r--r-- | unix/x0vncserver/buildtime.c | 18 | ||||
-rw-r--r-- | unix/x0vncserver/x0vncserver.cxx | 580 | ||||
-rw-r--r-- | unix/x0vncserver/x0vncserver.man | 33 |
14 files changed, 2707 insertions, 0 deletions
diff --git a/unix/x0vncserver/Geometry.cxx b/unix/x0vncserver/Geometry.cxx new file mode 100644 index 00000000..ccb4e699 --- /dev/null +++ b/unix/x0vncserver/Geometry.cxx @@ -0,0 +1,71 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// Geometry.cxx +// + +#include <rfb/Rect.h> +#include <rfb/LogWriter.h> +#include <x0vncserver/Geometry.h> + +static LogWriter vlog("Geometry"); + +StringParameter Geometry::m_geometryParam("Geometry", + "Screen area shown to VNC clients. " + "Format is <width>x<height>+<offset_x>+<offset_y>, " + "more information in man X, section GEOMETRY SPECIFICATIONS. " + "If the argument is empty, full screen is shown to VNC clients.", + ""); + +Geometry::Geometry(int fullWidth, int fullHeight) + : m_width(fullWidth), m_height(fullHeight), + m_offsetLeft(0), m_offsetTop(0) +{ + const char *param = m_geometryParam.getData(); + if (strlen(param) != 0) { + int w, h; + int x = 0, y = 0; + char sign_x[2] = "+"; + char sign_y[2] = "+"; + int n = sscanf(param, "%dx%d%1[+-]%d%1[+-]%d", + &w, &h, sign_x, &x, sign_y, &y); + if ((n == 2 || n == 6) && w > 0 && h > 0 && x >= 0 && y >= 0) { + if (sign_x[0] == '-') + x = fullWidth - w - x; + if (sign_y[0] == '-') + y = fullHeight - h - y; + Rect fullRect(0, 0, fullWidth, fullHeight); + Rect partRect(x, y, x + w, y + h); + Rect r = partRect.intersect(fullRect); + if (r.area() > 0) { + m_width = r.width(); + m_height = r.height(); + m_offsetLeft = r.tl.x; + m_offsetTop = r.tl.y; + } else { + vlog.error("Requested area is out of the desktop boundaries"); + } + } else { + vlog.error("Wrong argument format"); + } + } + vlog.info("Desktop geometry is %dx%d+%d+%d", + m_width, m_height, m_offsetLeft, m_offsetTop); +} + diff --git a/unix/x0vncserver/Geometry.h b/unix/x0vncserver/Geometry.h new file mode 100644 index 00000000..95059e7d --- /dev/null +++ b/unix/x0vncserver/Geometry.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// Geometry.h +// + +#ifndef __GEOMETRY_H__ +#define __GEOMETRY_H__ + +#include <rfb/Configuration.h> + +using namespace rfb; + +class Geometry +{ +public: + Geometry(int fullWidth, int fullHeight); + + int width() const { return m_width; } + int height() const { return m_height; } + int offsetLeft() const { return m_offsetLeft; } + int offsetTop() const { return m_offsetTop; } + +protected: + static StringParameter m_geometryParam; + + int m_width; + int m_height; + int m_offsetLeft; + int m_offsetTop; +}; + +#endif // __GEOMETRY_H__ + diff --git a/unix/x0vncserver/Image.cxx b/unix/x0vncserver/Image.cxx new file mode 100644 index 00000000..fc66c5a9 --- /dev/null +++ b/unix/x0vncserver/Image.cxx @@ -0,0 +1,549 @@ +/* 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 + * 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. + */ +// +// Image.cxx +// + +#include <stdio.h> +#include <sys/types.h> + +#ifdef HAVE_MITSHM +#include <sys/ipc.h> +#include <sys/shm.h> +#endif + +#include <rfb/LogWriter.h> +#include <x0vncserver/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; + + ~ImageCleanup() + { + while (!images.empty()) { + delete images.front(); + } + } +}; + +ImageCleanup imageCleanup; + +// +// Image class implementation. +// + +static rfb::LogWriter vlog("Image"); + +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) { + vlog.error("malloc() failed"); + exit(1); + } +} + +Image::~Image() +{ + imageCleanup.images.remove(this); + + // XDestroyImage will free xim->data if necessary + 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); +} + +// +// Copying pixels from one image to another. +// +// FIXME: Use Point and Rect structures? +// FIXME: Too many similar methods? +// + +inline +void Image::copyPixels(XImage *src, + int dst_x, int dst_y, + int src_x, int src_y, + int w, int h) +{ + const char *srcOffset = + src->data + (src_y * src->bytes_per_line + + src_x * (src->bits_per_pixel / 8)); + char *dstOffset = + xim->data + (dst_y * xim->bytes_per_line + + dst_x * (xim->bits_per_pixel / 8)); + + int rowLength = w * (xim->bits_per_pixel / 8); + + for (int i = 0; i < h ; i++) { + memcpy(dstOffset, srcOffset, rowLength); + srcOffset += src->bytes_per_line; + dstOffset += xim->bytes_per_line; + } +} + +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; + + copyPixels(src, dst_x, dst_y, 0, 0, w, h); +} + +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, int w, int h) +{ + // Correct width and height if necessary. + if (w > src->width) + w = src->width; + if (dst_x + w > xim->width) + w = xim->width - dst_x; + if (h > src->height) + h = src->height; + if (dst_y + h > xim->height) + h = xim->height - dst_y; + + copyPixels(src, dst_x, dst_y, 0, 0, w, h); +} + +void Image::updateRect(Image *src, int dst_x, int dst_y, int w, int h) +{ + updateRect(src->xim, dst_x, dst_y, w, h); +} + +void Image::updateRect(XImage *src, int dst_x, int dst_y, + int src_x, int src_y, int w, int h) +{ + // Correct width and height if necessary. + if (src_x + w > src->width) + w = src->width - src_x; + if (dst_x + w > xim->width) + w = xim->width - dst_x; + if (src_y + h > src->height) + h = src->height - src_y; + if (dst_y + h > xim->height) + h = xim->height - dst_y; + + copyPixels(src, dst_x, dst_y, src_x, src_y, w, h); +} + +void Image::updateRect(Image *src, int dst_x, int dst_y, + int src_x, int src_y, int w, int h) +{ + updateRect(src->xim, dst_x, dst_y, src_x, src_y, w, h); +} + +#ifdef HAVE_MITSHM + +// +// ShmImage class implementation. +// + +static bool caughtShmError = false; + +static int ShmCreationXErrorHandler(Display *dpy, XErrorEvent *error) +{ + caughtShmError = true; + return 0; +} + +ShmImage::ShmImage(Display *d) + : Image(d), shminfo(NULL) +{ +} + +ShmImage::ShmImage(Display *d, int width, int height) + : Image(d), shminfo(NULL) +{ + 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)) { + vlog.error("XShmQueryVersion() failed"); + 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) { + vlog.error("XShmCreateImage() failed"); + 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"); + vlog.error("shmget() failed (%d bytes requested)", + 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"); + vlog.error("shmat() failed (%d bytes requested)", + 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) { + vlog.error("XShmAttach() failed"); + shmdt(shminfo->shmaddr); + shmctl(shminfo->shmid, IPC_RMID, 0); + XDestroyImage(xim); + xim = NULL; + delete shminfo; + shminfo = NULL; + return; + } +} + +ShmImage::~ShmImage() +{ + // FIXME: Destroy image as described in MIT-SHM documentation. + if (shminfo != NULL) { + shmdt(shminfo->shmaddr); + shmctl(shminfo->shmid, IPC_RMID, 0); + delete shminfo; + } +} + +void ShmImage::get(Window wnd, int x, int y) +{ + XShmGetImage(dpy, wnd, xim, x, y, AllPlanes); +} + +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); +} + +#ifdef HAVE_READDISPLAY + +// +// IrixOverlayShmImage class implementation. +// + +IrixOverlayShmImage::IrixOverlayShmImage(Display *d) + : ShmImage(d), readDisplayBuf(NULL) +{ +} + +IrixOverlayShmImage::IrixOverlayShmImage(Display *d, int width, int height) + : ShmImage(d), readDisplayBuf(NULL) +{ + Init(width, height); +} + +void IrixOverlayShmImage::Init(int width, int height) +{ + // First determine the pixel format used by XReadDisplay. + XVisualInfo vinfo; + if (!getOverlayVisualInfo(&vinfo)) + return; + + // Create an SHM image of the same format. + ShmImage::Init(width, height, &vinfo); + if (xim == NULL) + return; + + // FIXME: Check if the extension is available at run time. + readDisplayBuf = XShmCreateReadDisplayBuf(dpy, NULL, shminfo, width, height); +} + +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() +{ + if (readDisplayBuf != NULL) + XShmDestroyReadDisplayBuf(readDisplayBuf); +} + +void IrixOverlayShmImage::get(Window wnd, int x, int y) +{ + get(wnd, x, y, xim->width, xim->height); +} + +void IrixOverlayShmImage::get(Window wnd, int x, int y, int w, int h) +{ + XRectangle rect; + unsigned long hints = XRD_TRANSPARENT | XRD_READ_POINTER; + + rect.x = x; + rect.y = y; + rect.width = w; + rect.height = h; + + XShmReadDisplayRects(dpy, wnd, + &rect, 1, readDisplayBuf, -x, -y, + hints, &hints); +} + +#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) { + vlog.error("XReadScreen() failed"); + return; + } +} + +SolarisOverlayImage::~SolarisOverlayImage() +{ +} + +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 +// +// FIXME: Make ImageFactory always create images of the same class? +// + +// 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) { + return image; + } + } +#elif defined(HAVE_SUN_OVL) + image = new SolarisOverlayImage(d, width, height); + if (image->xim != NULL) { + return image; + } +#endif + if (image != NULL) { + delete image; + vlog.error("Failed to create overlay image, trying other options"); + } + } +#endif // HAVE_OVERLAY_EXT + + // Now, try to use shared memory image. + +#ifdef HAVE_MITSHM + if (mayUseShm) { + image = new ShmImage(d, width, height); + if (image->xim != NULL) { + return image; + } + + delete image; + vlog.error("Failed to create SHM image, falling back to Xlib image"); + } +#endif // HAVE_MITSHM + + // Fall back to Xlib image. + + image = new Image(d, width, height); + return image; +} diff --git a/unix/x0vncserver/Image.h b/unix/x0vncserver/Image.h new file mode 100644 index 00000000..535cee6a --- /dev/null +++ b/unix/x0vncserver/Image.h @@ -0,0 +1,223 @@ +/* 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 + * 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. + */ +// +// Image.h +// + +#ifndef __IMAGE_H__ +#define __IMAGE_H__ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +// +// Image class is an Xlib-based implementation of screen image storage. +// + +class Image { + +public: + + Image(Display *d); + Image(Display *d, int width, int height); + virtual ~Image(); + + bool isTrueColor() const { return trueColor; } + + virtual const char *className() const { + return "Image"; + } + virtual const char *classDesc() const { + return "basic Xlib image"; + } + + virtual void get(Window wnd, int x = 0, int y = 0); + virtual void get(Window wnd, int x, int y, int w, int h); + +// Copying pixels from one image to another. + virtual void updateRect(XImage *src, int dst_x = 0, int dst_y = 0); + virtual void updateRect(Image *src, int dst_x = 0, int dst_y = 0); + virtual void updateRect(XImage *src, int dst_x, int dst_y, int w, int h); + virtual void updateRect(Image *src, int dst_x, int dst_y, int w, int h); + virtual void updateRect(XImage *src, int dst_x, int dst_y, + int src_x, int src_y, int w, int h); + virtual void updateRect(Image *src, int dst_x, int dst_y, + int src_x, int src_y, int w, int h); + + // 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; + +protected: + + void Init(int width, int height); + + // Like updateRect(), but does not check arguments. + void copyPixels(XImage *src, + int dst_x, int dst_y, + int src_x, int src_y, + int w, int h); + + Display *dpy; + bool trueColor; + +}; + +// +// 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 const char *className() const { + return "ShmImage"; + } + virtual const char *classDesc() const { + return "shared memory image"; + } + + 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 const char *className() const { + return "IrixOverlayShmImage"; + } + virtual const char *classDesc() const { + return "IRIX-specific SHM-aware overlay image"; + } + + 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 const char *className() const { + return "SolarisOverlayImage"; + } + virtual const char *classDesc() const { + return "Solaris-specific non-SHM overlay image"; + } + + 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/unix/x0vncserver/Makefile.in b/unix/x0vncserver/Makefile.in new file mode 100644 index 00000000..cbb9fed6 --- /dev/null +++ b/unix/x0vncserver/Makefile.in @@ -0,0 +1,30 @@ + +SRCS = Image.cxx TimeMillis.cxx PollingScheduler.cxx PollingManager.cxx \ + Geometry.cxx \ + x0vncserver.cxx ../vncconfig_unix/QueryConnectDialog.cxx + +OBJS = $(SRCS:.cxx=.o) + +program = x0vncserver + +DEP_LIBS = ../rfb/librfb.a \ + ../network/libnetwork.a \ + ../rdr/librdr.a \ + ../tx/libtx.a + +EXTRA_LIBS = @ZLIB_LIB@ @JPEG_LIB@ @INET_LIB@ @X_PRE_LIBS@ @X_LIBS@ \ + @XTEST_LIB@ -lXext -lX11 @X_EXTRA_LIBS@ + +# X_CFLAGS are really CPPFLAGS +DIR_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/tx -I$(top_srcdir)/vncconfig_unix \ + @XTEST_DEFINE@ @READDISPLAY_DEFINE@ @MITSHM_DEFINE@ @X_CFLAGS@ + +all:: $(program) + +$(program): $(OBJS) buildtime.o $(DEP_LIBS) + rm -f $(program) + $(CXXLD) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS) buildtime.o $(DEP_LIBS) $(LIBS) $(EXTRA_LIBS) + +buildtime.o: $(OBJS) $(DEP_LIBS) + +# followed by boilerplate.mk diff --git a/unix/x0vncserver/PollingManager.cxx b/unix/x0vncserver/PollingManager.cxx new file mode 100644 index 00000000..a823ed42 --- /dev/null +++ b/unix/x0vncserver/PollingManager.cxx @@ -0,0 +1,600 @@ +/* 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 + * 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. + */ +// +// PollingManager.cxx +// + +// FIXME: Don't compare pixels already marked as changed. +// FIXME: Use Image::copyPixels() instead of Image::updateRect()? +// In that case, note the fact that arguments are not checked. + +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <X11/Xlib.h> +#include <rfb/LogWriter.h> +#include <rfb/VNCServer.h> +#include <rfb/Configuration.h> +#include <rfb/ServerCore.h> + +#include <x0vncserver/PollingManager.h> + +static LogWriter vlog("PollingMgr"); + +BoolParameter PollingManager::pollPointer +("PollPointer", + "DEBUG: Poll area under the pointer with higher priority", + true); + +IntParameter PollingManager::pollingType +("PollingType", + "DEBUG: Select particular polling algorithm (0..3)", + 3); + +const int PollingManager::m_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 +}; + +// +// Constructor. +// +// Note that dpy and image should remain valid during the object +// lifetime, while factory is used only in the constructor itself. +// + +PollingManager::PollingManager(Display *dpy, Image *image, + ImageFactory *factory, + int offsetLeft, int offsetTop) + : m_dpy(dpy), m_server(0), m_image(image), + m_offsetLeft(offsetLeft), m_offsetTop(offsetTop), + m_pointerPosKnown(false), m_pollingStep(0) +{ + // Save width and height of the screen (and the image). + m_width = m_image->xim->width; + m_height = m_image->xim->height; + + // Compute width and height in 32x32 tiles. + m_widthTiles = (m_width + 31) / 32; + m_heightTiles = (m_height + 31) / 32; + + // Get initial screen image. + m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop); + + // Create additional images used in polling algorithms, warn if + // underlying class names are different from the class name of the + // primary image. + m_rowImage = factory->newImage(m_dpy, m_width, 1); + m_tileImage = factory->newImage(m_dpy, 32, 32); + m_areaImage = factory->newImage(m_dpy, 128, 128); + if (strcmp(m_image->className(), m_rowImage->className()) != 0 || + strcmp(m_image->className(), m_tileImage->className()) != 0 || + strcmp(m_image->className(), m_areaImage->className()) != 0) { + vlog.error("Image types do not match (%s, %s, %s)", + m_rowImage->className(), + m_tileImage->className(), + m_areaImage->className()); + } + + // FIXME: Extend the comment. + // Create a matrix with one byte per each 32x32 tile. It will be + // used to limit the rate of updates on continuously-changed screen + // areas (like video). + int numTiles = m_widthTiles * m_heightTiles; + m_statusMatrix = new char[numTiles]; + memset(m_statusMatrix, 0, numTiles); + + // FIXME: Extend the comment. + // Create a matrix with one byte per each 32x32 tile. It will be + // used to limit the rate of updates on continuously-changed screen + // areas (like video). + m_rateMatrix = new char[numTiles]; + m_videoFlags = new char[numTiles]; + m_changedFlags = new char[numTiles]; + memset(m_rateMatrix, 0, numTiles); + memset(m_videoFlags, 0, numTiles); + memset(m_changedFlags, 0, numTiles); +} + +PollingManager::~PollingManager() +{ + delete[] m_changedFlags; + delete[] m_videoFlags; + delete[] m_rateMatrix; + + delete[] m_statusMatrix; + + delete m_areaImage; + delete m_tileImage; + delete m_rowImage; +} + +// +// Register VNCServer object. +// + +void PollingManager::setVNCServer(VNCServer *s) +{ + m_server = s; +} + +// +// Update current pointer position which may be used as a hint for +// polling algorithms. +// + +void PollingManager::setPointerPos(const Point &pos) +{ + m_pointerPosTime = time(NULL); + m_pointerPos = pos; + m_pointerPosKnown = true; +} + +// +// Indicate that current pointer position is unknown. +// + +void PollingManager::unsetPointerPos() +{ + m_pointerPosKnown = false; +} + +// +// DEBUG: Measuring time spent in the poll() function, +// as well as time intervals between poll() calls. +// + +#ifdef DEBUG +void PollingManager::debugBeforePoll() +{ + TimeMillis timeNow; + int diff = timeNow.diffFrom(m_timeSaved); + fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32); + m_timeSaved = timeNow; +} + +void PollingManager::debugAfterPoll() +{ + TimeMillis timeNow; + int diff = timeNow.diffFrom(m_timeSaved); + fprintf(stderr, "[poll%4dms]\n", diff); + m_timeSaved = timeNow; +} + +#endif + +// +// Search for changed rectangles on the screen. +// + +void PollingManager::poll() +{ +#ifdef DEBUG + debugBeforePoll(); +#endif + + // First step: full-screen polling. + + bool changes1 = false; + + switch((int)pollingType) { + case 0: + changes1 = poll_Dumb(); + break; + case 1: + changes1 = poll_Traditional(); + break; + case 2: + changes1 = poll_SkipCycles(); + break; +//case 3: + default: + changes1 = poll_DetectVideo(); + break; + } + + // Second step: optional thorough polling of the area around the pointer. + // We do that only if the pointer position is known and was set recently. + + bool changes2 = false; + if (pollPointer) { + if (m_pointerPosKnown && time(NULL) - m_pointerPosTime >= 5) { + unsetPointerPos(); + } + if (m_pointerPosKnown) { + changes2 = pollPointerArea(); + } + } + + // Update if needed. + + if (changes1 || changes2) + m_server->tryUpdate(); + +#ifdef DEBUG + debugAfterPoll(); +#endif +} + +bool PollingManager::poll_DetectVideo() +{ + if (!m_server) + return false; + + const int GRAND_STEP_DIVISOR = 8; + const int VIDEO_THRESHOLD_0 = 3; + const int VIDEO_THRESHOLD_1 = 5; + + bool grandStep = (m_pollingStep % GRAND_STEP_DIVISOR == 0); + + // FIXME: Save shortcuts in member variables? + int scanLine = m_pollingOrder[m_pollingStep++ % 32]; + int bytesPerPixel = m_image->xim->bits_per_pixel / 8; + int bytesPerLine = m_image->xim->bytes_per_line; + + Rect rect; + int nTilesChanged = 0; + int idx = 0; + + for (int y = 0; y * 32 < m_height; y++) { + int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32; + if (scanLine >= tile_h) + break; + int scan_y = y * 32 + scanLine; + getRow(scan_y); + char *ptr_old = m_image->xim->data + scan_y * bytesPerLine; + char *ptr_new = m_rowImage->xim->data; + for (int x = 0; x * 32 < m_width; x++) { + int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32; + int nBytes = tile_w * bytesPerPixel; + + char wasChanged = (memcmp(ptr_old, ptr_new, nBytes) != 0); + m_rateMatrix[idx] += wasChanged; + + if (grandStep) { + if (m_rateMatrix[idx] <= VIDEO_THRESHOLD_0) { + m_videoFlags[idx] = 0; + } else if (m_rateMatrix[idx] >= VIDEO_THRESHOLD_1) { + m_videoFlags[idx] = 1; + } + m_rateMatrix[idx] = 0; + } + + m_changedFlags[idx] |= wasChanged; + if ( m_changedFlags[idx] && (!m_videoFlags[idx] || grandStep) ) { + getTile32(x, y, tile_w, tile_h); + m_image->updateRect(m_tileImage, x * 32, y * 32); + rect.setXYWH(x * 32, y * 32, tile_w, tile_h); + m_server->add_changed(rect); + nTilesChanged++; + m_changedFlags[idx] = 0; + } + + ptr_old += nBytes; + ptr_new += nBytes; + idx++; + } + } + + if (grandStep) + adjustVideoArea(); + + return (nTilesChanged != 0); +} + +bool PollingManager::poll_SkipCycles() +{ + if (!m_server) + return false; + + enum { + NOT_CHANGED, CHANGED_ONCE, CHANGED_AGAIN + }; + + bool grandStep = (m_pollingStep % 8 == 0); + + int nTilesChanged = 0; + int scanLine = m_pollingOrder[m_pollingStep++ % 32]; + int bytesPerPixel = m_image->xim->bits_per_pixel / 8; + int bytesPerLine = m_image->xim->bytes_per_line; + char *pstatus = m_statusMatrix; + bool wasChanged; + Rect rect; + + for (int y = 0; y * 32 < m_height; y++) { + int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32; + if (scanLine >= tile_h) + scanLine %= tile_h; + int scan_y = y * 32 + scanLine; + getRow(scan_y); + char *ptr_old = m_image->xim->data + scan_y * bytesPerLine; + char *ptr_new = m_rowImage->xim->data; + for (int x = 0; x * 32 < m_width; x++) { + int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32; + int nBytes = tile_w * bytesPerPixel; + + if (grandStep || *pstatus != CHANGED_AGAIN) { + wasChanged = (*pstatus == CHANGED_AGAIN) ? + true : (memcmp(ptr_old, ptr_new, nBytes) != 0); + if (wasChanged) { + if (grandStep || *pstatus == NOT_CHANGED) { + getTile32(x, y, tile_w, tile_h); + m_image->updateRect(m_tileImage, x * 32, y * 32); + rect.setXYWH(x * 32, y * 32, tile_w, tile_h); + m_server->add_changed(rect); + nTilesChanged++; + *pstatus = CHANGED_ONCE; + } else { + *pstatus = CHANGED_AGAIN; + } + } else if (grandStep) { + *pstatus = NOT_CHANGED; + } + } + + ptr_old += nBytes; + ptr_new += nBytes; + pstatus++; + } + } + + return (nTilesChanged != 0); +} + +bool PollingManager::poll_Traditional() +{ + if (!m_server) + return false; + + int nTilesChanged = 0; + int scanLine = m_pollingOrder[m_pollingStep++ % 32]; + int bytesPerPixel = m_image->xim->bits_per_pixel / 8; + int bytesPerLine = m_image->xim->bytes_per_line; + Rect rect; + + for (int y = 0; y * 32 < m_height; y++) { + int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32; + if (scanLine >= tile_h) + break; + int scan_y = y * 32 + scanLine; + getRow(scan_y); + char *ptr_old = m_image->xim->data + scan_y * bytesPerLine; + char *ptr_new = m_rowImage->xim->data; + for (int x = 0; x * 32 < m_width; x++) { + int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32; + int nBytes = tile_w * bytesPerPixel; + if (memcmp(ptr_old, ptr_new, nBytes)) { + getTile32(x, y, tile_w, tile_h); + m_image->updateRect(m_tileImage, x * 32, y * 32); + rect.setXYWH(x * 32, y * 32, tile_w, tile_h); + m_server->add_changed(rect); + nTilesChanged++; + } + ptr_old += nBytes; + ptr_new += nBytes; + } + } + + return (nTilesChanged != 0); +} + +// +// Simplest polling method, from the original x0vncserver of VNC4. +// + +bool PollingManager::poll_Dumb() +{ + if (!m_server) + return false; + + getScreen(); + Rect rect(0, 0, m_width, m_height); + m_server->add_changed(rect); + + // Report that some changes have been detected. + return true; +} + +// +// Compute coordinates of the rectangle around the pointer. +// +// ASSUMES: (m_pointerPosKnown != false) +// + +void PollingManager::computePointerArea(Rect *r) +{ + int x = m_pointerPos.x - 64; + int y = m_pointerPos.y - 64; + int w = 128; + int h = 128; + if (x < 0) { + w += x; x = 0; + } + if (x + w > m_width) { + w = m_width - x; + } + if (y < 0) { + h += y; y = 0; + } + if (y + h > m_height) { + h = m_height - y; + } + + r->setXYWH(x, y, w, h); +} + +// +// Poll the area under current pointer position. Each pixel of the +// area should be compared. Using such polling option gives higher +// priority to screen area under the pointer. +// +// ASSUMES: (m_server != NULL && m_pointerPosKnown != false) +// + +bool PollingManager::pollPointerArea() +{ + Rect r; + computePointerArea(&r); + + // Shortcuts for coordinates. + int x = r.tl.x, y = r.tl.y; + int w = r.width(), h = r.height(); + + // Get new pixels. + getArea128(x, y, w, h); + + // Now, try to minimize the rectangle by cutting out unchanged + // borders (at top and bottom). + // + // FIXME: Perhaps we should work on 32x32 tiles (properly aligned) + // to produce a region instead of a rectangle. If there would + // be just one universal polling algorithm, it could be + // better to integrate pointer area polling into that + // algorithm, instead of a separate pollPointerArea() + // function. + + // Shortcuts. + int bytesPerPixel = m_image->xim->bits_per_pixel / 8; + int oldBytesPerLine = m_image->xim->bytes_per_line; + int newBytesPerLine = m_areaImage->xim->bytes_per_line; + char *oldPtr = m_image->xim->data + y * oldBytesPerLine + x * bytesPerPixel; + char *newPtr = m_areaImage->xim->data; + + // Check and cut out unchanged rows at the top. + int ty; + for (ty = 0; ty < h; ty++) { + if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0) + break; + oldPtr += oldBytesPerLine; + newPtr += newBytesPerLine; + } + if (ty == h) { + return false; // no changes at all + } + y += ty; h -= ty; + + // Check and cut out unchanged rows at the bottom. + oldPtr = m_image->xim->data + (y+h-1) * oldBytesPerLine + x * bytesPerPixel; + newPtr = m_areaImage->xim->data + (ty+h-1) * newBytesPerLine; + int by; + for (by = 0; by < h - 1; by++) { + if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0) + break; + oldPtr -= oldBytesPerLine; + newPtr -= newBytesPerLine; + } + h -= by; + + // Copy pixels. + m_image->updateRect(m_areaImage, x, y, 0, ty, w, h); + + // Report updates to the server. + Rect rect(x, y, x+w, y+h); + m_server->add_changed(rect); + return true; +} + +// +// Make video area pattern more regular. +// +// FIXME: Replace the above with a normal comment. +// FIXME: Is the function efficient enough? +// + +void PollingManager::adjustVideoArea() +{ + char newFlags[m_widthTiles * m_heightTiles]; + char *ptr = newFlags; + int x, y; + + // DEBUG: + // int nVideoTiles = 0; + + for (y = 0; y < m_heightTiles; y++) { + for (x = 0; x < m_widthTiles; x++) { + + // DEBUG: + // nVideoTiles += m_videoFlags[y * m_widthTiles + x]; + + int weightedSum = 0, n; + if (y > 0 && x > 0) { + n = (m_videoFlags[ y * m_widthTiles + (x-1)] + + m_videoFlags[(y-1) * m_widthTiles + (x-1)] + + m_videoFlags[(y-1) * m_widthTiles + x ]); + if (n == 3) { + *ptr++ = 1; + continue; + } + weightedSum += n; + } + if (y > 0 && x < m_widthTiles - 1) { + n = (m_videoFlags[ y * m_widthTiles + (x+1)] + + m_videoFlags[(y-1) * m_widthTiles + (x+1)] + + m_videoFlags[(y-1) * m_widthTiles + x ]); + if (n == 3) { + *ptr++ = 1; + continue; + } + weightedSum += n; + } + if (y < m_heightTiles - 1 && x > 0) { + n = (m_videoFlags[ y * m_widthTiles + (x-1)] + + m_videoFlags[(y+1) * m_widthTiles + (x-1)] + + m_videoFlags[(y+1) * m_widthTiles + x ]); + if (n == 3) { + *ptr++ = 1; + continue; + } + weightedSum += n; + } + if (y < m_heightTiles - 1 && x < m_widthTiles - 1) { + n = (m_videoFlags[ y * m_widthTiles + (x+1)] + + m_videoFlags[(y+1) * m_widthTiles + (x+1)] + + m_videoFlags[(y+1) * m_widthTiles + x ]); + if (n == 3) { + *ptr++ = 1; + continue; + } + weightedSum += n; + } + *ptr++ = (weightedSum <= 3) ? 0 : m_videoFlags[y * m_widthTiles + x]; + } + } + + /* + /// DEBUG: ------------------------------------------------------ + if (nVideoTiles) { + for (y = 0; y < m_heightTiles; y++) { + for (x = 0; x < m_widthTiles; x++) { + printf("%c", m_videoFlags[y * m_widthTiles + x] ? '@' : ':'); + } + printf(" "); + for (x = 0; x < m_widthTiles; x++) { + printf("%c", newFlags[y * m_widthTiles + x] ? '@' : ':'); + } + printf("\n"); + } + printf("\n"); + } + /// ------------------------------------------------------------- + */ + + memcpy(m_videoFlags, newFlags, m_widthTiles * m_heightTiles); +} diff --git a/unix/x0vncserver/PollingManager.h b/unix/x0vncserver/PollingManager.h new file mode 100644 index 00000000..b8eef509 --- /dev/null +++ b/unix/x0vncserver/PollingManager.h @@ -0,0 +1,148 @@ +/* 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 + * 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. + */ + +// +// PollingManager.h +// + +#ifndef __POLLINGMANAGER_H__ +#define __POLLINGMANAGER_H__ + +#include <X11/Xlib.h> +#include <rfb/VNCServer.h> + +#include <x0vncserver/Image.h> + +#ifdef DEBUG +#include <x0vncserver/TimeMillis.h> +#endif + +using namespace rfb; + +class PollingManager { + +public: + + PollingManager(Display *dpy, Image *image, ImageFactory *factory, + int offsetLeft = 0, int offsetTop = 0); + virtual ~PollingManager(); + + void setVNCServer(VNCServer *s); + + void setPointerPos(const Point &pos); + void unsetPointerPos(); + + void poll(); + + // Configurable parameters. + static BoolParameter pollPointer; + static IntParameter pollingType; + +protected: + + // + // Implementations of different polling algorithms. + // Return value of true reports that some changes were detected. + // + bool poll_DetectVideo(); + bool poll_SkipCycles(); + bool poll_Traditional(); + bool poll_Dumb(); + + // Separate polling for the area around current pointer position. + void computePointerArea(Rect *r); + bool pollPointerArea(); + + Display *m_dpy; + VNCServer *m_server; + + Image *m_image; + int m_offsetLeft; + int m_offsetTop; + int m_width; + int m_height; + int m_widthTiles; + int m_heightTiles; + + // Tracking pointer position for polling improvements. + bool m_pointerPosKnown; + Point m_pointerPos; + time_t m_pointerPosTime; + +private: + + inline void getScreen() { + m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop); + } + + inline void getRow(int y) { + m_rowImage->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop + y); + } + + inline void getTile32(int tx, int ty, int w, int h) { + if (w == 32 && h == 32) { + // This version of get() may be better optimized. + m_tileImage->get(DefaultRootWindow(m_dpy), + m_offsetLeft + tx * 32, m_offsetTop + ty * 32); + } else { + // Generic version of get() for arbitrary width and height. + m_tileImage->get(DefaultRootWindow(m_dpy), + m_offsetLeft + tx * 32, m_offsetTop + ty * 32, w, h); + } + } + + inline void getArea128(int x, int y, int w, int h) { + if (w == 128 && h == 128) { + // This version of get() may be better optimized. + m_areaImage->get(DefaultRootWindow(m_dpy), + m_offsetLeft + x, m_offsetTop + y); + } else { + // Generic version of get() for arbitrary width and height. + m_areaImage->get(DefaultRootWindow(m_dpy), + m_offsetLeft + x, m_offsetTop + y, w, h); + } + } + + void adjustVideoArea(); + + // Additional images used in polling algorithms. + Image *m_rowImage; // One row of the framebuffer + Image *m_tileImage; // One tile (32x32 or less) + Image *m_areaImage; // Area around the pointer (up to 128x128) + + char *m_statusMatrix; + + char *m_rateMatrix; + char *m_videoFlags; + char *m_changedFlags; + + unsigned int m_pollingStep; + static const int m_pollingOrder[]; + +#ifdef DEBUG +private: + + void debugBeforePoll(); + void debugAfterPoll(); + + TimeMillis m_timeSaved; +#endif + +}; + +#endif // __POLLINGMANAGER_H__ diff --git a/unix/x0vncserver/PollingScheduler.cxx b/unix/x0vncserver/PollingScheduler.cxx new file mode 100644 index 00000000..c9d8d602 --- /dev/null +++ b/unix/x0vncserver/PollingScheduler.cxx @@ -0,0 +1,211 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// PollingScheduler class implementation. +// + +#include <string.h> +#include <stdlib.h> + +#ifdef DEBUG +#include <stdio.h> +#endif + +#include <x0vncserver/PollingScheduler.h> + +PollingScheduler::PollingScheduler(int interval, int maxload) +{ + setParameters(interval, maxload); + reset(); +} + +void PollingScheduler::setParameters(int interval, int maxload) +{ + m_interval = interval; + m_maxload = maxload; + + if (m_interval < 0) { + m_interval = 0; + } + if (m_maxload < 1) { + m_maxload = 1; + } else if (m_maxload > 100) { + m_maxload = 100; + } +} + +void PollingScheduler::reset() +{ + m_initialState = true; +} + +void PollingScheduler::newPass() +{ + TimeMillis timeNow; + + if (m_initialState) { + + // First polling pass: initialize statistics. + m_initialState = false; + m_ratedDuration = 0; + m_sleeping = 0; + memset(m_errors, 0, sizeof(m_errors)); + m_errorSum = 0; + m_errorAbsSum = 0; + memset(m_durations, 0, sizeof(m_durations)); + m_durationSum = 0; + memset(m_slept, 0, sizeof(m_slept)); + m_sleptSum = 0; + m_idx = 0; + m_count = 0; + + } else { + + // Stop sleeping if not yet. + if (m_sleeping) + sleepFinished(); + + // Update statistics on sleeping time and total pass duration. + int duration = timeNow.diffFrom(m_passStarted); + + int oldest = m_durations[m_idx]; + m_durations[m_idx] = duration; + m_durationSum = m_durationSum - oldest + duration; + + oldest = m_slept[m_idx]; + m_slept[m_idx] = m_sleptThisPass; + m_sleptSum = m_sleptSum - oldest + m_sleptThisPass; + + // Compute and save the difference between actual and planned time. + int newError = duration - m_interval; + oldest = m_errors[m_idx]; + m_errors[m_idx] = newError; + m_errorSum = m_errorSum - oldest + newError; + m_errorAbsSum = m_errorAbsSum - abs(oldest) + abs(newError); + + // + // Below is the most important part. + // Compute desired duration of the upcoming polling pass. + // + + // Estimation based on keeping up constant interval. + m_ratedDuration = m_interval - m_errorSum / 2; + + // Estimations based on keeping up desired CPU load. + int optimalLoadDuration1 = 0; + int optimalLoadDuration8 = 0; + int optimalLoadDuration = 0; + + if (m_count > 4) { + // Estimation 1 (use previous pass statistics). + optimalLoadDuration1 = + ((duration - m_sleptThisPass) * 100 + m_maxload/2) / m_maxload; + + if (m_count > 16) { + // Estimation 2 (use history of 8 previous passes). + optimalLoadDuration8 = + ((m_durationSum - m_sleptSum) * 900 + m_maxload*4) / (m_maxload*8) + - m_durationSum; + // Mix the above two giving more priority to the first. + optimalLoadDuration = + (2 * optimalLoadDuration1 + optimalLoadDuration8) / 3; + } else { + optimalLoadDuration = optimalLoadDuration1; + } + } + +#ifdef DEBUG + fprintf(stderr, "<est %3d,%3d,%d>\t", + m_ratedDuration, optimalLoadDuration1, optimalLoadDuration8); +#endif + + // Choose final estimation. + if (m_ratedDuration < optimalLoadDuration) { + m_ratedDuration = optimalLoadDuration; + } + if (m_ratedDuration < 0) { + m_ratedDuration = 0; + } else if (m_ratedDuration > 500 && m_interval <= 100) { + m_ratedDuration = 500; + } else if (m_ratedDuration > 1000) { + m_ratedDuration = 1000; + } + +#ifdef DEBUG + fprintf(stderr, "<final est %3d>\t", m_ratedDuration); +#endif + + // Update ring buffer indexer (8 elements per each arrays). + m_idx = (m_idx + 1) & 7; + + // Update pass counter. + m_count++; + + } + + m_passStarted = timeNow; + m_sleptThisPass = 0; +} + +void PollingScheduler::sleepStarted() +{ + if (m_initialState || m_sleeping) + return; + + m_sleepStarted.update(); + + m_sleeping = true; +} + +void PollingScheduler::sleepFinished() +{ + if (m_initialState || !m_sleeping) + return; + + TimeMillis timeNow; + m_sleptThisPass += timeNow.diffFrom(m_sleepStarted); + + m_sleeping = false; +} + +int PollingScheduler::millisRemaining() const +{ + if (m_initialState) + return 0; + + TimeMillis timeNow; + int elapsed = timeNow.diffFrom(m_passStarted); + + if (elapsed > m_ratedDuration) + return 0; + + return (m_ratedDuration - elapsed); +} + +bool PollingScheduler::goodTimeToPoll() const +{ + if (m_initialState) + return true; + + // Average error (per 8 elements in the ring buffer). + int errorAvg = (m_errorAbsSum + 4) / 8; + + // It's ok to poll earlier if new error is no more than half-average. + return (millisRemaining() <= errorAvg / 2); +} diff --git a/unix/x0vncserver/PollingScheduler.h b/unix/x0vncserver/PollingScheduler.h new file mode 100644 index 00000000..2e3e5be7 --- /dev/null +++ b/unix/x0vncserver/PollingScheduler.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// PollingScheduler class. It is used for deciding when to start new +// polling pass, and how much time it is ok to sleep before starting. +// PollingScheduler is given a desired polling interval, but it can +// add time between polling passes if needed for satisfying processor +// usage limitation. +// + +#ifndef __POLLINGSCHEDULER_H__ +#define __POLLINGSCHEDULER_H__ + +#include <x0vncserver/TimeMillis.h> + +class PollingScheduler { + +public: + + PollingScheduler(int interval, int maxload = 50); + + // Set polling parameters. + void setParameters(int interval, int maxload = 50); + + // Reset the object into the initial state (no polling performed). + void reset(); + + // Tell the scheduler that new polling pass is just being started. + void newPass(); + + // Inform the scheduler about times when we sleep. + void sleepStarted(); + void sleepFinished(); + + // This function estimates time remaining before new polling pass. + int millisRemaining() const; + + // This function tells if it's ok to start polling pass right now. + bool goodTimeToPoll() const; + +protected: + + // Parameters. + int m_interval; + int m_maxload; + + // This boolean flag is true when we do not poll the screen. + bool m_initialState; + + // Time stamp saved on starting current polling pass. + TimeMillis m_passStarted; + + // Desired duration of current polling pass. + int m_ratedDuration; + + // These are for measuring sleep time in current pass. + TimeMillis m_sleepStarted; + bool m_sleeping; + int m_sleptThisPass; + + // Ring buffer for tracking past timing errors. + int m_errors[8]; + int m_errorSum; + int m_errorAbsSum; + + // Ring buffer for tracking total pass durations (work + sleep). + int m_durations[8]; + int m_durationSum; + + // Ring buffer for tracking past sleep times. + int m_slept[8]; + int m_sleptSum; + + // Indexer for all ring buffers. + int m_idx; + + // Pass counter. + int m_count; +}; + +#endif // __POLLINGSCHEDULER_H__ + diff --git a/unix/x0vncserver/TimeMillis.cxx b/unix/x0vncserver/TimeMillis.cxx new file mode 100644 index 00000000..059c043b --- /dev/null +++ b/unix/x0vncserver/TimeMillis.cxx @@ -0,0 +1,49 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// TimeMillis.cxx +// + +#include <x0vncserver/TimeMillis.h> + +// XXX Lynx/OS 2.3: get proto for gettimeofday() +#ifdef Lynx +#include <sys/proto.h> +#endif + +TimeMillis::TimeMillis() +{ + update(); +} + +bool TimeMillis::update() +{ + struct timezone tz; + return (gettimeofday(&m_timeval, &tz) == 0); +} + +int TimeMillis::diffFrom(const TimeMillis &older) const +{ + int diff = (int) + ((m_timeval.tv_usec - older.m_timeval.tv_usec + 500) / 1000 + + (m_timeval.tv_sec - older.m_timeval.tv_sec) * 1000); + + return diff; +} + diff --git a/unix/x0vncserver/TimeMillis.h b/unix/x0vncserver/TimeMillis.h new file mode 100644 index 00000000..e79db12c --- /dev/null +++ b/unix/x0vncserver/TimeMillis.h @@ -0,0 +1,47 @@ +/* Copyright (C) 2006 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 + * 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. + */ + +// +// TimeMillis.h +// + +#ifndef __TIMEMILLIS_H__ +#define __TIMEMILLIS_H__ + +#include <sys/time.h> + +class TimeMillis { + +public: + + TimeMillis(); + + // Set this object to current time, returns true on sucess. + bool update(); + + // Return difference in milliseconds between two time points. + int diffFrom(const TimeMillis &older) const; + +protected: + + struct timeval m_timeval; + +}; + +#endif // __TIMEMILLIS_H__ + diff --git a/unix/x0vncserver/buildtime.c b/unix/x0vncserver/buildtime.c new file mode 100644 index 00000000..a96031cc --- /dev/null +++ b/unix/x0vncserver/buildtime.c @@ -0,0 +1,18 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. 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 + * 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. + */ +char buildtime[] = __DATE__ " " __TIME__; diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx new file mode 100644 index 00000000..80483bf8 --- /dev/null +++ b/unix/x0vncserver/x0vncserver.cxx @@ -0,0 +1,580 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2004-2006 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 + * 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. + */ + +// FIXME: Check cases when screen width/height is not a multiply of 32. +// e.g. 800x600. + +#include <strings.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <rfb/Logger_stdio.h> +#include <rfb/LogWriter.h> +#include <rfb/VNCServerST.h> +#include <rfb/Configuration.h> +#include <rfb/SSecurityFactoryStandard.h> +#include <rfb/Timer.h> +#include <network/TcpSocket.h> +#include <tx/TXWindow.h> + +#include <vncconfig_unix/QueryConnectDialog.h> + +#include <signal.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#ifdef HAVE_XTEST +#include <X11/extensions/XTest.h> +#endif + +#include <x0vncserver/Geometry.h> +#include <x0vncserver/Image.h> +#include <x0vncserver/PollingManager.h> +#include <x0vncserver/PollingScheduler.h> + +// XXX Lynx/OS 2.3: protos for select(), bzero() +#ifdef Lynx +#include <sys/proto.h> +#endif + +using namespace rfb; +using namespace network; + +static LogWriter vlog("Main"); + +IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling " + "cycle; actual interval may be dynamically " + "adjusted to satisfy MaxProcessorUsage setting", 30); +IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of " + "CPU time to be consumed", 35); +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); +IntParameter queryConnectTimeout("QueryConnectTimeout", + "Number of seconds to show the Accept Connection dialog before " + "rejecting the connection", + 10); +StringParameter hostsFile("HostsFile", "File with IP access control rules", ""); + +// +// Allow the main loop terminate itself gracefully on receiving a signal. +// + +static bool caughtSignal = false; + +static void CleanupSignalHandler(int sig) +{ + caughtSignal = true; +} + + +class QueryConnHandler : public VNCServerST::QueryConnectionHandler, + public QueryResultCallback { +public: + QueryConnHandler(Display* dpy, VNCServerST* vs) + : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {} + ~QueryConnHandler() { delete queryConnectDialog; } + + // -=- VNCServerST::QueryConnectionHandler interface + virtual VNCServerST::queryResult queryConnection(network::Socket* sock, + const char* userName, + char** reason) { + if (queryConnectSock) { + *reason = strDup("Another connection is currently being queried."); + return VNCServerST::REJECT; + } + if (!userName) userName = "(anonymous)"; + queryConnectSock = sock; + CharArray address(sock->getPeerAddress()); + delete queryConnectDialog; + queryConnectDialog = new QueryConnectDialog(display, address.buf, + userName, queryConnectTimeout, + this); + queryConnectDialog->map(); + return VNCServerST::PENDING; + } + + // -=- QueryResultCallback interface + virtual void queryApproved() { + server->approveConnection(queryConnectSock, true, 0); + queryConnectSock = 0; + } + virtual void queryRejected() { + server->approveConnection(queryConnectSock, false, + "Connection rejected by local user"); + queryConnectSock = 0; + } +private: + Display* display; + VNCServerST* server; + QueryConnectDialog* queryConnectDialog; + network::Socket* queryConnectSock; +}; + + +// +// XPixelBuffer is a modification of FullFramePixelBuffer that does +// not always return buffer width in getStride(). +// + +class XPixelBuffer : public FullFramePixelBuffer +{ +public: + XPixelBuffer(const PixelFormat& pf, int width, int height, + rdr::U8* data_, ColourMap* cm, int stride_) : + FullFramePixelBuffer(pf, width, height, data_, cm), stride(stride_) + { + } + + virtual int getStride() const { return stride; } + +protected: + int stride; +}; + + +class XDesktop : public SDesktop, public ColourMap +{ +public: + XDesktop(Display* dpy_, Geometry *geometry_) + : dpy(dpy_), geometry(geometry_), pb(0), server(0), image(0), pollmgr(0), + oldButtonMask(0), haveXtest(false), maxButtons(0), running(false) + { +#ifdef HAVE_XTEST + int xtestEventBase; + int xtestErrorBase; + int major, minor; + + if (XTestQueryExtension(dpy, &xtestEventBase, + &xtestErrorBase, &major, &minor)) { + XTestGrabControl(dpy, True); + 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 + + } + virtual ~XDesktop() { + stop(); + } + + // -=- SDesktop interface + + virtual void start(VNCServer* vs) { + + // 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" : ""); + + // Create an image for maintaining framebuffer data. + ImageFactory factory((bool)useShm, (bool)useOverlay); + image = factory.newImage(dpy, geometry->width(), geometry->height()); + vlog.info("Allocated %s", image->classDesc()); + + // Create polling manager object. It will track screen changes and + // keep pixels of the `image' object up to date. + pollmgr = new PollingManager(dpy, image, &factory, + geometry->offsetLeft(), + geometry->offsetTop()); + pollmgr->setVNCServer(vs); + + pf.bpp = image->xim->bits_per_pixel; + pf.depth = image->xim->depth; + pf.bigEndian = (image->xim->byte_order == MSBFirst); + 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; + + // Calculate the number of pixels in a row, with padding included. + int stride = image->xim->bytes_per_line * 8 / image->xim->bits_per_pixel; + + // Provide pixel buffer to the server object. + pb = new XPixelBuffer(pf, geometry->width(), geometry->height(), + (rdr::U8*)image->xim->data, this, stride); + server = vs; + server->setPixelBuffer(pb); + + running = true; + } + + virtual void stop() { + running = false; + + delete pb; + delete pollmgr; + delete image; + + pb = 0; + pollmgr = 0; + image = 0; + } + + inline bool isRunning() { + return running; + } + + inline void poll() { + if (pollmgr) + pollmgr->poll(); + } + + virtual void pointerEvent(const Point& pos, int buttonMask) { + pollmgr->setPointerPos(pos); +#ifdef HAVE_XTEST + if (!haveXtest) return; + XTestFakeMotionEvent(dpy, DefaultScreen(dpy), + geometry->offsetLeft() + pos.x, + geometry->offsetTop() + pos.y, + CurrentTime); + if (buttonMask != oldButtonMask) { + for (int i = 0; i < maxButtons; i++) { + if ((buttonMask ^ oldButtonMask) & (1<<i)) { + if (buttonMask & (1<<i)) { + XTestFakeButtonEvent(dpy, i+1, True, CurrentTime); + } else { + XTestFakeButtonEvent(dpy, i+1, False, CurrentTime); + } + } + } + } + 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) { + } + + virtual Point getFbSize() { + return Point(pb->width(), pb->height()); + } + + // -=- ColourMap callbacks + virtual void lookup(int index, int* r, int* g, int* b) { + XColor xc; + xc.pixel = index; + if (index < DisplayCells(dpy,DefaultScreen(dpy))) { + XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc); + } else { + xc.red = xc.green = xc.blue = 0; + } + *r = xc.red; + *g = xc.green; + *b = xc.blue; + } + +protected: + Display* dpy; + Geometry* geometry; + PixelFormat pf; + PixelBuffer* pb; + VNCServer* server; + Image* image; + PollingManager* pollmgr; + int oldButtonMask; + bool haveXtest; + int maxButtons; + bool running; +}; + + +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() +{ + fprintf(stderr, "\nusage: %s [<parameters>]\n", programName); + fprintf(stderr,"\n" + "Parameters can be turned on with -<param> or off with -<param>=0\n" + "Parameters which take a value can be specified as " + "-<param> <value>\n" + "Other valid forms are <param>=<value> -<param>=<value> " + "--<param>=<value>\n" + "Parameter names are case-insensitive. The parameters are:\n\n"); + Configuration::listParams(79, 14); + exit(1); +} + +int main(int argc, char** argv) +{ + initStdIOLoggers(); + LogWriter::setLogParams("*:stderr:30"); + + programName = argv[0]; + Display* dpy; + + for (int i = 1; i < argc; i++) { + if (Configuration::setParam(argv[i])) + continue; + + if (argv[i][0] == '-') { + if (i+1 < argc) { + if (Configuration::setParam(&argv[i][1], argv[i+1])) { + i++; + continue; + } + } + usage(); + } + + usage(); + } + + CharArray dpyStr(displayname.getData()); + if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) { + fprintf(stderr,"%s: unable to open display \"%s\"\r\n", + programName, XDisplayName(displayname.getData())); + exit(1); + } + + signal(SIGHUP, CleanupSignalHandler); + signal(SIGINT, CleanupSignalHandler); + signal(SIGTERM, CleanupSignalHandler); + + try { + TXWindow::init(dpy,"x0vncserver"); + Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)), + DisplayHeight(dpy, DefaultScreen(dpy))); + XDesktop desktop(dpy, &geo); + VNCServerST server("x0vncserver", &desktop); + QueryConnHandler qcHandler(dpy, &server); + server.setQueryConnectionHandler(&qcHandler); + + TcpListener listener((int)rfbport); + vlog.info("Listening on port %d", (int)rfbport); + + FileTcpFilter fileTcpFilter(hostsFile.getData()); + if (strlen(hostsFile.getData()) != 0) + listener.setFilter(&fileTcpFilter); + + PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage); + + while (!caughtSignal) { + struct timeval tv; + fd_set rfds; + std::list<Socket*> sockets; + std::list<Socket*>::iterator i; + + // Process any incoming X events + TXWindow::handleXEvents(dpy); + + FD_ZERO(&rfds); + FD_SET(listener.getFd(), &rfds); + server.getSockets(&sockets); + int clients_connected = 0; + for (i = sockets.begin(); i != sockets.end(); i++) { + if ((*i)->isShutdown()) { + server.removeSocket(*i); + delete (*i); + } else { + FD_SET((*i)->getFd(), &rfds); + clients_connected++; + } + } + + if (clients_connected) { + int wait_ms = sched.millisRemaining(); + if (wait_ms > 500) { + wait_ms = 500; + } + tv.tv_usec = wait_ms * 1000; +#ifdef DEBUG + // fprintf(stderr, "[%d]\t", wait_ms); +#endif + } else { + sched.reset(); + tv.tv_usec = 100000; + } + tv.tv_sec = 0; + + // Do the wait... + sched.sleepStarted(); + int n = select(FD_SETSIZE, &rfds, 0, 0, &tv); + sched.sleepFinished(); + + if (n < 0) { + if (errno == EINTR) { + vlog.debug("Interrupted select() system call"); + continue; + } else { + throw rdr::SystemException("select", errno); + } + } + + // Accept new VNC connections + if (FD_ISSET(listener.getFd(), &rfds)) { + Socket* sock = listener.accept(); + if (sock) { + server.addSocket(sock); + } else { + vlog.status("Client connection rejected"); + } + } + + Timer::checkTimeouts(); + server.checkTimeouts(); + + // Client list could have been changed. + server.getSockets(&sockets); + + // Nothing more to do if there are no client connections. + if (sockets.empty()) + continue; + + // Process events on existing VNC connections + for (i = sockets.begin(); i != sockets.end(); i++) { + if (FD_ISSET((*i)->getFd(), &rfds)) + server.processSocketEvent(*i); + } + + if (desktop.isRunning() && sched.goodTimeToPoll()) { + sched.newPass(); + desktop.poll(); + } + } + + } catch (rdr::Exception &e) { + vlog.error(e.str()); + return 1; + } + + vlog.info("Terminated"); + return 0; +} diff --git a/unix/x0vncserver/x0vncserver.man b/unix/x0vncserver/x0vncserver.man new file mode 100644 index 00000000..da9ba944 --- /dev/null +++ b/unix/x0vncserver/x0vncserver.man @@ -0,0 +1,33 @@ +.TH x0vncserver 1 "17 Apr 2006" "TightVNC" "Virtual Network Computing" +.SH NAME +x0vncserver \- VNC server which continuously polls an X display +.SH SYNOPSIS +.B x0vncserver +[\fIparameters\fP] +.SH DESCRIPTION +.B x0vncserver +is a VNC server which continuously polls any X display, allowing it to be +controlled via VNC. How usable it will be depends a lot on the machine it's +running on, and what you're expecting. It won't be as fast as Xvnc or a native +X server with VNC support compiled in, but in many cases it is the best option +since it is just an ordinary X application requiring no special installation. + +It has many of the same parameters as Xvnc. Running \fBx0vncserver -h\fP will +give a list of parameters with descriptions. Note that you need to explicitly +specify an appropriate password file using the PasswordFile parameter. + +.SH SEE ALSO +.BR Xvnc (1) +.BR vncpasswd (1), +.BR vncviewer (1), +.BR vncserver (1), +.br +http://www.tightvnc.com + +.SH AUTHOR +Tristan Richardson, RealVNC Ltd. + +VNC was originally developed by the RealVNC team while at Olivetti +Research Ltd / AT&T Laboratories Cambridge. TightVNC additions was +implemented by Constantin Kaplinsky. Many other people participated in +development, testing and support. |