diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 835be15c64..dd576329dc 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -25,6 +25,7 @@ #include "throw_event.h" #include "lightReMutexHolder.h" #include "nativeWindowHandle.h" +#include "virtualFileSystem.h" #include #include @@ -53,6 +54,35 @@ #include #endif +#ifdef HAVE_XCURSOR +static int xcursor_read(XcursorFile *file, unsigned char *buf, int len) { + istream* str = (istream*) file->closure; + str->read((char*) buf, len); + return str->gcount(); +} + +static int xcursor_write(XcursorFile *file, unsigned char *buf, int len) { + // Not implemented, we don't need it. + nassertr_always(false, 0); +} + +static int xcursor_seek(XcursorFile *file, long offset, int whence) { + istream* str = (istream*) file->closure; + switch (whence) { + case SEEK_SET: + str->seekg(offset, istream::beg); + break; + case SEEK_CUR: + str->seekg(offset, istream::cur); + break; + case SEEK_END: + str->seekg(offset, istream::end); + } + + return str->tellg(); +} +#endif + TypeHandle x11GraphicsWindow::_type_handle; #define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7))) @@ -1873,23 +1903,252 @@ get_cursor(const Filename &filename) { return fi->second; } - Filename os = resolved.to_os_specific(); + // Open the file through the virtual file system. + istream *str = VirtualFileSystem::get_global_ptr()->open_read_file(resolved, true); + if (str == NULL) { + x11display_cat.warning() + << "Could not open cursor file " << filename << "\n"; + return None; + } + + // Check the first four bytes to see what kind of file it is. + char magic[4]; + str->read(magic, 4); + if (!str->good()) { + x11display_cat.warning() + << "Could not read from cursor file " << filename << "\n"; + return None; + } + str->seekg(0, istream::beg); + Cursor h = None; + if (memcmp(magic, "Xcur", 4) == 0) { + // X11 cursor. #ifdef HAVE_XCURSOR - Cursor h = XcursorFilenameLoadCursor(_display, os.c_str()); + x11display_cat.debug() + << "Loading X11 cursor " << filename << "\n"; + XcursorFile xcfile; + xcfile.closure = str; + xcfile.read = &xcursor_read; + xcfile.write = &xcursor_write; + xcfile.seek = &xcursor_seek; + + XcursorImages *images = XcursorXcFileLoadImages(&xcfile, XcursorGetDefaultSize(_display)); + if (images != NULL) { + h = XcursorImagesLoadCursor(_display, images); + XcursorImagesDestroy(images); + } +#endif + + } else { + // Windows .ico or .cur file. + x11display_cat.debug() + << "Loading Windows cursor " << filename << "\n"; + h = read_ico(*str); + } + + delete str; if (h == None) { x11display_cat.warning() - << "x11 cursor filename '" << os << "' could not be loaded!!\n"; + << "X11 cursor filename '" << resolved << "' could not be loaded!\n"; } -#else - // Can't support loading cursor from image, so fall back to default - Cursor h = None; -#endif _cursor_filenames[filename] = h; _cursor_filenames[resolved] = h; return h; } - +//////////////////////////////////////////////////////////////////// +// Function: x11GraphicsWindow::load_ico +// Access: Private +// Description: Reads a Windows .ico or .cur file from the +// indicated stream and returns it as an X11 Cursor. +// If the file cannot be loaded, returns None. +//////////////////////////////////////////////////////////////////// +Cursor x11GraphicsWindow:: +read_ico(istream &ico) { + // Local structs, this is just POD, make input easier + typedef struct { + uint16_t reserved, type, count; + } IcoHeader; + + typedef struct { + uint8_t width, height, colorCount, reserved; + uint16_t xhot, yhot; + uint32_t bitmapSize, offset; + } IcoEntry; + + typedef struct { + uint32_t headerSize, width, height; + uint16_t planes, bitsPerPixel; + uint32_t compression, imageSize, xPixelsPerM, yPixelsPerM, colorsUsed, colorsImportant; + } IcoInfoHeader; + + typedef struct { + uint8_t blue, green, red, reserved; + } IcoColor; + + int i, entry = 0; + unsigned int j, k, mask, shift; + size_t colorCount, bitsPerPixel; + IcoHeader header; + IcoInfoHeader infoHeader; + IcoEntry *entries = NULL; + IcoColor color, *palette = NULL; + + size_t xorBmpSize, andBmpSize; + char *curXor, *curAnd; + char *xorBmp = NULL, *andBmp = NULL; + XcursorImage *image = NULL; + Cursor ret = None; + +#ifdef HAVE_XCURSOR + int def_size = XcursorGetDefaultSize(_display); +#endif + + // Get our header, note that ICO = type 1 and CUR = type 2. + ico.read(reinterpret_cast(&header), sizeof(IcoHeader)); + if (!ico.good()) goto cleanup; + cerr << header.type; + if (header.type != 1 && header.type != 2) goto cleanup; + if (header.count < 1) goto cleanup; + // Read the entry table into memory, select the largest entry. + entries = new IcoEntry[header.count]; + ico.read(reinterpret_cast(entries), header.count * sizeof(IcoEntry)); + if (!ico.good()) goto cleanup; + for (i = 1; i < header.count; i++) { +#ifdef HAVE_XCURSOR + if (entries[i].width == def_size && entries[i].height == def_size) { + // Wait, this is the default cursor size. This is perfect. + entry = i; + break; + } +#endif + if (entries[i].width > entries[entry].width || + entries[i].height > entries[entry].height) + entry = i; + } + + // Seek to the image in the ICO. + ico.seekg(entries[entry].offset); + if (!ico.good()) goto cleanup; + ico.read(reinterpret_cast(&infoHeader), sizeof(IcoInfoHeader)); + if (!ico.good()) goto cleanup; + bitsPerPixel = infoHeader.bitsPerPixel; + + // TODO: Support PNG compressed ICOs. + if (infoHeader.compression != 0) goto cleanup; + + // Load the color palette, if one exists. + colorCount = entries[entry].colorCount == 0 ? 256 : entries[entry].colorCount; + palette = new IcoColor[colorCount]; + if (bitsPerPixel <= 8) ico.read(reinterpret_cast(palette), colorCount * sizeof(IcoColor)); + if (!ico.good()) goto cleanup; + + // Read in the pixel data. + xorBmpSize = (infoHeader.width * (infoHeader.height / 2) * bitsPerPixel) / 8; + andBmpSize = (infoHeader.width * (infoHeader.height / 2)) / 8; + curXor = xorBmp = new char[xorBmpSize]; + curAnd = andBmp = new char[andBmpSize]; + ico.read(xorBmp, xorBmpSize); + if (!ico.good()) goto cleanup; + ico.read(andBmp, andBmpSize); + if (!ico.good()) goto cleanup; + +#ifdef HAVE_XCURSOR + // If this is an actual CUR not an ICO set up the hotspot properly. + image = XcursorImageCreate(infoHeader.width, infoHeader.height / 2); + if (header.type == 2) { image->xhot = entries[entry].xhot; image->yhot = entries[entry].yhot; } + + // Support all the formats that GIMP supports, minus PNG compressed ICOs. + // Would need to use libpng to decode the compressed ones. + switch (bitsPerPixel) { + case 1: + case 4: + case 8: + // For colors less that a byte wide, shift and mask the palette indices + // off each element of the xorBmp and append them to the image. + mask = ((1 << bitsPerPixel) - 1); + for (i = image->height - 1; i >= 0; i--) { + for (j = 0; j < image->width; j += 8 / bitsPerPixel) { + for (k = 0; k < 8 / bitsPerPixel; k++) { + shift = 8 - ((k + 1) * bitsPerPixel); + color = palette[(*curXor & (mask << shift)) >> shift]; + image->pixels[(i * image->width) + j + k] = (color.red << 16) + + (color.green << 8) + + (color.blue); + } + + curXor++; + } + + // Set the alpha byte properly according to the andBmp. + for (j = 0; j < image->width; j += 8) { + for (k = 0; k < 8; k++) { + shift = 7 - k; + image->pixels[(i * image->width) + j + k] |= + ((*curAnd & (1 << shift)) >> shift) ? 0x0 : (0xff << 24); + } + + curAnd++; + } + } + + break; + case 24: + // Pack each of the three bytes into a single color, BGR -> 0RGB + for (i = image->height - 1; i >= 0; i--) { + for (j = 0; j < image->width; j++) { + image->pixels[(i * image->width) + j] = (*(curXor + 2) << 16) + + (*(curXor + 1) << 8) + (*curXor); + curXor += 3; + } + + // Set the alpha byte properly according to the andBmp. + for (j = 0; j < image->width; j += 8) { + for (k = 0; k < 8; k++) { + shift = 7 - k; + image->pixels[(i * image->width) + j + k] |= + ((*curAnd & (1 << shift)) >> shift) ? 0x0 : (0xff << 24); + } + + curAnd++; + } + + } + + break; + case 32: + // Pack each of the four bytes into a single color, BGRA -> ARGB + for (i = image->height - 1; i >= 0; i--) { + for (j = 0; j < image->width; j++) { + image->pixels[(i * image->width) + j] = (*(curXor + 3) << 24) + + (*(curXor + 2) << 16) + + (*(curXor + 1) << 8) + + (*curXor); + curXor += 4; + } + } + + break; + default: + goto cleanup; + break; + } + + ret = XcursorImageLoadCursor(_display, image); +#endif + +cleanup: +#ifdef HAVE_XCURSOR + XcursorImageDestroy(image); +#endif + delete[] entries; + delete[] palette; + delete[] xorBmp; + delete[] andBmp; + + return ret; +} + diff --git a/panda/src/x11display/x11GraphicsWindow.h b/panda/src/x11display/x11GraphicsWindow.h index cff763a64b..dc64b010a1 100644 --- a/panda/src/x11display/x11GraphicsWindow.h +++ b/panda/src/x11display/x11GraphicsWindow.h @@ -76,6 +76,7 @@ protected: private: Cursor get_cursor(const Filename &filename); + Cursor read_ico(istream &ico); protected: Display *_display;