1/*
2 * Copyright © 2002 Keith Packard
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the
13 * next paragraph) shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26#include "xcursor.h"
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <dirent.h>
31
32/*
33 * From libXcursor/include/X11/extensions/Xcursor.h
34 */
35
36#define XcursorTrue 1
37#define XcursorFalse 0
38
39/*
40 * Cursor files start with a header. The header
41 * contains a magic number, a version number and a
42 * table of contents which has type and offset information
43 * for the remaining tables in the file.
44 *
45 * File minor versions increment for compatible changes
46 * File major versions increment for incompatible changes (never, we hope)
47 *
48 * Chunks of the same type are always upward compatible. Incompatible
49 * changes are made with new chunk types; the old data can remain under
50 * the old type. Upward compatible changes can add header data as the
51 * header lengths are specified in the file.
52 *
53 * File:
54 * FileHeader
55 * LISTofChunk
56 *
57 * FileHeader:
58 * CARD32 magic magic number
59 * CARD32 header bytes in file header
60 * CARD32 version file version
61 * CARD32 ntoc number of toc entries
62 * LISTofFileToc toc table of contents
63 *
64 * FileToc:
65 * CARD32 type entry type
66 * CARD32 subtype entry subtype (size for images)
67 * CARD32 position absolute file position
68 */
69
70#define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */
71
72/*
73 * Current Xcursor version number. Will be substituted by configure
74 * from the version in the libXcursor configure.ac file.
75 */
76
77#define XCURSOR_LIB_MAJOR 1
78#define XCURSOR_LIB_MINOR 1
79#define XCURSOR_LIB_REVISION 13
80#define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
81 (XCURSOR_LIB_MINOR * 100) + \
82 (XCURSOR_LIB_REVISION))
83
84/*
85 * This version number is stored in cursor files; changes to the
86 * file format require updating this version number
87 */
88#define XCURSOR_FILE_MAJOR 1
89#define XCURSOR_FILE_MINOR 0
90#define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR))
91#define XCURSOR_FILE_HEADER_LEN (4 * 4)
92#define XCURSOR_FILE_TOC_LEN (3 * 4)
93
94typedef struct _XcursorFileToc {
95 XcursorUInt type; /* chunk type */
96 XcursorUInt subtype; /* subtype (size for images) */
97 XcursorUInt position; /* absolute position in file */
98} XcursorFileToc;
99
100typedef struct _XcursorFileHeader {
101 XcursorUInt magic; /* magic number */
102 XcursorUInt header; /* byte length of header */
103 XcursorUInt version; /* file version number */
104 XcursorUInt ntoc; /* number of toc entries */
105 XcursorFileToc *tocs; /* table of contents */
106} XcursorFileHeader;
107
108/*
109 * The rest of the file is a list of chunks, each tagged by type
110 * and version.
111 *
112 * Chunk:
113 * ChunkHeader
114 * <extra type-specific header fields>
115 * <type-specific data>
116 *
117 * ChunkHeader:
118 * CARD32 header bytes in chunk header + type header
119 * CARD32 type chunk type
120 * CARD32 subtype chunk subtype
121 * CARD32 version chunk type version
122 */
123
124#define XCURSOR_CHUNK_HEADER_LEN (4 * 4)
125
126typedef struct _XcursorChunkHeader {
127 XcursorUInt header; /* bytes in chunk header */
128 XcursorUInt type; /* chunk type */
129 XcursorUInt subtype; /* chunk subtype (size for images) */
130 XcursorUInt version; /* version of this type */
131} XcursorChunkHeader;
132
133/*
134 * Here's a list of the known chunk types
135 */
136
137/*
138 * Comments consist of a 4-byte length field followed by
139 * UTF-8 encoded text
140 *
141 * Comment:
142 * ChunkHeader header chunk header
143 * CARD32 length bytes in text
144 * LISTofCARD8 text UTF-8 encoded text
145 */
146
147#define XCURSOR_COMMENT_TYPE 0xfffe0001
148#define XCURSOR_COMMENT_VERSION 1
149#define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
150#define XCURSOR_COMMENT_COPYRIGHT 1
151#define XCURSOR_COMMENT_LICENSE 2
152#define XCURSOR_COMMENT_OTHER 3
153#define XCURSOR_COMMENT_MAX_LEN 0x100000
154
155typedef struct _XcursorComment {
156 XcursorUInt version;
157 XcursorUInt comment_type;
158 char *comment;
159} XcursorComment;
160
161/*
162 * Each cursor image occupies a separate image chunk.
163 * The length of the image header follows the chunk header
164 * so that future versions can extend the header without
165 * breaking older applications
166 *
167 * Image:
168 * ChunkHeader header chunk header
169 * CARD32 width actual width
170 * CARD32 height actual height
171 * CARD32 xhot hot spot x
172 * CARD32 yhot hot spot y
173 * CARD32 delay animation delay
174 * LISTofCARD32 pixels ARGB pixels
175 */
176
177#define XCURSOR_IMAGE_TYPE 0xfffd0002
178#define XCURSOR_IMAGE_VERSION 1
179#define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4))
180#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */
181
182typedef struct _XcursorFile XcursorFile;
183
184struct _XcursorFile {
185 void *closure;
186 int (*read) (XcursorFile *file, unsigned char *buf, int len);
187 int (*write) (XcursorFile *file, unsigned char *buf, int len);
188 int (*seek) (XcursorFile *file, long offset, int whence);
189};
190
191typedef struct _XcursorComments {
192 int ncomment; /* number of comments */
193 XcursorComment **comments; /* array of XcursorComment pointers */
194} XcursorComments;
195
196/*
197 * From libXcursor/src/file.c
198 */
199
200static XcursorImage *
201XcursorImageCreate (int width, int height)
202{
203 XcursorImage *image;
204
205 if (width < 0 || height < 0)
206 return NULL;
207 if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE)
208 return NULL;
209
210 image = malloc (size: sizeof (XcursorImage) +
211 width * height * sizeof (XcursorPixel));
212 if (!image)
213 return NULL;
214 image->version = XCURSOR_IMAGE_VERSION;
215 image->pixels = (XcursorPixel *) (image + 1);
216 image->size = width > height ? width : height;
217 image->width = width;
218 image->height = height;
219 image->delay = 0;
220 return image;
221}
222
223static void
224XcursorImageDestroy (XcursorImage *image)
225{
226 free (ptr: image);
227}
228
229static XcursorImages *
230XcursorImagesCreate (int size)
231{
232 XcursorImages *images;
233
234 images = malloc (size: sizeof (XcursorImages) +
235 size * sizeof (XcursorImage *));
236 if (!images)
237 return NULL;
238 images->nimage = 0;
239 images->images = (XcursorImage **) (images + 1);
240 images->name = NULL;
241 return images;
242}
243
244static void
245XcursorImagesDestroy (XcursorImages *images)
246{
247 int n;
248
249 if (!images)
250 return;
251
252 for (n = 0; n < images->nimage; n++)
253 XcursorImageDestroy (image: images->images[n]);
254 if (images->name)
255 free (ptr: images->name);
256 free (ptr: images);
257}
258
259static XcursorBool
260_XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
261{
262 unsigned char bytes[4];
263
264 if (!file || !u)
265 return XcursorFalse;
266
267 if ((*file->read) (file, bytes, 4) != 4)
268 return XcursorFalse;
269 *u = ((bytes[0] << 0) |
270 (bytes[1] << 8) |
271 (bytes[2] << 16) |
272 (bytes[3] << 24));
273 return XcursorTrue;
274}
275
276static void
277_XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
278{
279 free (ptr: fileHeader);
280}
281
282static XcursorFileHeader *
283_XcursorFileHeaderCreate (int ntoc)
284{
285 XcursorFileHeader *fileHeader;
286
287 if (ntoc > 0x10000)
288 return NULL;
289 fileHeader = malloc (size: sizeof (XcursorFileHeader) +
290 ntoc * sizeof (XcursorFileToc));
291 if (!fileHeader)
292 return NULL;
293 fileHeader->magic = XCURSOR_MAGIC;
294 fileHeader->header = XCURSOR_FILE_HEADER_LEN;
295 fileHeader->version = XCURSOR_FILE_VERSION;
296 fileHeader->ntoc = ntoc;
297 fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
298 return fileHeader;
299}
300
301static XcursorFileHeader *
302_XcursorReadFileHeader (XcursorFile *file)
303{
304 XcursorFileHeader head, *fileHeader;
305 XcursorUInt skip;
306 unsigned int n;
307
308 if (!file)
309 return NULL;
310
311 if (!_XcursorReadUInt (file, u: &head.magic))
312 return NULL;
313 if (head.magic != XCURSOR_MAGIC)
314 return NULL;
315 if (!_XcursorReadUInt (file, u: &head.header))
316 return NULL;
317 if (!_XcursorReadUInt (file, u: &head.version))
318 return NULL;
319 if (!_XcursorReadUInt (file, u: &head.ntoc))
320 return NULL;
321 skip = head.header - XCURSOR_FILE_HEADER_LEN;
322 if (skip)
323 if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
324 return NULL;
325 fileHeader = _XcursorFileHeaderCreate (ntoc: head.ntoc);
326 if (!fileHeader)
327 return NULL;
328 fileHeader->magic = head.magic;
329 fileHeader->header = head.header;
330 fileHeader->version = head.version;
331 fileHeader->ntoc = head.ntoc;
332 for (n = 0; n < fileHeader->ntoc; n++)
333 {
334 if (!_XcursorReadUInt (file, u: &fileHeader->tocs[n].type))
335 break;
336 if (!_XcursorReadUInt (file, u: &fileHeader->tocs[n].subtype))
337 break;
338 if (!_XcursorReadUInt (file, u: &fileHeader->tocs[n].position))
339 break;
340 }
341 if (n != fileHeader->ntoc)
342 {
343 _XcursorFileHeaderDestroy (fileHeader);
344 return NULL;
345 }
346 return fileHeader;
347}
348
349static XcursorBool
350_XcursorSeekToToc (XcursorFile *file,
351 XcursorFileHeader *fileHeader,
352 int toc)
353{
354 if (!file || !fileHeader || \
355 (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
356 return XcursorFalse;
357 return XcursorTrue;
358}
359
360static XcursorBool
361_XcursorFileReadChunkHeader (XcursorFile *file,
362 XcursorFileHeader *fileHeader,
363 int toc,
364 XcursorChunkHeader *chunkHeader)
365{
366 if (!file || !fileHeader || !chunkHeader)
367 return XcursorFalse;
368 if (!_XcursorSeekToToc (file, fileHeader, toc))
369 return XcursorFalse;
370 if (!_XcursorReadUInt (file, u: &chunkHeader->header))
371 return XcursorFalse;
372 if (!_XcursorReadUInt (file, u: &chunkHeader->type))
373 return XcursorFalse;
374 if (!_XcursorReadUInt (file, u: &chunkHeader->subtype))
375 return XcursorFalse;
376 if (!_XcursorReadUInt (file, u: &chunkHeader->version))
377 return XcursorFalse;
378 /* sanity check */
379 if (chunkHeader->type != fileHeader->tocs[toc].type ||
380 chunkHeader->subtype != fileHeader->tocs[toc].subtype)
381 return XcursorFalse;
382 return XcursorTrue;
383}
384
385#define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
386
387static XcursorDim
388_XcursorFindBestSize (XcursorFileHeader *fileHeader,
389 XcursorDim size,
390 int *nsizesp)
391{
392 unsigned int n;
393 int nsizes = 0;
394 XcursorDim bestSize = 0;
395 XcursorDim thisSize;
396
397 if (!fileHeader || !nsizesp)
398 return 0;
399
400 for (n = 0; n < fileHeader->ntoc; n++)
401 {
402 if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
403 continue;
404 thisSize = fileHeader->tocs[n].subtype;
405 if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
406 {
407 bestSize = thisSize;
408 nsizes = 1;
409 }
410 else if (thisSize == bestSize)
411 nsizes++;
412 }
413 *nsizesp = nsizes;
414 return bestSize;
415}
416
417static int
418_XcursorFindImageToc (XcursorFileHeader *fileHeader,
419 XcursorDim size,
420 int count)
421{
422 unsigned int toc;
423 XcursorDim thisSize;
424
425 if (!fileHeader)
426 return 0;
427
428 for (toc = 0; toc < fileHeader->ntoc; toc++)
429 {
430 if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
431 continue;
432 thisSize = fileHeader->tocs[toc].subtype;
433 if (thisSize != size)
434 continue;
435 if (!count)
436 break;
437 count--;
438 }
439 if (toc == fileHeader->ntoc)
440 return -1;
441 return toc;
442}
443
444static XcursorImage *
445_XcursorReadImage (XcursorFile *file,
446 XcursorFileHeader *fileHeader,
447 int toc)
448{
449 XcursorChunkHeader chunkHeader;
450 XcursorImage head;
451 XcursorImage *image;
452 int n;
453 XcursorPixel *p;
454
455 if (!file || !fileHeader)
456 return NULL;
457
458 if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, chunkHeader: &chunkHeader))
459 return NULL;
460 if (!_XcursorReadUInt (file, u: &head.width))
461 return NULL;
462 if (!_XcursorReadUInt (file, u: &head.height))
463 return NULL;
464 if (!_XcursorReadUInt (file, u: &head.xhot))
465 return NULL;
466 if (!_XcursorReadUInt (file, u: &head.yhot))
467 return NULL;
468 if (!_XcursorReadUInt (file, u: &head.delay))
469 return NULL;
470 /* sanity check data */
471 if (head.width > XCURSOR_IMAGE_MAX_SIZE ||
472 head.height > XCURSOR_IMAGE_MAX_SIZE)
473 return NULL;
474 if (head.width == 0 || head.height == 0)
475 return NULL;
476 if (head.xhot > head.width || head.yhot > head.height)
477 return NULL;
478
479 /* Create the image and initialize it */
480 image = XcursorImageCreate (width: head.width, height: head.height);
481 if (image == NULL)
482 return NULL;
483 if (chunkHeader.version < image->version)
484 image->version = chunkHeader.version;
485 image->size = chunkHeader.subtype;
486 image->xhot = head.xhot;
487 image->yhot = head.yhot;
488 image->delay = head.delay;
489 n = image->width * image->height;
490 p = image->pixels;
491 while (n--)
492 {
493 if (!_XcursorReadUInt (file, u: p))
494 {
495 XcursorImageDestroy (image);
496 return NULL;
497 }
498 p++;
499 }
500 return image;
501}
502
503static XcursorImages *
504XcursorXcFileLoadImages (XcursorFile *file, int size)
505{
506 XcursorFileHeader *fileHeader;
507 XcursorDim bestSize;
508 int nsize;
509 XcursorImages *images;
510 int n;
511 int toc;
512
513 if (!file || size < 0)
514 return NULL;
515 fileHeader = _XcursorReadFileHeader (file);
516 if (!fileHeader)
517 return NULL;
518 bestSize = _XcursorFindBestSize (fileHeader, size: (XcursorDim) size, nsizesp: &nsize);
519 if (!bestSize)
520 {
521 _XcursorFileHeaderDestroy (fileHeader);
522 return NULL;
523 }
524 images = XcursorImagesCreate (size: nsize);
525 if (!images)
526 {
527 _XcursorFileHeaderDestroy (fileHeader);
528 return NULL;
529 }
530 for (n = 0; n < nsize; n++)
531 {
532 toc = _XcursorFindImageToc (fileHeader, size: bestSize, count: n);
533 if (toc < 0)
534 break;
535 images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
536 toc);
537 if (!images->images[images->nimage])
538 break;
539 images->nimage++;
540 }
541 _XcursorFileHeaderDestroy (fileHeader);
542 if (images->nimage != nsize)
543 {
544 XcursorImagesDestroy (images);
545 images = NULL;
546 }
547 return images;
548}
549
550static int
551_XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
552{
553 FILE *f = file->closure;
554 return fread (ptr: buf, size: 1, n: len, stream: f);
555}
556
557static int
558_XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
559{
560 FILE *f = file->closure;
561 return fwrite (ptr: buf, size: 1, n: len, s: f);
562}
563
564static int
565_XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
566{
567 FILE *f = file->closure;
568 return fseek (stream: f, off: offset, whence: whence);
569}
570
571static void
572_XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
573{
574 file->closure = stdfile;
575 file->read = _XcursorStdioFileRead;
576 file->write = _XcursorStdioFileWrite;
577 file->seek = _XcursorStdioFileSeek;
578}
579
580static XcursorImages *
581XcursorFileLoadImages (FILE *file, int size)
582{
583 XcursorFile f;
584
585 if (!file)
586 return NULL;
587
588 _XcursorStdioFileInitialize (stdfile: file, file: &f);
589 return XcursorXcFileLoadImages (file: &f, size);
590}
591
592XcursorImages *
593xcursor_load_images (const char *path, int size)
594{
595 FILE *f;
596 XcursorImages *images;
597
598 f = fopen (filename: path, modes: "r");
599 if (!f)
600 return NULL;
601
602 images = XcursorFileLoadImages (file: f, size);
603 fclose (stream: f);
604
605 return images;
606}
607
608void
609xcursor_images_destroy (XcursorImages *images)
610{
611 XcursorImagesDestroy (images);
612}
613

source code of gtk/gdk/wayland/cursor/xcursor.c