File indexing completed on 2024-05-19 05:31:17

0001 /*
0002  * Copyright © 2002 Keith Packard
0003  *
0004  * Permission is hereby granted, free of charge, to any person obtaining
0005  * a copy of this software and associated documentation files (the
0006  * "Software"), to deal in the Software without restriction, including
0007  * without limitation the rights to use, copy, modify, merge, publish,
0008  * distribute, sublicense, and/or sell copies of the Software, and to
0009  * permit persons to whom the Software is furnished to do so, subject to
0010  * the following conditions:
0011  *
0012  * The above copyright notice and this permission notice (including the
0013  * next paragraph) shall be included in all copies or substantial
0014  * portions of the Software.
0015  *
0016  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
0017  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
0018  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
0019  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
0020  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
0021  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
0022  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
0023  * SOFTWARE.
0024  */
0025 
0026 #define _DEFAULT_SOURCE
0027 #include "xcursor.h"
0028 #include <stdio.h>
0029 #include <stdlib.h>
0030 #include <string.h>
0031 #include <dirent.h>
0032 
0033 /*
0034  * From libXcursor/include/X11/extensions/Xcursor.h
0035  */
0036 
0037 #define XcursorTrue 1
0038 #define XcursorFalse    0
0039 
0040 /*
0041  * Cursor files start with a header.  The header
0042  * contains a magic number, a version number and a
0043  * table of contents which has type and offset information
0044  * for the remaining tables in the file.
0045  *
0046  * File minor versions increment for compatible changes
0047  * File major versions increment for incompatible changes (never, we hope)
0048  *
0049  * Chunks of the same type are always upward compatible.  Incompatible
0050  * changes are made with new chunk types; the old data can remain under
0051  * the old type.  Upward compatible changes can add header data as the
0052  * header lengths are specified in the file.
0053  *
0054  *  File:
0055  *  FileHeader
0056  *  LISTofChunk
0057  *
0058  *  FileHeader:
0059  *  CARD32      magic       magic number
0060  *  CARD32      header      bytes in file header
0061  *  CARD32      version     file version
0062  *  CARD32      ntoc        number of toc entries
0063  *  LISTofFileToc   toc     table of contents
0064  *
0065  *  FileToc:
0066  *  CARD32      type        entry type
0067  *  CARD32      subtype     entry subtype (size for images)
0068  *  CARD32      position    absolute file position
0069  */
0070 
0071 #define XCURSOR_MAGIC   0x72756358  /* "Xcur" LSBFirst */
0072 
0073 /*
0074  * Current Xcursor version number.  Will be substituted by configure
0075  * from the version in the libXcursor configure.ac file.
0076  */
0077 
0078 #define XCURSOR_LIB_MAJOR 1
0079 #define XCURSOR_LIB_MINOR 1
0080 #define XCURSOR_LIB_REVISION 13
0081 #define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
0082                  (XCURSOR_LIB_MINOR * 100) + \
0083                  (XCURSOR_LIB_REVISION))
0084 
0085 /*
0086  * This version number is stored in cursor files; changes to the
0087  * file format require updating this version number
0088  */
0089 #define XCURSOR_FILE_MAJOR  1
0090 #define XCURSOR_FILE_MINOR  0
0091 #define XCURSOR_FILE_VERSION    ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR))
0092 #define XCURSOR_FILE_HEADER_LEN (4 * 4)
0093 #define XCURSOR_FILE_TOC_LEN    (3 * 4)
0094 
0095 typedef struct _XcursorFileToc {
0096     XcursorUInt     type;   /* chunk type */
0097     XcursorUInt     subtype;    /* subtype (size for images) */
0098     XcursorUInt     position;   /* absolute position in file */
0099 } XcursorFileToc;
0100 
0101 typedef struct _XcursorFileHeader {
0102     XcursorUInt     magic;  /* magic number */
0103     XcursorUInt     header; /* byte length of header */
0104     XcursorUInt     version;    /* file version number */
0105     XcursorUInt     ntoc;   /* number of toc entries */
0106     XcursorFileToc  *tocs;  /* table of contents */
0107 } XcursorFileHeader;
0108 
0109 /*
0110  * The rest of the file is a list of chunks, each tagged by type
0111  * and version.
0112  *
0113  *  Chunk:
0114  *  ChunkHeader
0115  *  <extra type-specific header fields>
0116  *  <type-specific data>
0117  *
0118  *  ChunkHeader:
0119  *  CARD32      header  bytes in chunk header + type header
0120  *  CARD32      type    chunk type
0121  *  CARD32      subtype chunk subtype
0122  *  CARD32      version chunk type version
0123  */
0124 
0125 #define XCURSOR_CHUNK_HEADER_LEN    (4 * 4)
0126 
0127 typedef struct _XcursorChunkHeader {
0128     XcursorUInt     header; /* bytes in chunk header */
0129     XcursorUInt     type;   /* chunk type */
0130     XcursorUInt     subtype;    /* chunk subtype (size for images) */
0131     XcursorUInt     version;    /* version of this type */
0132 } XcursorChunkHeader;
0133 
0134 /*
0135  * Here's a list of the known chunk types
0136  */
0137 
0138 /*
0139  * Comments consist of a 4-byte length field followed by
0140  * UTF-8 encoded text
0141  *
0142  *  Comment:
0143  *  ChunkHeader header  chunk header
0144  *  CARD32      length  bytes in text
0145  *  LISTofCARD8 text    UTF-8 encoded text
0146  */
0147 
0148 #define XCURSOR_COMMENT_TYPE        0xfffe0001
0149 #define XCURSOR_COMMENT_VERSION     1
0150 #define XCURSOR_COMMENT_HEADER_LEN  (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
0151 #define XCURSOR_COMMENT_COPYRIGHT   1
0152 #define XCURSOR_COMMENT_LICENSE     2
0153 #define XCURSOR_COMMENT_OTHER       3
0154 #define XCURSOR_COMMENT_MAX_LEN     0x100000
0155 
0156 typedef struct _XcursorComment {
0157     XcursorUInt     version;
0158     XcursorUInt     comment_type;
0159     char        *comment;
0160 } XcursorComment;
0161 
0162 /*
0163  * Each cursor image occupies a separate image chunk.
0164  * The length of the image header follows the chunk header
0165  * so that future versions can extend the header without
0166  * breaking older applications
0167  *
0168  *  Image:
0169  *  ChunkHeader header  chunk header
0170  *  CARD32      width   actual width
0171  *  CARD32      height  actual height
0172  *  CARD32      xhot    hot spot x
0173  *  CARD32      yhot    hot spot y
0174  *  CARD32      delay   animation delay
0175  *  LISTofCARD32    pixels  ARGB pixels
0176  */
0177 
0178 #define XCURSOR_IMAGE_TYPE          0xfffd0002
0179 #define XCURSOR_IMAGE_VERSION       1
0180 #define XCURSOR_IMAGE_HEADER_LEN    (XCURSOR_CHUNK_HEADER_LEN + (5*4))
0181 #define XCURSOR_IMAGE_MAX_SIZE      0x7fff  /* 32767x32767 max cursor size */
0182 
0183 typedef struct _XcursorFile XcursorFile;
0184 
0185 struct _XcursorFile {
0186     void    *closure;
0187     int     (*read)  (XcursorFile *file, unsigned char *buf, int len);
0188     int     (*write) (XcursorFile *file, unsigned char *buf, int len);
0189     int     (*seek)  (XcursorFile *file, long offset, int whence);
0190 };
0191 
0192 typedef struct _XcursorComments {
0193     int         ncomment;   /* number of comments */
0194     XcursorComment  **comments; /* array of XcursorComment pointers */
0195 } XcursorComments;
0196 
0197 /*
0198  * From libXcursor/src/file.c
0199  */
0200 
0201 static XcursorImage *
0202 XcursorImageCreate (int width, int height)
0203 {
0204     XcursorImage    *image;
0205 
0206     if (width < 0 || height < 0)
0207        return NULL;
0208     if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE)
0209        return NULL;
0210 
0211     image = malloc (sizeof (XcursorImage) +
0212             width * height * sizeof (XcursorPixel));
0213     if (!image)
0214     return NULL;
0215     image->version = XCURSOR_IMAGE_VERSION;
0216     image->pixels = (XcursorPixel *) (image + 1);
0217     image->size = width > height ? width : height;
0218     image->width = width;
0219     image->height = height;
0220     image->delay = 0;
0221     return image;
0222 }
0223 
0224 static void
0225 XcursorImageDestroy (XcursorImage *image)
0226 {
0227     free (image);
0228 }
0229 
0230 static XcursorImages *
0231 XcursorImagesCreate (int size)
0232 {
0233     XcursorImages   *images;
0234 
0235     images = malloc (sizeof (XcursorImages) +
0236              size * sizeof (XcursorImage *));
0237     if (!images)
0238     return NULL;
0239     images->nimage = 0;
0240     images->images = (XcursorImage **) (images + 1);
0241     return images;
0242 }
0243 
0244 void
0245 XcursorImagesDestroy (XcursorImages *images)
0246 {
0247     int n;
0248 
0249     if (!images)
0250         return;
0251 
0252     for (n = 0; n < images->nimage; n++)
0253     XcursorImageDestroy (images->images[n]);
0254     free (images);
0255 }
0256 
0257 static XcursorBool
0258 _XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
0259 {
0260     unsigned char   bytes[4];
0261 
0262     if (!file || !u)
0263         return XcursorFalse;
0264 
0265     if ((*file->read) (file, bytes, 4) != 4)
0266     return XcursorFalse;
0267 
0268     *u = ((XcursorUInt)(bytes[0]) << 0) |
0269          ((XcursorUInt)(bytes[1]) << 8) |
0270          ((XcursorUInt)(bytes[2]) << 16) |
0271          ((XcursorUInt)(bytes[3]) << 24);
0272     return XcursorTrue;
0273 }
0274 
0275 static void
0276 _XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
0277 {
0278     free (fileHeader);
0279 }
0280 
0281 static XcursorFileHeader *
0282 _XcursorFileHeaderCreate (XcursorUInt ntoc)
0283 {
0284     XcursorFileHeader   *fileHeader;
0285 
0286     if (ntoc > 0x10000)
0287     return NULL;
0288     fileHeader = malloc (sizeof (XcursorFileHeader) +
0289              ntoc * sizeof (XcursorFileToc));
0290     if (!fileHeader)
0291     return NULL;
0292     fileHeader->magic = XCURSOR_MAGIC;
0293     fileHeader->header = XCURSOR_FILE_HEADER_LEN;
0294     fileHeader->version = XCURSOR_FILE_VERSION;
0295     fileHeader->ntoc = ntoc;
0296     fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
0297     return fileHeader;
0298 }
0299 
0300 static XcursorFileHeader *
0301 _XcursorReadFileHeader (XcursorFile *file)
0302 {
0303     XcursorFileHeader   head, *fileHeader;
0304     XcursorUInt     skip;
0305     unsigned int    n;
0306 
0307     if (!file)
0308         return NULL;
0309 
0310     if (!_XcursorReadUInt (file, &head.magic))
0311     return NULL;
0312     if (head.magic != XCURSOR_MAGIC)
0313     return NULL;
0314     if (!_XcursorReadUInt (file, &head.header))
0315     return NULL;
0316     if (!_XcursorReadUInt (file, &head.version))
0317     return NULL;
0318     if (!_XcursorReadUInt (file, &head.ntoc))
0319     return NULL;
0320     skip = head.header - XCURSOR_FILE_HEADER_LEN;
0321     if (skip)
0322     if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
0323         return NULL;
0324     fileHeader = _XcursorFileHeaderCreate (head.ntoc);
0325     if (!fileHeader)
0326     return NULL;
0327     fileHeader->magic = head.magic;
0328     fileHeader->header = head.header;
0329     fileHeader->version = head.version;
0330     fileHeader->ntoc = head.ntoc;
0331     for (n = 0; n < fileHeader->ntoc; n++)
0332     {
0333     if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
0334         break;
0335     if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
0336         break;
0337     if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
0338         break;
0339     }
0340     if (n != fileHeader->ntoc)
0341     {
0342     _XcursorFileHeaderDestroy (fileHeader);
0343     return NULL;
0344     }
0345     return fileHeader;
0346 }
0347 
0348 static XcursorBool
0349 _XcursorSeekToToc (XcursorFile      *file,
0350            XcursorFileHeader    *fileHeader,
0351            int          toc)
0352 {
0353     if (!file || !fileHeader || \
0354         (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
0355     return XcursorFalse;
0356     return XcursorTrue;
0357 }
0358 
0359 static XcursorBool
0360 _XcursorFileReadChunkHeader (XcursorFile    *file,
0361                  XcursorFileHeader  *fileHeader,
0362                  int        toc,
0363                  XcursorChunkHeader *chunkHeader)
0364 {
0365     if (!file || !fileHeader || !chunkHeader)
0366         return XcursorFalse;
0367     if (!_XcursorSeekToToc (file, fileHeader, toc))
0368     return XcursorFalse;
0369     if (!_XcursorReadUInt (file, &chunkHeader->header))
0370     return XcursorFalse;
0371     if (!_XcursorReadUInt (file, &chunkHeader->type))
0372     return XcursorFalse;
0373     if (!_XcursorReadUInt (file, &chunkHeader->subtype))
0374     return XcursorFalse;
0375     if (!_XcursorReadUInt (file, &chunkHeader->version))
0376     return XcursorFalse;
0377     /* sanity check */
0378     if (chunkHeader->type != fileHeader->tocs[toc].type ||
0379     chunkHeader->subtype != fileHeader->tocs[toc].subtype)
0380     return XcursorFalse;
0381     return XcursorTrue;
0382 }
0383 
0384 #define dist(a,b)   ((a) > (b) ? (a) - (b) : (b) - (a))
0385 
0386 static XcursorDim
0387 _XcursorFindBestSize (XcursorFileHeader *fileHeader,
0388               XcursorDim    size,
0389               int       *nsizesp)
0390 {
0391     unsigned int n;
0392     int     nsizes = 0;
0393     XcursorDim  bestSize = 0;
0394     XcursorDim  thisSize;
0395 
0396     if (!fileHeader || !nsizesp)
0397         return 0;
0398 
0399     for (n = 0; n < fileHeader->ntoc; n++)
0400     {
0401     if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
0402         continue;
0403     thisSize = fileHeader->tocs[n].subtype;
0404     if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
0405     {
0406         bestSize = thisSize;
0407         nsizes = 1;
0408     }
0409     else if (thisSize == bestSize)
0410         nsizes++;
0411     }
0412     *nsizesp = nsizes;
0413     return bestSize;
0414 }
0415 
0416 static int
0417 _XcursorFindImageToc (XcursorFileHeader *fileHeader,
0418               XcursorDim    size,
0419               int       count)
0420 {
0421     unsigned int    toc;
0422     XcursorDim      thisSize;
0423 
0424     if (!fileHeader)
0425         return 0;
0426 
0427     for (toc = 0; toc < fileHeader->ntoc; toc++)
0428     {
0429     if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
0430         continue;
0431     thisSize = fileHeader->tocs[toc].subtype;
0432     if (thisSize != size)
0433         continue;
0434     if (!count)
0435         break;
0436     count--;
0437     }
0438     if (toc == fileHeader->ntoc)
0439     return -1;
0440     return toc;
0441 }
0442 
0443 static XcursorImage *
0444 _XcursorReadImage (XcursorFile      *file,
0445            XcursorFileHeader    *fileHeader,
0446            int          toc)
0447 {
0448     XcursorChunkHeader  chunkHeader;
0449     XcursorImage    head;
0450     XcursorImage    *image;
0451     int         n;
0452     XcursorPixel    *p;
0453 
0454     if (!file || !fileHeader)
0455         return NULL;
0456 
0457     if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
0458     return NULL;
0459     if (!_XcursorReadUInt (file, &head.width))
0460     return NULL;
0461     if (!_XcursorReadUInt (file, &head.height))
0462     return NULL;
0463     if (!_XcursorReadUInt (file, &head.xhot))
0464     return NULL;
0465     if (!_XcursorReadUInt (file, &head.yhot))
0466     return NULL;
0467     if (!_XcursorReadUInt (file, &head.delay))
0468     return NULL;
0469     /* sanity check data */
0470     if (head.width > XCURSOR_IMAGE_MAX_SIZE  ||
0471     head.height > XCURSOR_IMAGE_MAX_SIZE)
0472     return NULL;
0473     if (head.width == 0 || head.height == 0)
0474     return NULL;
0475     if (head.xhot > head.width || head.yhot > head.height)
0476     return NULL;
0477 
0478     /* Create the image and initialize it */
0479     image = XcursorImageCreate (head.width, head.height);
0480     if (image == NULL)
0481         return NULL;
0482     if (chunkHeader.version < image->version)
0483     image->version = chunkHeader.version;
0484     image->size = chunkHeader.subtype;
0485     image->xhot = head.xhot;
0486     image->yhot = head.yhot;
0487     image->delay = head.delay;
0488     n = image->width * image->height;
0489     p = image->pixels;
0490     while (n--)
0491     {
0492     if (!_XcursorReadUInt (file, p))
0493     {
0494         XcursorImageDestroy (image);
0495         return NULL;
0496     }
0497     p++;
0498     }
0499     return image;
0500 }
0501 
0502 static XcursorImages *
0503 XcursorXcFileLoadImages (XcursorFile *file, int size)
0504 {
0505     XcursorFileHeader   *fileHeader;
0506     XcursorDim      bestSize;
0507     int         nsize;
0508     XcursorImages   *images;
0509     int         n;
0510     int         toc;
0511 
0512     if (!file || size < 0)
0513     return NULL;
0514     fileHeader = _XcursorReadFileHeader (file);
0515     if (!fileHeader)
0516     return NULL;
0517     bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
0518     if (!bestSize)
0519     {
0520         _XcursorFileHeaderDestroy (fileHeader);
0521     return NULL;
0522     }
0523     images = XcursorImagesCreate (nsize);
0524     if (!images)
0525     {
0526         _XcursorFileHeaderDestroy (fileHeader);
0527     return NULL;
0528     }
0529     for (n = 0; n < nsize; n++)
0530     {
0531     toc = _XcursorFindImageToc (fileHeader, bestSize, n);
0532     if (toc < 0)
0533         break;
0534     images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
0535                                 toc);
0536     if (!images->images[images->nimage])
0537         break;
0538     images->nimage++;
0539     }
0540     _XcursorFileHeaderDestroy (fileHeader);
0541     if (images->nimage != nsize)
0542     {
0543     XcursorImagesDestroy (images);
0544     images = NULL;
0545     }
0546     return images;
0547 }
0548 
0549 static int
0550 _XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
0551 {
0552     FILE    *f = file->closure;
0553     return fread (buf, 1, len, f);
0554 }
0555 
0556 static int
0557 _XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
0558 {
0559     FILE    *f = file->closure;
0560     return fwrite (buf, 1, len, f);
0561 }
0562 
0563 static int
0564 _XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
0565 {
0566     FILE    *f = file->closure;
0567     return fseek (f, offset, whence);
0568 }
0569 
0570 static void
0571 _XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
0572 {
0573     file->closure = stdfile;
0574     file->read = _XcursorStdioFileRead;
0575     file->write = _XcursorStdioFileWrite;
0576     file->seek = _XcursorStdioFileSeek;
0577 }
0578 
0579 XcursorImages *
0580 XcursorFileLoadImages (const char *file, int size)
0581 {
0582     XcursorFile f;
0583     XcursorImages *images;
0584 
0585     FILE *fp = fopen(file, "r");
0586     if (!fp)
0587         return NULL;
0588 
0589     _XcursorStdioFileInitialize (fp, &f);
0590     images = XcursorXcFileLoadImages (&f, size);
0591     fclose(fp);
0592 
0593     return images;
0594 }