aboutsummaryrefslogtreecommitdiffstats
path: root/unix/x0vncserver/Image.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'unix/x0vncserver/Image.cxx')
-rw-r--r--unix/x0vncserver/Image.cxx549
1 files changed, 549 insertions, 0 deletions
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;
+}