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