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
94 typedef struct _XcursorFileToc {
95 XcursorUInt type; /* chunk type */
96 XcursorUInt subtype; /* subtype (size for images) */
97 XcursorUInt position; /* absolute position in file */
98 } XcursorFileToc;
99
100 typedef 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
126 typedef 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
155 typedef 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
182 typedef struct _XcursorFile XcursorFile;
183
184 struct _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
191 typedef 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
200 static XcursorImage *
XcursorImageCreate(int width,int height)201 XcursorImageCreate (int width, int height)
202 {
203 XcursorImage *image;
204
205 image = malloc (sizeof (XcursorImage) +
206 width * height * sizeof (XcursorPixel));
207 if (!image)
208 return NULL;
209 image->version = XCURSOR_IMAGE_VERSION;
210 image->pixels = (XcursorPixel *) (image + 1);
211 image->size = width > height ? width : height;
212 image->width = width;
213 image->height = height;
214 image->delay = 0;
215 return image;
216 }
217
218 static void
XcursorImageDestroy(XcursorImage * image)219 XcursorImageDestroy (XcursorImage *image)
220 {
221 free (image);
222 }
223
224 static XcursorImages *
XcursorImagesCreate(int size)225 XcursorImagesCreate (int size)
226 {
227 XcursorImages *images;
228
229 images = malloc (sizeof (XcursorImages) +
230 size * sizeof (XcursorImage *));
231 if (!images)
232 return NULL;
233 images->nimage = 0;
234 images->images = (XcursorImage **) (images + 1);
235 images->name = NULL;
236 return images;
237 }
238
239 void
XcursorImagesDestroy(XcursorImages * images)240 XcursorImagesDestroy (XcursorImages *images)
241 {
242 int n;
243
244 if (!images)
245 return;
246
247 for (n = 0; n < images->nimage; n++)
248 XcursorImageDestroy (images->images[n]);
249 if (images->name)
250 free (images->name);
251 free (images);
252 }
253
254 static void
XcursorImagesSetName(XcursorImages * images,const char * name)255 XcursorImagesSetName (XcursorImages *images, const char *name)
256 {
257 char *new;
258
259 if (!images || !name)
260 return;
261
262 new = malloc (strlen (name) + 1);
263
264 if (!new)
265 return;
266
267 strcpy (new, name);
268 if (images->name)
269 free (images->name);
270 images->name = new;
271 }
272
273 static XcursorBool
_XcursorReadUInt(XcursorFile * file,XcursorUInt * u)274 _XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
275 {
276 unsigned char bytes[4];
277
278 if (!file || !u)
279 return XcursorFalse;
280
281 if ((*file->read) (file, bytes, 4) != 4)
282 return XcursorFalse;
283 *u = ((bytes[0] << 0) |
284 (bytes[1] << 8) |
285 (bytes[2] << 16) |
286 (bytes[3] << 24));
287 return XcursorTrue;
288 }
289
290 static void
_XcursorFileHeaderDestroy(XcursorFileHeader * fileHeader)291 _XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
292 {
293 free (fileHeader);
294 }
295
296 static XcursorFileHeader *
_XcursorFileHeaderCreate(int ntoc)297 _XcursorFileHeaderCreate (int ntoc)
298 {
299 XcursorFileHeader *fileHeader;
300
301 if (ntoc > 0x10000)
302 return NULL;
303 fileHeader = malloc (sizeof (XcursorFileHeader) +
304 ntoc * sizeof (XcursorFileToc));
305 if (!fileHeader)
306 return NULL;
307 fileHeader->magic = XCURSOR_MAGIC;
308 fileHeader->header = XCURSOR_FILE_HEADER_LEN;
309 fileHeader->version = XCURSOR_FILE_VERSION;
310 fileHeader->ntoc = ntoc;
311 fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
312 return fileHeader;
313 }
314
315 static XcursorFileHeader *
_XcursorReadFileHeader(XcursorFile * file)316 _XcursorReadFileHeader (XcursorFile *file)
317 {
318 XcursorFileHeader head, *fileHeader;
319 XcursorUInt skip;
320 unsigned int n;
321
322 if (!file)
323 return NULL;
324
325 if (!_XcursorReadUInt (file, &head.magic))
326 return NULL;
327 if (head.magic != XCURSOR_MAGIC)
328 return NULL;
329 if (!_XcursorReadUInt (file, &head.header))
330 return NULL;
331 if (!_XcursorReadUInt (file, &head.version))
332 return NULL;
333 if (!_XcursorReadUInt (file, &head.ntoc))
334 return NULL;
335 skip = head.header - XCURSOR_FILE_HEADER_LEN;
336 if (skip)
337 if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
338 return NULL;
339 fileHeader = _XcursorFileHeaderCreate (head.ntoc);
340 if (!fileHeader)
341 return NULL;
342 fileHeader->magic = head.magic;
343 fileHeader->header = head.header;
344 fileHeader->version = head.version;
345 fileHeader->ntoc = head.ntoc;
346 for (n = 0; n < fileHeader->ntoc; n++)
347 {
348 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
349 break;
350 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
351 break;
352 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
353 break;
354 }
355 if (n != fileHeader->ntoc)
356 {
357 _XcursorFileHeaderDestroy (fileHeader);
358 return NULL;
359 }
360 return fileHeader;
361 }
362
363 static XcursorBool
_XcursorSeekToToc(XcursorFile * file,XcursorFileHeader * fileHeader,int toc)364 _XcursorSeekToToc (XcursorFile *file,
365 XcursorFileHeader *fileHeader,
366 int toc)
367 {
368 if (!file || !fileHeader || \
369 (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
370 return XcursorFalse;
371 return XcursorTrue;
372 }
373
374 static XcursorBool
_XcursorFileReadChunkHeader(XcursorFile * file,XcursorFileHeader * fileHeader,int toc,XcursorChunkHeader * chunkHeader)375 _XcursorFileReadChunkHeader (XcursorFile *file,
376 XcursorFileHeader *fileHeader,
377 int toc,
378 XcursorChunkHeader *chunkHeader)
379 {
380 if (!file || !fileHeader || !chunkHeader)
381 return XcursorFalse;
382 if (!_XcursorSeekToToc (file, fileHeader, toc))
383 return XcursorFalse;
384 if (!_XcursorReadUInt (file, &chunkHeader->header))
385 return XcursorFalse;
386 if (!_XcursorReadUInt (file, &chunkHeader->type))
387 return XcursorFalse;
388 if (!_XcursorReadUInt (file, &chunkHeader->subtype))
389 return XcursorFalse;
390 if (!_XcursorReadUInt (file, &chunkHeader->version))
391 return XcursorFalse;
392 /* sanity check */
393 if (chunkHeader->type != fileHeader->tocs[toc].type ||
394 chunkHeader->subtype != fileHeader->tocs[toc].subtype)
395 return XcursorFalse;
396 return XcursorTrue;
397 }
398
399 #define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
400
401 static XcursorDim
_XcursorFindBestSize(XcursorFileHeader * fileHeader,XcursorDim size,int * nsizesp)402 _XcursorFindBestSize (XcursorFileHeader *fileHeader,
403 XcursorDim size,
404 int *nsizesp)
405 {
406 unsigned int n;
407 int nsizes = 0;
408 XcursorDim bestSize = 0;
409 XcursorDim thisSize;
410
411 if (!fileHeader || !nsizesp)
412 return 0;
413
414 for (n = 0; n < fileHeader->ntoc; n++)
415 {
416 if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
417 continue;
418 thisSize = fileHeader->tocs[n].subtype;
419 if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
420 {
421 bestSize = thisSize;
422 nsizes = 1;
423 }
424 else if (thisSize == bestSize)
425 nsizes++;
426 }
427 *nsizesp = nsizes;
428 return bestSize;
429 }
430
431 static int
_XcursorFindImageToc(XcursorFileHeader * fileHeader,XcursorDim size,int count)432 _XcursorFindImageToc (XcursorFileHeader *fileHeader,
433 XcursorDim size,
434 int count)
435 {
436 unsigned int toc;
437 XcursorDim thisSize;
438
439 if (!fileHeader)
440 return 0;
441
442 for (toc = 0; toc < fileHeader->ntoc; toc++)
443 {
444 if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
445 continue;
446 thisSize = fileHeader->tocs[toc].subtype;
447 if (thisSize != size)
448 continue;
449 if (!count)
450 break;
451 count--;
452 }
453 if (toc == fileHeader->ntoc)
454 return -1;
455 return toc;
456 }
457
458 static XcursorImage *
_XcursorReadImage(XcursorFile * file,XcursorFileHeader * fileHeader,int toc)459 _XcursorReadImage (XcursorFile *file,
460 XcursorFileHeader *fileHeader,
461 int toc)
462 {
463 XcursorChunkHeader chunkHeader;
464 XcursorImage head;
465 XcursorImage *image;
466 int n;
467 XcursorPixel *p;
468
469 if (!file || !fileHeader)
470 return NULL;
471
472 if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
473 return NULL;
474 if (!_XcursorReadUInt (file, &head.width))
475 return NULL;
476 if (!_XcursorReadUInt (file, &head.height))
477 return NULL;
478 if (!_XcursorReadUInt (file, &head.xhot))
479 return NULL;
480 if (!_XcursorReadUInt (file, &head.yhot))
481 return NULL;
482 if (!_XcursorReadUInt (file, &head.delay))
483 return NULL;
484 /* sanity check data */
485 if (head.width >= 0x10000 || head.height > 0x10000)
486 return NULL;
487 if (head.width == 0 || head.height == 0)
488 return NULL;
489 if (head.xhot > head.width || head.yhot > head.height)
490 return NULL;
491
492 /* Create the image and initialize it */
493 image = XcursorImageCreate (head.width, head.height);
494 if (image == NULL)
495 return NULL;
496 if (chunkHeader.version < image->version)
497 image->version = chunkHeader.version;
498 image->size = chunkHeader.subtype;
499 image->xhot = head.xhot;
500 image->yhot = head.yhot;
501 image->delay = head.delay;
502 n = image->width * image->height;
503 p = image->pixels;
504 while (n--)
505 {
506 if (!_XcursorReadUInt (file, p))
507 {
508 XcursorImageDestroy (image);
509 return NULL;
510 }
511 p++;
512 }
513 return image;
514 }
515
516 static XcursorImages *
XcursorXcFileLoadImages(XcursorFile * file,int size)517 XcursorXcFileLoadImages (XcursorFile *file, int size)
518 {
519 XcursorFileHeader *fileHeader;
520 XcursorDim bestSize;
521 int nsize;
522 XcursorImages *images;
523 int n;
524 int toc;
525
526 if (!file || size < 0)
527 return NULL;
528 fileHeader = _XcursorReadFileHeader (file);
529 if (!fileHeader)
530 return NULL;
531 bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
532 if (!bestSize)
533 {
534 _XcursorFileHeaderDestroy (fileHeader);
535 return NULL;
536 }
537 images = XcursorImagesCreate (nsize);
538 if (!images)
539 {
540 _XcursorFileHeaderDestroy (fileHeader);
541 return NULL;
542 }
543 for (n = 0; n < nsize; n++)
544 {
545 toc = _XcursorFindImageToc (fileHeader, bestSize, n);
546 if (toc < 0)
547 break;
548 images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
549 toc);
550 if (!images->images[images->nimage])
551 break;
552 images->nimage++;
553 }
554 _XcursorFileHeaderDestroy (fileHeader);
555 if (images->nimage != nsize)
556 {
557 XcursorImagesDestroy (images);
558 images = NULL;
559 }
560 return images;
561 }
562
563 static int
_XcursorStdioFileRead(XcursorFile * file,unsigned char * buf,int len)564 _XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
565 {
566 FILE *f = file->closure;
567 return fread (buf, 1, len, f);
568 }
569
570 static int
_XcursorStdioFileWrite(XcursorFile * file,unsigned char * buf,int len)571 _XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
572 {
573 FILE *f = file->closure;
574 return fwrite (buf, 1, len, f);
575 }
576
577 static int
_XcursorStdioFileSeek(XcursorFile * file,long offset,int whence)578 _XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
579 {
580 FILE *f = file->closure;
581 return fseek (f, offset, whence);
582 }
583
584 static void
_XcursorStdioFileInitialize(FILE * stdfile,XcursorFile * file)585 _XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
586 {
587 file->closure = stdfile;
588 file->read = _XcursorStdioFileRead;
589 file->write = _XcursorStdioFileWrite;
590 file->seek = _XcursorStdioFileSeek;
591 }
592
593 static XcursorImages *
XcursorFileLoadImages(FILE * file,int size)594 XcursorFileLoadImages (FILE *file, int size)
595 {
596 XcursorFile f;
597
598 if (!file)
599 return NULL;
600
601 _XcursorStdioFileInitialize (file, &f);
602 return XcursorXcFileLoadImages (&f, size);
603 }
604
605 /*
606 * From libXcursor/src/library.c
607 */
608
609 #ifndef ICONDIR
610 #define ICONDIR "/usr/X11R6/lib/X11/icons"
611 #endif
612
613 #ifndef XCURSORPATH
614 #define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR
615 #endif
616
617 static const char *
XcursorLibraryPath(void)618 XcursorLibraryPath (void)
619 {
620 static const char *path;
621
622 if (!path)
623 {
624 path = getenv ("XCURSOR_PATH");
625 if (!path)
626 path = XCURSORPATH;
627 }
628 return path;
629 }
630
631 static void
_XcursorAddPathElt(char * path,const char * elt,int len)632 _XcursorAddPathElt (char *path, const char *elt, int len)
633 {
634 int pathlen = strlen (path);
635
636 /* append / if the path doesn't currently have one */
637 if (path[0] == '\0' || path[pathlen - 1] != '/')
638 {
639 strcat (path, "/");
640 pathlen++;
641 }
642 if (len == -1)
643 len = strlen (elt);
644 /* strip leading slashes */
645 while (len && elt[0] == '/')
646 {
647 elt++;
648 len--;
649 }
650 strncpy (path + pathlen, elt, len);
651 path[pathlen + len] = '\0';
652 }
653
654 static char *
_XcursorBuildThemeDir(const char * dir,const char * theme)655 _XcursorBuildThemeDir (const char *dir, const char *theme)
656 {
657 const char *colon;
658 const char *tcolon;
659 char *full;
660 char *home;
661 int dirlen;
662 int homelen;
663 int themelen;
664 int len;
665
666 if (!dir || !theme)
667 return NULL;
668
669 colon = strchr (dir, ':');
670 if (!colon)
671 colon = dir + strlen (dir);
672
673 dirlen = colon - dir;
674
675 tcolon = strchr (theme, ':');
676 if (!tcolon)
677 tcolon = theme + strlen (theme);
678
679 themelen = tcolon - theme;
680
681 home = NULL;
682 homelen = 0;
683 if (*dir == '~')
684 {
685 home = getenv ("HOME");
686 if (!home)
687 return NULL;
688 homelen = strlen (home);
689 dir++;
690 dirlen--;
691 }
692
693 /*
694 * add space for any needed directory separators, one per component,
695 * and one for the trailing null
696 */
697 len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
698
699 full = malloc (len);
700 if (!full)
701 return NULL;
702 full[0] = '\0';
703
704 if (home)
705 _XcursorAddPathElt (full, home, -1);
706 _XcursorAddPathElt (full, dir, dirlen);
707 _XcursorAddPathElt (full, theme, themelen);
708 return full;
709 }
710
711 static char *
_XcursorBuildFullname(const char * dir,const char * subdir,const char * file)712 _XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
713 {
714 char *full;
715
716 if (!dir || !subdir || !file)
717 return NULL;
718
719 full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
720 if (!full)
721 return NULL;
722 full[0] = '\0';
723 _XcursorAddPathElt (full, dir, -1);
724 _XcursorAddPathElt (full, subdir, -1);
725 _XcursorAddPathElt (full, file, -1);
726 return full;
727 }
728
729 static const char *
_XcursorNextPath(const char * path)730 _XcursorNextPath (const char *path)
731 {
732 char *colon = strchr (path, ':');
733
734 if (!colon)
735 return NULL;
736 return colon + 1;
737 }
738
739 #define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
740 #define XcursorSep(c) ((c) == ';' || (c) == ',')
741
742 static char *
_XcursorThemeInherits(const char * full)743 _XcursorThemeInherits (const char *full)
744 {
745 char line[8192];
746 char *result = NULL;
747 FILE *f;
748
749 if (!full)
750 return NULL;
751
752 f = fopen (full, "r");
753 if (f)
754 {
755 while (fgets (line, sizeof (line), f))
756 {
757 if (!strncmp (line, "Inherits", 8))
758 {
759 char *l = line + 8;
760 char *r;
761 while (*l == ' ') l++;
762 if (*l != '=') continue;
763 l++;
764 while (*l == ' ') l++;
765 result = malloc (strlen (l) + 1);
766 if (result)
767 {
768 r = result;
769 while (*l)
770 {
771 while (XcursorSep(*l) || XcursorWhite (*l)) l++;
772 if (!*l)
773 break;
774 if (r != result)
775 *r++ = ':';
776 while (*l && !XcursorWhite(*l) &&
777 !XcursorSep(*l))
778 *r++ = *l++;
779 }
780 *r++ = '\0';
781 }
782 break;
783 }
784 }
785 fclose (f);
786 }
787 return result;
788 }
789
790 static FILE *
XcursorScanTheme(const char * theme,const char * name)791 XcursorScanTheme (const char *theme, const char *name)
792 {
793 FILE *f = NULL;
794 char *full;
795 char *dir;
796 const char *path;
797 char *inherits = NULL;
798 const char *i;
799
800 if (!theme || !name)
801 return NULL;
802
803 /*
804 * Scan this theme
805 */
806 for (path = XcursorLibraryPath ();
807 path && f == NULL;
808 path = _XcursorNextPath (path))
809 {
810 dir = _XcursorBuildThemeDir (path, theme);
811 if (dir)
812 {
813 full = _XcursorBuildFullname (dir, "cursors", name);
814 if (full)
815 {
816 f = fopen (full, "r");
817 free (full);
818 }
819 if (!f && !inherits)
820 {
821 full = _XcursorBuildFullname (dir, "", "index.theme");
822 if (full)
823 {
824 inherits = _XcursorThemeInherits (full);
825 free (full);
826 }
827 }
828 free (dir);
829 }
830 }
831 /*
832 * Recurse to scan inherited themes
833 */
834 for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
835 f = XcursorScanTheme (i, name);
836 if (inherits != NULL)
837 free (inherits);
838 return f;
839 }
840
841 XcursorImages *
XcursorLibraryLoadImages(const char * file,const char * theme,int size)842 XcursorLibraryLoadImages (const char *file, const char *theme, int size)
843 {
844 FILE *f = NULL;
845 XcursorImages *images = NULL;
846
847 if (!file)
848 return NULL;
849
850 if (theme)
851 f = XcursorScanTheme (theme, file);
852 if (!f)
853 f = XcursorScanTheme ("default", file);
854 if (f)
855 {
856 images = XcursorFileLoadImages (f, size);
857 if (images)
858 XcursorImagesSetName (images, file);
859 fclose (f);
860 }
861 return images;
862 }
863
864 static void
load_all_cursors_from_dir(const char * path,int size,void (* load_callback)(XcursorImages *,void *),void * user_data)865 load_all_cursors_from_dir(const char *path, int size,
866 void (*load_callback)(XcursorImages *, void *),
867 void *user_data)
868 {
869 FILE *f;
870 DIR *dir = opendir(path);
871 struct dirent *ent;
872 char *full;
873 XcursorImages *images;
874
875 if (!dir)
876 return;
877
878 for(ent = readdir(dir); ent; ent = readdir(dir)) {
879 #ifdef _DIRENT_HAVE_D_TYPE
880 if (ent->d_type != DT_UNKNOWN &&
881 (ent->d_type != DT_REG && ent->d_type != DT_LNK))
882 continue;
883 #endif
884
885 full = _XcursorBuildFullname(path, "", ent->d_name);
886 if (!full)
887 continue;
888
889 f = fopen(full, "r");
890 if (!f) {
891 free(full);
892 continue;
893 }
894
895 images = XcursorFileLoadImages(f, size);
896
897 if (images) {
898 XcursorImagesSetName(images, ent->d_name);
899 load_callback(images, user_data);
900 }
901
902 fclose (f);
903 free(full);
904 }
905
906 closedir(dir);
907 }
908
909 /** Load all the cursor of a theme
910 *
911 * This function loads all the cursor images of a given theme and its
912 * inherited themes. Each cursor is loaded into an XcursorImages object
913 * which is passed to the caller's load callback. If a cursor appears
914 * more than once across all the inherited themes, the load callback
915 * will be called multiple times, with possibly different XcursorImages
916 * object which have the same name. The user is expected to destroy the
917 * XcursorImages objects passed to the callback with
918 * XcursorImagesDestroy().
919 *
920 * \param theme The name of theme that should be loaded
921 * \param size The desired size of the cursor images
922 * \param load_callback A callback function that will be called
923 * for each cursor loaded. The first parameter is the XcursorImages
924 * object representing the loaded cursor and the second is a pointer
925 * to data provided by the user.
926 * \param user_data The data that should be passed to the load callback
927 */
928 void
xcursor_load_theme(const char * theme,int size,void (* load_callback)(XcursorImages *,void *),void * user_data)929 xcursor_load_theme(const char *theme, int size,
930 void (*load_callback)(XcursorImages *, void *),
931 void *user_data)
932 {
933 char *full, *dir;
934 char *inherits = NULL;
935 const char *path, *i;
936
937 if (!theme)
938 theme = "default";
939
940 for (path = XcursorLibraryPath();
941 path;
942 path = _XcursorNextPath(path)) {
943 dir = _XcursorBuildThemeDir(path, theme);
944 if (!dir)
945 continue;
946
947 full = _XcursorBuildFullname(dir, "cursors", "");
948
949 if (full) {
950 load_all_cursors_from_dir(full, size, load_callback,
951 user_data);
952 free(full);
953 }
954
955 if (!inherits) {
956 full = _XcursorBuildFullname(dir, "", "index.theme");
957 if (full) {
958 inherits = _XcursorThemeInherits(full);
959 free(full);
960 }
961 }
962
963 free(dir);
964 }
965
966 for (i = inherits; i; i = _XcursorNextPath(i))
967 xcursor_load_theme(i, size, load_callback, user_data);
968
969 if (inherits)
970 free(inherits);
971 }
972