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 #define _DEFAULT_SOURCE
27 #include <dirent.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include "xcursor/xcursor.h"
32
33 /*
34 * From libXcursor/include/X11/extensions/Xcursor.h
35 */
36
37 #define XcursorTrue 1
38 #define XcursorFalse 0
39
40 /*
41 * Cursor files start with a header. The header
42 * contains a magic number, a version number and a
43 * table of contents which has type and offset information
44 * for the remaining tables in the file.
45 *
46 * File minor versions increment for compatible changes
47 * File major versions increment for incompatible changes (never, we hope)
48 *
49 * Chunks of the same type are always upward compatible. Incompatible
50 * changes are made with new chunk types; the old data can remain under
51 * the old type. Upward compatible changes can add header data as the
52 * header lengths are specified in the file.
53 *
54 * File:
55 * FileHeader
56 * LISTofChunk
57 *
58 * FileHeader:
59 * CARD32 magic magic number
60 * CARD32 header bytes in file header
61 * CARD32 version file version
62 * CARD32 ntoc number of toc entries
63 * LISTofFileToc toc table of contents
64 *
65 * FileToc:
66 * CARD32 type entry type
67 * CARD32 subtype entry subtype (size for images)
68 * CARD32 position absolute file position
69 */
70
71 #define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */
72
73 /*
74 * Current Xcursor version number. Will be substituted by configure
75 * from the version in the libXcursor configure.ac file.
76 */
77
78 #define XCURSOR_LIB_MAJOR 1
79 #define XCURSOR_LIB_MINOR 1
80 #define XCURSOR_LIB_REVISION 13
81 #define XCURSOR_LIB_VERSION ((XCURSOR_LIB_MAJOR * 10000) + \
82 (XCURSOR_LIB_MINOR * 100) + \
83 (XCURSOR_LIB_REVISION))
84
85 /*
86 * This version number is stored in cursor files; changes to the
87 * file format require updating this version number
88 */
89 #define XCURSOR_FILE_MAJOR 1
90 #define XCURSOR_FILE_MINOR 0
91 #define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR))
92 #define XCURSOR_FILE_HEADER_LEN (4 * 4)
93 #define XCURSOR_FILE_TOC_LEN (3 * 4)
94
95 typedef struct _XcursorFileToc {
96 XcursorUInt type; /* chunk type */
97 XcursorUInt subtype; /* subtype (size for images) */
98 XcursorUInt position; /* absolute position in file */
99 } XcursorFileToc;
100
101 typedef struct _XcursorFileHeader {
102 XcursorUInt magic; /* magic number */
103 XcursorUInt header; /* byte length of header */
104 XcursorUInt version; /* file version number */
105 XcursorUInt ntoc; /* number of toc entries */
106 XcursorFileToc *tocs; /* table of contents */
107 } XcursorFileHeader;
108
109 /*
110 * The rest of the file is a list of chunks, each tagged by type
111 * and version.
112 *
113 * Chunk:
114 * ChunkHeader
115 * <extra type-specific header fields>
116 * <type-specific data>
117 *
118 * ChunkHeader:
119 * CARD32 header bytes in chunk header + type header
120 * CARD32 type chunk type
121 * CARD32 subtype chunk subtype
122 * CARD32 version chunk type version
123 */
124
125 #define XCURSOR_CHUNK_HEADER_LEN (4 * 4)
126
127 typedef struct _XcursorChunkHeader {
128 XcursorUInt header; /* bytes in chunk header */
129 XcursorUInt type; /* chunk type */
130 XcursorUInt subtype; /* chunk subtype (size for images) */
131 XcursorUInt version; /* version of this type */
132 } XcursorChunkHeader;
133
134 /*
135 * Here's a list of the known chunk types
136 */
137
138 /*
139 * Comments consist of a 4-byte length field followed by
140 * UTF-8 encoded text
141 *
142 * Comment:
143 * ChunkHeader header chunk header
144 * CARD32 length bytes in text
145 * LISTofCARD8 text UTF-8 encoded text
146 */
147
148 #define XCURSOR_COMMENT_TYPE 0xfffe0001
149 #define XCURSOR_COMMENT_VERSION 1
150 #define XCURSOR_COMMENT_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (1 *4))
151 #define XCURSOR_COMMENT_COPYRIGHT 1
152 #define XCURSOR_COMMENT_LICENSE 2
153 #define XCURSOR_COMMENT_OTHER 3
154 #define XCURSOR_COMMENT_MAX_LEN 0x100000
155
156 typedef struct _XcursorComment {
157 XcursorUInt version;
158 XcursorUInt comment_type;
159 char *comment;
160 } XcursorComment;
161
162 /*
163 * Each cursor image occupies a separate image chunk.
164 * The length of the image header follows the chunk header
165 * so that future versions can extend the header without
166 * breaking older applications
167 *
168 * Image:
169 * ChunkHeader header chunk header
170 * CARD32 width actual width
171 * CARD32 height actual height
172 * CARD32 xhot hot spot x
173 * CARD32 yhot hot spot y
174 * CARD32 delay animation delay
175 * LISTofCARD32 pixels ARGB pixels
176 */
177
178 #define XCURSOR_IMAGE_TYPE 0xfffd0002
179 #define XCURSOR_IMAGE_VERSION 1
180 #define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4))
181 #define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */
182
183 typedef struct _XcursorFile XcursorFile;
184
185 struct _XcursorFile {
186 void *closure;
187 int (*read) (XcursorFile *file, unsigned char *buf, int len);
188 int (*write) (XcursorFile *file, unsigned char *buf, int len);
189 int (*seek) (XcursorFile *file, long offset, int whence);
190 };
191
192 typedef struct _XcursorComments {
193 int ncomment; /* number of comments */
194 XcursorComment **comments; /* array of XcursorComment pointers */
195 } XcursorComments;
196
197 /*
198 * From libXcursor/src/file.c
199 */
200
201 static XcursorImage *
XcursorImageCreate(int width,int height)202 XcursorImageCreate (int width, int height)
203 {
204 XcursorImage *image;
205
206 if (width < 0 || height < 0)
207 return NULL;
208 if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE)
209 return NULL;
210
211 image = malloc (sizeof (XcursorImage) +
212 width * height * sizeof (XcursorPixel));
213 if (!image)
214 return NULL;
215 image->version = XCURSOR_IMAGE_VERSION;
216 image->pixels = (XcursorPixel *) (image + 1);
217 image->size = width > height ? width : height;
218 image->width = width;
219 image->height = height;
220 image->delay = 0;
221 return image;
222 }
223
224 static void
XcursorImageDestroy(XcursorImage * image)225 XcursorImageDestroy (XcursorImage *image)
226 {
227 free (image);
228 }
229
230 static XcursorImages *
XcursorImagesCreate(int size)231 XcursorImagesCreate (int size)
232 {
233 XcursorImages *images;
234
235 images = malloc (sizeof (XcursorImages) +
236 size * sizeof (XcursorImage *));
237 if (!images)
238 return NULL;
239 images->nimage = 0;
240 images->images = (XcursorImage **) (images + 1);
241 images->name = NULL;
242 return images;
243 }
244
245 void
XcursorImagesDestroy(XcursorImages * images)246 XcursorImagesDestroy (XcursorImages *images)
247 {
248 int n;
249
250 if (!images)
251 return;
252
253 for (n = 0; n < images->nimage; n++)
254 XcursorImageDestroy (images->images[n]);
255 if (images->name)
256 free (images->name);
257 free (images);
258 }
259
260 static void
XcursorImagesSetName(XcursorImages * images,const char * name)261 XcursorImagesSetName (XcursorImages *images, const char *name)
262 {
263 char *new;
264
265 if (!images || !name)
266 return;
267
268 new = malloc (strlen (name) + 1);
269
270 if (!new)
271 return;
272
273 strcpy (new, name);
274 if (images->name)
275 free (images->name);
276 images->name = new;
277 }
278
279 static XcursorBool
_XcursorReadUInt(XcursorFile * file,XcursorUInt * u)280 _XcursorReadUInt (XcursorFile *file, XcursorUInt *u)
281 {
282 unsigned char bytes[4];
283
284 if (!file || !u)
285 return XcursorFalse;
286
287 if ((*file->read) (file, bytes, 4) != 4)
288 return XcursorFalse;
289
290 *u = ((XcursorUInt)(bytes[0]) << 0) |
291 ((XcursorUInt)(bytes[1]) << 8) |
292 ((XcursorUInt)(bytes[2]) << 16) |
293 ((XcursorUInt)(bytes[3]) << 24);
294 return XcursorTrue;
295 }
296
297 static void
_XcursorFileHeaderDestroy(XcursorFileHeader * fileHeader)298 _XcursorFileHeaderDestroy (XcursorFileHeader *fileHeader)
299 {
300 free (fileHeader);
301 }
302
303 static XcursorFileHeader *
_XcursorFileHeaderCreate(XcursorUInt ntoc)304 _XcursorFileHeaderCreate (XcursorUInt ntoc)
305 {
306 XcursorFileHeader *fileHeader;
307
308 if (ntoc > 0x10000)
309 return NULL;
310 fileHeader = malloc (sizeof (XcursorFileHeader) +
311 ntoc * sizeof (XcursorFileToc));
312 if (!fileHeader)
313 return NULL;
314 fileHeader->magic = XCURSOR_MAGIC;
315 fileHeader->header = XCURSOR_FILE_HEADER_LEN;
316 fileHeader->version = XCURSOR_FILE_VERSION;
317 fileHeader->ntoc = ntoc;
318 fileHeader->tocs = (XcursorFileToc *) (fileHeader + 1);
319 return fileHeader;
320 }
321
322 static XcursorFileHeader *
_XcursorReadFileHeader(XcursorFile * file)323 _XcursorReadFileHeader (XcursorFile *file)
324 {
325 XcursorFileHeader head, *fileHeader;
326 XcursorUInt skip;
327 unsigned int n;
328
329 if (!file)
330 return NULL;
331
332 if (!_XcursorReadUInt (file, &head.magic))
333 return NULL;
334 if (head.magic != XCURSOR_MAGIC)
335 return NULL;
336 if (!_XcursorReadUInt (file, &head.header))
337 return NULL;
338 if (!_XcursorReadUInt (file, &head.version))
339 return NULL;
340 if (!_XcursorReadUInt (file, &head.ntoc))
341 return NULL;
342 skip = head.header - XCURSOR_FILE_HEADER_LEN;
343 if (skip)
344 if ((*file->seek) (file, skip, SEEK_CUR) == EOF)
345 return NULL;
346 fileHeader = _XcursorFileHeaderCreate (head.ntoc);
347 if (!fileHeader)
348 return NULL;
349 fileHeader->magic = head.magic;
350 fileHeader->header = head.header;
351 fileHeader->version = head.version;
352 fileHeader->ntoc = head.ntoc;
353 for (n = 0; n < fileHeader->ntoc; n++)
354 {
355 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].type))
356 break;
357 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].subtype))
358 break;
359 if (!_XcursorReadUInt (file, &fileHeader->tocs[n].position))
360 break;
361 }
362 if (n != fileHeader->ntoc)
363 {
364 _XcursorFileHeaderDestroy (fileHeader);
365 return NULL;
366 }
367 return fileHeader;
368 }
369
370 static XcursorBool
_XcursorSeekToToc(XcursorFile * file,XcursorFileHeader * fileHeader,int toc)371 _XcursorSeekToToc (XcursorFile *file,
372 XcursorFileHeader *fileHeader,
373 int toc)
374 {
375 if (!file || !fileHeader || \
376 (*file->seek) (file, fileHeader->tocs[toc].position, SEEK_SET) == EOF)
377 return XcursorFalse;
378 return XcursorTrue;
379 }
380
381 static XcursorBool
_XcursorFileReadChunkHeader(XcursorFile * file,XcursorFileHeader * fileHeader,int toc,XcursorChunkHeader * chunkHeader)382 _XcursorFileReadChunkHeader (XcursorFile *file,
383 XcursorFileHeader *fileHeader,
384 int toc,
385 XcursorChunkHeader *chunkHeader)
386 {
387 if (!file || !fileHeader || !chunkHeader)
388 return XcursorFalse;
389 if (!_XcursorSeekToToc (file, fileHeader, toc))
390 return XcursorFalse;
391 if (!_XcursorReadUInt (file, &chunkHeader->header))
392 return XcursorFalse;
393 if (!_XcursorReadUInt (file, &chunkHeader->type))
394 return XcursorFalse;
395 if (!_XcursorReadUInt (file, &chunkHeader->subtype))
396 return XcursorFalse;
397 if (!_XcursorReadUInt (file, &chunkHeader->version))
398 return XcursorFalse;
399 /* sanity check */
400 if (chunkHeader->type != fileHeader->tocs[toc].type ||
401 chunkHeader->subtype != fileHeader->tocs[toc].subtype)
402 return XcursorFalse;
403 return XcursorTrue;
404 }
405
406 #define dist(a,b) ((a) > (b) ? (a) - (b) : (b) - (a))
407
408 static XcursorDim
_XcursorFindBestSize(XcursorFileHeader * fileHeader,XcursorDim size,int * nsizesp)409 _XcursorFindBestSize (XcursorFileHeader *fileHeader,
410 XcursorDim size,
411 int *nsizesp)
412 {
413 unsigned int n;
414 int nsizes = 0;
415 XcursorDim bestSize = 0;
416 XcursorDim thisSize;
417
418 if (!fileHeader || !nsizesp)
419 return 0;
420
421 for (n = 0; n < fileHeader->ntoc; n++)
422 {
423 if (fileHeader->tocs[n].type != XCURSOR_IMAGE_TYPE)
424 continue;
425 thisSize = fileHeader->tocs[n].subtype;
426 if (!bestSize || dist (thisSize, size) < dist (bestSize, size))
427 {
428 bestSize = thisSize;
429 nsizes = 1;
430 }
431 else if (thisSize == bestSize)
432 nsizes++;
433 }
434 *nsizesp = nsizes;
435 return bestSize;
436 }
437
438 static int
_XcursorFindImageToc(XcursorFileHeader * fileHeader,XcursorDim size,int count)439 _XcursorFindImageToc (XcursorFileHeader *fileHeader,
440 XcursorDim size,
441 int count)
442 {
443 unsigned int toc;
444 XcursorDim thisSize;
445
446 if (!fileHeader)
447 return 0;
448
449 for (toc = 0; toc < fileHeader->ntoc; toc++)
450 {
451 if (fileHeader->tocs[toc].type != XCURSOR_IMAGE_TYPE)
452 continue;
453 thisSize = fileHeader->tocs[toc].subtype;
454 if (thisSize != size)
455 continue;
456 if (!count)
457 break;
458 count--;
459 }
460 if (toc == fileHeader->ntoc)
461 return -1;
462 return toc;
463 }
464
465 static XcursorImage *
_XcursorReadImage(XcursorFile * file,XcursorFileHeader * fileHeader,int toc)466 _XcursorReadImage (XcursorFile *file,
467 XcursorFileHeader *fileHeader,
468 int toc)
469 {
470 XcursorChunkHeader chunkHeader;
471 XcursorImage head;
472 XcursorImage *image;
473 int n;
474 XcursorPixel *p;
475
476 if (!file || !fileHeader)
477 return NULL;
478
479 if (!_XcursorFileReadChunkHeader (file, fileHeader, toc, &chunkHeader))
480 return NULL;
481 if (!_XcursorReadUInt (file, &head.width))
482 return NULL;
483 if (!_XcursorReadUInt (file, &head.height))
484 return NULL;
485 if (!_XcursorReadUInt (file, &head.xhot))
486 return NULL;
487 if (!_XcursorReadUInt (file, &head.yhot))
488 return NULL;
489 if (!_XcursorReadUInt (file, &head.delay))
490 return NULL;
491 /* sanity check data */
492 if (head.width > XCURSOR_IMAGE_MAX_SIZE ||
493 head.height > XCURSOR_IMAGE_MAX_SIZE)
494 return NULL;
495 if (head.width == 0 || head.height == 0)
496 return NULL;
497 if (head.xhot > head.width || head.yhot > head.height)
498 return NULL;
499
500 /* Create the image and initialize it */
501 image = XcursorImageCreate (head.width, head.height);
502 if (image == NULL)
503 return NULL;
504 if (chunkHeader.version < image->version)
505 image->version = chunkHeader.version;
506 image->size = chunkHeader.subtype;
507 image->xhot = head.xhot;
508 image->yhot = head.yhot;
509 image->delay = head.delay;
510 n = image->width * image->height;
511 p = image->pixels;
512 while (n--)
513 {
514 if (!_XcursorReadUInt (file, p))
515 {
516 XcursorImageDestroy (image);
517 return NULL;
518 }
519 p++;
520 }
521 return image;
522 }
523
524 static XcursorImages *
XcursorXcFileLoadImages(XcursorFile * file,int size)525 XcursorXcFileLoadImages (XcursorFile *file, int size)
526 {
527 XcursorFileHeader *fileHeader;
528 XcursorDim bestSize;
529 int nsize;
530 XcursorImages *images;
531 int n;
532 int toc;
533
534 if (!file || size < 0)
535 return NULL;
536 fileHeader = _XcursorReadFileHeader (file);
537 if (!fileHeader)
538 return NULL;
539 bestSize = _XcursorFindBestSize (fileHeader, (XcursorDim) size, &nsize);
540 if (!bestSize)
541 {
542 _XcursorFileHeaderDestroy (fileHeader);
543 return NULL;
544 }
545 images = XcursorImagesCreate (nsize);
546 if (!images)
547 {
548 _XcursorFileHeaderDestroy (fileHeader);
549 return NULL;
550 }
551 for (n = 0; n < nsize; n++)
552 {
553 toc = _XcursorFindImageToc (fileHeader, bestSize, n);
554 if (toc < 0)
555 break;
556 images->images[images->nimage] = _XcursorReadImage (file, fileHeader,
557 toc);
558 if (!images->images[images->nimage])
559 break;
560 images->nimage++;
561 }
562 _XcursorFileHeaderDestroy (fileHeader);
563 if (images->nimage != nsize)
564 {
565 XcursorImagesDestroy (images);
566 images = NULL;
567 }
568 return images;
569 }
570
571 static int
_XcursorStdioFileRead(XcursorFile * file,unsigned char * buf,int len)572 _XcursorStdioFileRead (XcursorFile *file, unsigned char *buf, int len)
573 {
574 FILE *f = file->closure;
575 return fread (buf, 1, len, f);
576 }
577
578 static int
_XcursorStdioFileWrite(XcursorFile * file,unsigned char * buf,int len)579 _XcursorStdioFileWrite (XcursorFile *file, unsigned char *buf, int len)
580 {
581 FILE *f = file->closure;
582 return fwrite (buf, 1, len, f);
583 }
584
585 static int
_XcursorStdioFileSeek(XcursorFile * file,long offset,int whence)586 _XcursorStdioFileSeek (XcursorFile *file, long offset, int whence)
587 {
588 FILE *f = file->closure;
589 return fseek (f, offset, whence);
590 }
591
592 static void
_XcursorStdioFileInitialize(FILE * stdfile,XcursorFile * file)593 _XcursorStdioFileInitialize (FILE *stdfile, XcursorFile *file)
594 {
595 file->closure = stdfile;
596 file->read = _XcursorStdioFileRead;
597 file->write = _XcursorStdioFileWrite;
598 file->seek = _XcursorStdioFileSeek;
599 }
600
601 static XcursorImages *
XcursorFileLoadImages(FILE * file,int size)602 XcursorFileLoadImages (FILE *file, int size)
603 {
604 XcursorFile f;
605
606 if (!file)
607 return NULL;
608
609 _XcursorStdioFileInitialize (file, &f);
610 return XcursorXcFileLoadImages (&f, size);
611 }
612
613 /*
614 * From libXcursor/src/library.c
615 */
616
617 #ifndef ICONDIR
618 #define ICONDIR "/usr/X11R6/lib/X11/icons"
619 #endif
620
621 #ifndef XCURSORPATH
622 #define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR
623 #endif
624
625 static const char *
XcursorLibraryPath(void)626 XcursorLibraryPath (void)
627 {
628 static const char *path;
629
630 if (!path)
631 {
632 path = getenv ("XCURSOR_PATH");
633 if (!path)
634 path = XCURSORPATH;
635 }
636 return path;
637 }
638
639 static void
_XcursorAddPathElt(char * path,const char * elt,int len)640 _XcursorAddPathElt (char *path, const char *elt, int len)
641 {
642 int pathlen = strlen (path);
643
644 /* append / if the path doesn't currently have one */
645 if (path[0] == '\0' || path[pathlen - 1] != '/')
646 {
647 strcat (path, "/");
648 pathlen++;
649 }
650 if (len == -1)
651 len = strlen (elt);
652 /* strip leading slashes */
653 while (len && elt[0] == '/')
654 {
655 elt++;
656 len--;
657 }
658 strncpy (path + pathlen, elt, len);
659 path[pathlen + len] = '\0';
660 }
661
662 static char *
_XcursorBuildThemeDir(const char * dir,const char * theme)663 _XcursorBuildThemeDir (const char *dir, const char *theme)
664 {
665 const char *colon;
666 const char *tcolon;
667 char *full;
668 char *home;
669 int dirlen;
670 int homelen;
671 int themelen;
672 int len;
673
674 if (!dir || !theme)
675 return NULL;
676
677 colon = strchr (dir, ':');
678 if (!colon)
679 colon = dir + strlen (dir);
680
681 dirlen = colon - dir;
682
683 tcolon = strchr (theme, ':');
684 if (!tcolon)
685 tcolon = theme + strlen (theme);
686
687 themelen = tcolon - theme;
688
689 home = NULL;
690 homelen = 0;
691 if (*dir == '~')
692 {
693 home = getenv ("HOME");
694 if (!home)
695 return NULL;
696 homelen = strlen (home);
697 dir++;
698 dirlen--;
699 }
700
701 /*
702 * add space for any needed directory separators, one per component,
703 * and one for the trailing null
704 */
705 len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
706
707 full = malloc (len);
708 if (!full)
709 return NULL;
710 full[0] = '\0';
711
712 if (home)
713 _XcursorAddPathElt (full, home, -1);
714 _XcursorAddPathElt (full, dir, dirlen);
715 _XcursorAddPathElt (full, theme, themelen);
716 return full;
717 }
718
719 static char *
_XcursorBuildFullname(const char * dir,const char * subdir,const char * file)720 _XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
721 {
722 char *full;
723
724 if (!dir || !subdir || !file)
725 return NULL;
726
727 full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
728 if (!full)
729 return NULL;
730 full[0] = '\0';
731 _XcursorAddPathElt (full, dir, -1);
732 _XcursorAddPathElt (full, subdir, -1);
733 _XcursorAddPathElt (full, file, -1);
734 return full;
735 }
736
737 static const char *
_XcursorNextPath(const char * path)738 _XcursorNextPath (const char *path)
739 {
740 char *colon = strchr (path, ':');
741
742 if (!colon)
743 return NULL;
744 return colon + 1;
745 }
746
747 #define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
748 #define XcursorSep(c) ((c) == ';' || (c) == ',')
749
750 static char *
_XcursorThemeInherits(const char * full)751 _XcursorThemeInherits (const char *full)
752 {
753 char line[8192];
754 char *result = NULL;
755 FILE *f;
756
757 if (!full)
758 return NULL;
759
760 f = fopen (full, "r");
761 if (f)
762 {
763 while (fgets (line, sizeof (line), f))
764 {
765 if (!strncmp (line, "Inherits", 8))
766 {
767 char *l = line + 8;
768 char *r;
769 while (*l == ' ') l++;
770 if (*l != '=') continue;
771 l++;
772 while (*l == ' ') l++;
773 result = malloc (strlen (l) + 1);
774 if (result)
775 {
776 r = result;
777 while (*l)
778 {
779 while (XcursorSep(*l) || XcursorWhite (*l)) l++;
780 if (!*l)
781 break;
782 if (r != result)
783 *r++ = ':';
784 while (*l && !XcursorWhite(*l) &&
785 !XcursorSep(*l))
786 *r++ = *l++;
787 }
788 *r++ = '\0';
789 }
790 break;
791 }
792 }
793 fclose (f);
794 }
795 return result;
796 }
797
798 static FILE *
XcursorScanTheme(const char * theme,const char * name)799 XcursorScanTheme (const char *theme, const char *name)
800 {
801 FILE *f = NULL;
802 char *full;
803 char *dir;
804 const char *path;
805 char *inherits = NULL;
806 const char *i;
807
808 if (!theme || !name)
809 return NULL;
810
811 /*
812 * Scan this theme
813 */
814 for (path = XcursorLibraryPath ();
815 path && f == NULL;
816 path = _XcursorNextPath (path))
817 {
818 dir = _XcursorBuildThemeDir (path, theme);
819 if (dir)
820 {
821 full = _XcursorBuildFullname (dir, "cursors", name);
822 if (full)
823 {
824 f = fopen (full, "r");
825 free (full);
826 }
827 if (!f && !inherits)
828 {
829 full = _XcursorBuildFullname (dir, "", "index.theme");
830 if (full)
831 {
832 inherits = _XcursorThemeInherits (full);
833 free (full);
834 }
835 }
836 free (dir);
837 }
838 }
839 /*
840 * Recurse to scan inherited themes
841 */
842 for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
843 {
844 if (strcmp(i, theme) != 0)
845 f = XcursorScanTheme (i, name);
846 else
847 printf("Not calling XcursorScanTheme because of circular dependency: %s. %s", i, name);
848 }
849 if (inherits != NULL)
850 free (inherits);
851 return f;
852 }
853
854 XcursorImages *
XcursorLibraryLoadImages(const char * file,const char * theme,int size)855 XcursorLibraryLoadImages (const char *file, const char *theme, int size)
856 {
857 FILE *f = NULL;
858 XcursorImages *images = NULL;
859
860 if (!file)
861 return NULL;
862
863 if (theme)
864 f = XcursorScanTheme (theme, file);
865 if (!f)
866 f = XcursorScanTheme ("default", file);
867 if (f)
868 {
869 images = XcursorFileLoadImages (f, size);
870 if (images)
871 XcursorImagesSetName (images, file);
872 fclose (f);
873 }
874 return images;
875 }
876
877 static void
load_all_cursors_from_dir(const char * path,int size,void (* load_callback)(XcursorImages *,void *),void * user_data)878 load_all_cursors_from_dir(const char *path, int size,
879 void (*load_callback)(XcursorImages *, void *),
880 void *user_data)
881 {
882 FILE *f;
883 DIR *dir = opendir(path);
884 struct dirent *ent;
885 char *full;
886 XcursorImages *images;
887
888 if (!dir)
889 return;
890
891 for(ent = readdir(dir); ent; ent = readdir(dir)) {
892 #ifdef _DIRENT_HAVE_D_TYPE
893 if (ent->d_type != DT_UNKNOWN &&
894 (ent->d_type != DT_REG && ent->d_type != DT_LNK))
895 continue;
896 #endif
897
898 full = _XcursorBuildFullname(path, "", ent->d_name);
899 if (!full)
900 continue;
901
902 f = fopen(full, "r");
903 if (!f) {
904 free(full);
905 continue;
906 }
907
908 images = XcursorFileLoadImages(f, size);
909
910 if (images) {
911 XcursorImagesSetName(images, ent->d_name);
912 load_callback(images, user_data);
913 }
914
915 fclose (f);
916 free(full);
917 }
918
919 closedir(dir);
920 }
921
922 /** Load all the cursor of a theme
923 *
924 * This function loads all the cursor images of a given theme and its
925 * inherited themes. Each cursor is loaded into an XcursorImages object
926 * which is passed to the caller's load callback. If a cursor appears
927 * more than once across all the inherited themes, the load callback
928 * will be called multiple times, with possibly different XcursorImages
929 * object which have the same name. The user is expected to destroy the
930 * XcursorImages objects passed to the callback with
931 * XcursorImagesDestroy().
932 *
933 * \param theme The name of theme that should be loaded
934 * \param size The desired size of the cursor images
935 * \param load_callback A callback function that will be called
936 * for each cursor loaded. The first parameter is the XcursorImages
937 * object representing the loaded cursor and the second is a pointer
938 * to data provided by the user.
939 * \param user_data The data that should be passed to the load callback
940 */
941 void
xcursor_load_theme(const char * theme,int size,void (* load_callback)(XcursorImages *,void *),void * user_data)942 xcursor_load_theme(const char *theme, int size,
943 void (*load_callback)(XcursorImages *, void *),
944 void *user_data)
945 {
946 char *full, *dir;
947 char *inherits = NULL;
948 const char *path, *i;
949
950 if (!theme)
951 theme = "default";
952
953 for (path = XcursorLibraryPath();
954 path;
955 path = _XcursorNextPath(path)) {
956 dir = _XcursorBuildThemeDir(path, theme);
957 if (!dir)
958 continue;
959
960 full = _XcursorBuildFullname(dir, "cursors", "");
961
962 if (full) {
963 load_all_cursors_from_dir(full, size, load_callback,
964 user_data);
965 free(full);
966 }
967
968 if (!inherits) {
969 full = _XcursorBuildFullname(dir, "", "index.theme");
970 if (full) {
971 inherits = _XcursorThemeInherits(full);
972 free(full);
973 }
974 }
975
976 free(dir);
977 }
978
979 for (i = inherits; i; i = _XcursorNextPath(i))
980 xcursor_load_theme(i, size, load_callback, user_data);
981
982 if (inherits)
983 free(inherits);
984 }
985