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

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