blob: a5bd2b0ded97da6971236475c719ff9fca130a27 [file] [log] [blame] [edit]
/* 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 <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#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)
{
imageCleanup.images.push_back(this);
}
Image::Image(Display *d, int width, int height)
: xim(NULL), dpy(d)
{
imageCleanup.images.push_back(this);
Init(width, height);
}
void Image::Init(int width, int height)
{
Visual* vis = DefaultVisual(dpy, DefaultScreen(dpy));
if (vis->c_class != TrueColor) {
vlog.error("pseudocolour not supported");
exit(1);
}
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,
int dst_x, int dst_y)
{
XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, dst_x, dst_y);
}
//
// 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);
}
//
// 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;
}
if (visual->c_class != TrueColor) {
vlog.error("pseudocolour not supported");
exit(1);
}
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,
int dst_x, int dst_y)
{
// XShmGetImage is faster, but can only retrieve the entire
// window. Use it for large reads.
if (x == dst_x && y == dst_y && (long)w * h > (long)xim->width * xim->height / 4) {
XShmGetImage(dpy, wnd, xim, 0, 0, AllPlanes);
} else {
XGetSubImage(dpy, wnd, x, y, w, h, AllPlanes, ZPixmap, xim, dst_x, dst_y);
}
}
//
// ImageFactory class implementation
//
// FIXME: Make ImageFactory always create images of the same class?
//
ImageFactory::ImageFactory(bool allowShm)
: mayUseShm(allowShm)
{
}
ImageFactory::~ImageFactory()
{
}
Image *ImageFactory::newImage(Display *d, int width, int height)
{
Image *image = NULL;
// Now, try to use shared memory image.
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");
}
// Fall back to Xlib image.
image = new Image(d, width, height);
return image;
}