| /* Copyright (C) 2002-2005 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. |
| */ |
| // |
| // TXImage.cxx |
| // |
| |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <strings.h> |
| #include <sys/types.h> |
| #include <sys/ipc.h> |
| #include <sys/shm.h> |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| #include <list> |
| #include <rfb/TransImageGetter.h> |
| #include <rfb/Exception.h> |
| #include <rfb/LogWriter.h> |
| #include "TXWindow.h" |
| #include "TXImage.h" |
| |
| using namespace rfb; |
| |
| static rfb::LogWriter vlog("TXImage"); |
| |
| TXImage::TXImage(Display* d, int width, int height, Visual* vis_, int depth_) |
| : xim(0), dpy(d), vis(vis_), depth(depth_), tig(0), cube(0) |
| { |
| #ifdef HAVE_MITSHM |
| shminfo = 0; |
| #endif |
| width_ = width; |
| height_ = height; |
| for (int i = 0; i < 256; i++) |
| colourMap[i].r = colourMap[i].g = colourMap[i].b = 0; |
| |
| if (!vis) |
| vis = DefaultVisual(dpy,DefaultScreen(dpy)); |
| if (!depth) |
| depth = DefaultDepth(dpy,DefaultScreen(dpy)); |
| |
| createXImage(); |
| getNativePixelFormat(vis, depth); |
| colourmap = this; |
| format.bpp = 0; // just make it different to any valid format, so that... |
| setPF(nativePF); // ...setPF() always works |
| } |
| |
| TXImage::~TXImage() |
| { |
| if (data != (rdr::U8*)xim->data) delete [] data; |
| destroyXImage(); |
| delete tig; |
| delete cube; |
| } |
| |
| void TXImage::resize(int w, int h) |
| { |
| if (w == width() && h == height()) return; |
| |
| int oldStrideBytes = getStride() * (format.bpp/8); |
| int rowsToCopy = __rfbmin(h, height()); |
| int bytesPerRow = __rfbmin(w, width()) * (format.bpp/8); |
| rdr::U8* oldData = 0; |
| bool allocData = false; |
| |
| if (data != (rdr::U8*)xim->data) { |
| oldData = (rdr::U8*)data; |
| allocData = true; |
| } else { |
| oldData = new rdr::U8[xim->bytes_per_line * height()]; |
| memcpy(oldData, xim->data, xim->bytes_per_line * height()); |
| } |
| |
| destroyXImage(); |
| width_ = w; |
| height_ = h; |
| createXImage(); |
| |
| if (allocData) |
| data = new rdr::U8[width() * height() * (format.bpp/8)]; |
| else |
| data = (rdr::U8*)xim->data; |
| |
| int newStrideBytes = getStride() * (format.bpp/8); |
| for (int i = 0; i < rowsToCopy; i++) |
| memcpy((rdr::U8*)data + newStrideBytes * i, oldData + oldStrideBytes * i, |
| bytesPerRow); |
| delete [] oldData; |
| } |
| |
| void TXImage::setPF(const PixelFormat& newPF) |
| { |
| if (newPF.equal(format)) return; |
| format = newPF; |
| |
| if (data != (rdr::U8*)xim->data) delete [] data; |
| delete tig; |
| tig = 0; |
| |
| if (format.equal(nativePF) && format.trueColour) { |
| data = (rdr::U8*)xim->data; |
| } else { |
| data = new rdr::U8[width() * height() * (format.bpp/8)]; |
| tig = new TransImageGetter(); |
| tig->init(this, nativePF, 0, cube); |
| } |
| } |
| |
| int TXImage::getStride() const |
| { |
| if (data == (rdr::U8*)xim->data) |
| return xim->bytes_per_line / (xim->bits_per_pixel / 8); |
| else |
| return width(); |
| } |
| |
| void TXImage::put(Window win, GC gc, const rfb::Rect& r) |
| { |
| if (r.is_empty()) return; |
| int x = r.tl.x; |
| int y = r.tl.y; |
| int w = r.width(); |
| int h = r.height(); |
| if (data != (rdr::U8*)xim->data) { |
| rdr::U8* ximDataStart = ((rdr::U8*)xim->data + y * xim->bytes_per_line |
| + x * (xim->bits_per_pixel / 8)); |
| tig->getImage(ximDataStart, r, |
| xim->bytes_per_line / (xim->bits_per_pixel / 8)); |
| } |
| #ifdef HAVE_MITSHM |
| if (usingShm()) { |
| XShmPutImage(dpy, win, gc, xim, x, y, x, y, w, h, False); |
| return; |
| } |
| #endif |
| XPutImage(dpy, win, gc, xim, x, y, x, y, w, h); |
| } |
| |
| void TXImage::setColourMapEntries(int firstColour, int nColours, rdr::U16* rgbs) |
| { |
| for (int i = 0; i < nColours; i++) { |
| colourMap[firstColour+i].r = rgbs[i*3]; |
| colourMap[firstColour+i].g = rgbs[i*3+1]; |
| colourMap[firstColour+i].b = rgbs[i*3+2]; |
| } |
| } |
| |
| void TXImage::updateColourMap() |
| { |
| if (tig != 0) |
| tig->setColourMapEntries(0, 0); |
| } |
| |
| void TXImage::lookup(int index, int* r, int* g, int* b) |
| { |
| *r = colourMap[index].r; |
| *g = colourMap[index].g; |
| *b = colourMap[index].b; |
| } |
| |
| #ifdef HAVE_MITSHM |
| static bool caughtError = false; |
| |
| static int XShmAttachErrorHandler(Display *dpy, XErrorEvent *error) |
| { |
| caughtError = true; |
| return 0; |
| } |
| |
| class TXImageCleanup { |
| public: |
| std::list<TXImage*> images; |
| ~TXImageCleanup() { |
| while (!images.empty()) |
| delete images.front(); |
| } |
| }; |
| |
| static TXImageCleanup imageCleanup; |
| #endif |
| |
| void TXImage::createXImage() |
| { |
| #ifdef HAVE_MITSHM |
| int major, minor; |
| Bool pixmaps; |
| |
| if (XShmQueryVersion(dpy, &major, &minor, &pixmaps)) { |
| shminfo = new XShmSegmentInfo; |
| |
| xim = XShmCreateImage(dpy, vis, depth, ZPixmap, |
| 0, shminfo, width(), height()); |
| |
| if (xim) { |
| shminfo->shmid = shmget(IPC_PRIVATE, |
| xim->bytes_per_line * xim->height, |
| IPC_CREAT|0777); |
| |
| if (shminfo->shmid != -1) { |
| shminfo->shmaddr = xim->data = (char*)shmat(shminfo->shmid, 0, 0); |
| |
| if (shminfo->shmaddr != (char *)-1) { |
| |
| shminfo->readOnly = False; |
| |
| XErrorHandler oldHdlr = XSetErrorHandler(XShmAttachErrorHandler); |
| XShmAttach(dpy, shminfo); |
| XSync(dpy, False); |
| XSetErrorHandler(oldHdlr); |
| |
| if (!caughtError) { |
| vlog.debug("Using shared memory XImage"); |
| imageCleanup.images.push_back(this); |
| return; |
| } |
| |
| shmdt(shminfo->shmaddr); |
| } else { |
| vlog.error("shmat failed"); |
| perror("shmat"); |
| } |
| |
| shmctl(shminfo->shmid, IPC_RMID, 0); |
| } else { |
| vlog.error("shmget failed"); |
| perror("shmget"); |
| } |
| |
| XDestroyImage(xim); |
| xim = 0; |
| } else { |
| vlog.error("XShmCreateImage failed"); |
| } |
| |
| delete shminfo; |
| shminfo = 0; |
| } |
| #endif |
| |
| xim = XCreateImage(dpy, vis, depth, ZPixmap, |
| 0, 0, width(), height(), BitmapPad(dpy), 0); |
| |
| xim->data = (char*)malloc(xim->bytes_per_line * xim->height); |
| if (!xim->data) { |
| vlog.error("malloc failed"); |
| exit(1); |
| } |
| } |
| |
| void TXImage::destroyXImage() |
| { |
| #ifdef HAVE_MITSHM |
| if (shminfo) { |
| vlog.debug("Freeing shared memory XImage"); |
| shmdt(shminfo->shmaddr); |
| shmctl(shminfo->shmid, IPC_RMID, 0); |
| delete shminfo; |
| shminfo = 0; |
| imageCleanup.images.remove(this); |
| } |
| #endif |
| // XDestroyImage() will free(xim->data) if appropriate |
| if (xim) XDestroyImage(xim); |
| xim = 0; |
| } |
| |
| |
| static bool supportedBPP(int bpp) { |
| return (bpp == 8 || bpp == 16 || bpp == 32); |
| } |
| |
| static int depth2bpp(Display* dpy, int depth) |
| { |
| int nformats; |
| XPixmapFormatValues* format = XListPixmapFormats(dpy, &nformats); |
| |
| int i; |
| for (i = 0; i < nformats; i++) |
| if (format[i].depth == depth) break; |
| |
| if (i == nformats || !supportedBPP(format[i].bits_per_pixel)) |
| throw rfb::Exception("Error: couldn't find suitable pixmap format"); |
| |
| int bpp = format[i].bits_per_pixel; |
| XFree(format); |
| return bpp; |
| } |
| |
| void TXImage::getNativePixelFormat(Visual* vis, int depth) |
| { |
| int bpp; |
| int trueColour, bigEndian; |
| int redShift, greenShift, blueShift; |
| int redMax, greenMax, blueMax; |
| |
| cube = 0; |
| |
| bpp = depth2bpp(dpy, depth); |
| bigEndian = (ImageByteOrder(dpy) == MSBFirst); |
| trueColour = (vis->c_class == TrueColor); |
| |
| vlog.info("Using default colormap and visual, %sdepth %d.", |
| (vis->c_class == TrueColor) ? "TrueColor, " : |
| ((vis->c_class == PseudoColor) ? "PseudoColor, " : ""), |
| depth); |
| |
| redShift = ffs(vis->red_mask) - 1; |
| greenShift = ffs(vis->green_mask) - 1; |
| blueShift = ffs(vis->blue_mask) - 1; |
| redMax = vis->red_mask >> redShift; |
| greenMax = vis->green_mask >> greenShift; |
| blueMax = vis->blue_mask >> blueShift; |
| |
| nativePF = PixelFormat(bpp, depth, bigEndian, trueColour, |
| redMax, greenMax, blueMax, |
| redShift, greenShift, blueShift); |
| |
| if (!trueColour) { |
| XColor xc[256]; |
| cube = new rfb::ColourCube(6,6,6); |
| int r; |
| for (r = 0; r < cube->nRed; r++) { |
| for (int g = 0; g < cube->nGreen; g++) { |
| for (int b = 0; b < cube->nBlue; b++) { |
| int i = (r * cube->nGreen + g) * cube->nBlue + b; |
| xc[i].red = r * 65535 / (cube->nRed-1); |
| xc[i].green = g * 65535 / (cube->nGreen-1); |
| xc[i].blue = b * 65535 / (cube->nBlue-1); |
| } |
| } |
| } |
| |
| TXWindow::getColours(dpy, xc, cube->size()); |
| |
| for (r = 0; r < cube->nRed; r++) { |
| for (int g = 0; g < cube->nGreen; g++) { |
| for (int b = 0; b < cube->nBlue; b++) { |
| int i = (r * cube->nGreen + g) * cube->nBlue + b; |
| cube->set(r, g, b, xc[i].pixel); |
| } |
| } |
| } |
| } |
| } |