1/*
2 * Copyright © 2024 Thomas E. Dickey
3 * Copyright © 2002 Keith Packard
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of Keith Packard not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission.  Keith Packard makes no
12 * representations about the suitability of this software for any purpose.  It
13 * is provided "as is" without express or implied warranty.
14 *
15 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23
24#include "xcursorint.h"
25#include <stdlib.h>
26#include <string.h>
27
28#ifndef ICONDIR
29#define ICONDIR "/usr/X11R6/lib/X11/icons"
30#endif
31
32#ifndef XCURSORPATH
33#define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"ICONDIR
34#endif
35
36typedef struct XcursorInherit {
37    char	*line;
38    const char	*theme;
39} XcursorInherit;
40
41const char *
42XcursorLibraryPath (void)
43{
44    static const char	*path;
45
46    if (!path)
47    {
48	path = getenv ("XCURSOR_PATH");
49	if (!path)
50	    path = XCURSORPATH;
51	traceOpts((T_OPTION(XCURSOR_PATH) ": %s\n", NonNull(path)));
52    }
53    return path;
54}
55
56static  void
57_XcursorAddPathElt (char *path, const char *elt, int len)
58{
59    size_t    pathlen = strlen (path);
60
61    /* append / if the path doesn't currently have one */
62    if (path[0] == '\0' || path[pathlen - 1] != '/')
63    {
64	strcat (path, "/");
65	pathlen++;
66    }
67    if (len == -1)
68	len = (int) strlen (elt);
69    /* strip leading slashes */
70    while (len && elt[0] == '/')
71    {
72	elt++;
73	len--;
74    }
75    strncpy (path + pathlen, elt, (size_t) len);
76    path[pathlen + (size_t) len] = '\0';
77}
78
79static char *
80_XcursorBuildThemeDir (const char *dir, const char *theme)
81{
82    const char	    *colon;
83    const char	    *tcolon;
84    char	    *full;
85    char	    *home;
86    int		    dirlen;
87    int		    homelen;
88    int		    themelen;
89    int		    len;
90
91    if (!dir || !theme)
92	return NULL;
93
94    colon = strchr (dir, ':');
95    if (!colon)
96	colon = dir + strlen (dir);
97
98    dirlen = (int) (colon - dir);
99
100    tcolon = strchr (theme, ':');
101    if (!tcolon)
102	tcolon = theme + strlen (theme);
103
104    themelen = (int) (tcolon - theme);
105
106    home = NULL;
107    homelen = 0;
108    if (*dir == '~')
109    {
110	home = getenv ("HOME");
111	if (!home)
112	    return NULL;
113	homelen = (int) strlen (home);
114	dir++;
115	dirlen--;
116    }
117
118    /*
119     * add space for any needed directory separators, one per component,
120     * and one for the trailing null
121     */
122    len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
123
124    full = malloc ((size_t)len);
125    if (!full)
126	return NULL;
127    full[0] = '\0';
128
129    if (home)
130	_XcursorAddPathElt (full, home, -1);
131    _XcursorAddPathElt (full, dir, dirlen);
132    _XcursorAddPathElt (full, theme, themelen);
133    return full;
134}
135
136static char *
137_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
138{
139    char    *full;
140
141    if (!dir || !subdir || !file)
142	return NULL;
143
144    full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
145    if (!full)
146	return NULL;
147    full[0] = '\0';
148    _XcursorAddPathElt (full, dir, -1);
149    _XcursorAddPathElt (full, subdir, -1);
150    _XcursorAddPathElt (full, file, -1);
151    return full;
152}
153
154static const char *
155_XcursorNextPath (const char *path)
156{
157    char    *colon = strchr (path, ':');
158
159    if (!colon)
160	return NULL;
161    return colon + 1;
162}
163
164/*
165 * _XcursorThemeInherits, XcursorWhite, & XcursorSep are copied in
166 * libxcb-cursor/cursor/load_cursor.c.  Please update that copy to
167 * include any changes made to the code for those here.
168 */
169
170#define XcursorWhite(c)	((c) == ' ' || (c) == '\t' || (c) == '\n')
171#define XcursorSep(c) ((c) == ';' || (c) == ',')
172
173static char *
174_XcursorThemeInherits (const char *full)
175{
176    char    line[8192];
177    char    *result = NULL;
178    FILE    *f;
179
180    if (!full)
181	return NULL;
182
183    f = fopen (full, "r" FOPEN_CLOEXEC);
184    if (f)
185    {
186	while (fgets (line, sizeof (line), f))
187	{
188	    if (!strncmp (line, "Inherits", 8))
189	    {
190		char    *l = line + 8;
191		while (*l == ' ') l++;
192		if (*l != '=') continue;
193		l++;
194		while (*l == ' ') l++;
195		result = malloc (strlen (l) + 1);
196		if (result)
197		{
198		    char *r = result;
199		    while (*l)
200		    {
201			while (XcursorSep(*l) || XcursorWhite (*l)) l++;
202			if (!*l)
203			    break;
204			if (r != result)
205			    *r++ = ':';
206			while (*l && !XcursorWhite(*l) &&
207			       !XcursorSep(*l))
208			    *r++ = *l++;
209		    }
210		    *r++ = '\0';
211		}
212		break;
213	    }
214	}
215	fclose (f);
216    }
217    return result;
218}
219
220#define XCURSOR_SCAN_CORE   ((FILE *) 1)
221#define MAX_INHERITS_DEPTH  32
222
223static FILE *
224XcursorScanTheme (const char *theme, const char *name)
225{
226    FILE		*f = NULL;
227    char		*full;
228    char		*dir;
229    const char		*path;
230    XcursorInherit	 inherits[MAX_INHERITS_DEPTH + 1];
231    int			 d;
232
233    if (!theme || !name)
234	return NULL;
235
236    /*
237     * XCURSOR_CORE_THEME is a magic name; cursors from the core set
238     * are never found in any directory.  Instead, a magic value is
239     * returned which truncates any search so that overlying functions
240     * can switch to equivalent core cursors
241     */
242    if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0)
243	return XCURSOR_SCAN_CORE;
244
245    memset (inherits, 0, sizeof (inherits));
246
247    d = 0;
248    inherits[d].theme = theme;
249
250    while (f == NULL && d >= 0 && inherits[d].theme != NULL)
251    {
252	/*
253	 * Scan this theme
254	 */
255	for (path = XcursorLibraryPath ();
256	     path && f == NULL;
257	     path = _XcursorNextPath (path))
258	{
259	    dir = _XcursorBuildThemeDir (path, inherits[d].theme);
260	    if (dir)
261	    {
262		full = _XcursorBuildFullname (dir, "cursors", name);
263		if (full)
264		{
265		    f = fopen (full, "r" FOPEN_CLOEXEC);
266		    free (full);
267		}
268		if (!f && inherits[d + 1].line == NULL)
269		{
270		    if (d + 1 >= MAX_INHERITS_DEPTH)
271		    {
272			free (dir);
273			goto finish;
274		    }
275		    full = _XcursorBuildFullname (dir, "", "index.theme");
276		    if (full)
277		    {
278			inherits[d + 1].line = _XcursorThemeInherits (full);
279			inherits[d + 1].theme = inherits[d + 1].line;
280			free (full);
281		    }
282		}
283		free (dir);
284	    }
285	}
286
287	d++;
288	while (d > 0 && inherits[d].theme == NULL)
289	{
290	    free (inherits[d].line);
291	    inherits[d].line = NULL;
292
293	    if (--d == 0)
294		inherits[d].theme = NULL;
295	    else
296		inherits[d].theme = _XcursorNextPath (inherits[d].theme);
297	}
298
299	/*
300	 * Detect and break self reference loop early on.
301	 */
302	if (inherits[d].theme != NULL && strcmp (inherits[d].theme, theme) == 0)
303	    break;
304    }
305
306finish:
307    for (d = 1; d <= MAX_INHERITS_DEPTH; d++)
308	free (inherits[d].line);
309
310    return f;
311}
312
313XcursorImage *
314XcursorLibraryLoadImage (const char *file, const char *theme, int size)
315{
316    FILE	    *f = NULL;
317    XcursorImage    *image = NULL;
318
319    enterFunc((T_CALLED(XcursorLibraryLoadImage) "(\"%s\",\"%s\", %d)\n",
320	      NonNull(file), NonNull(theme), size));
321
322    if (!file)
323	returnAddr(NULL);
324
325    if (theme)
326	f = XcursorScanTheme (theme, file);
327    if (!f)
328	f = XcursorScanTheme ("default", file);
329    if (f != NULL && f != XCURSOR_SCAN_CORE)
330    {
331	image = XcursorFileLoadImage (f, size);
332	fclose (f);
333    }
334    returnAddr(image);
335}
336
337static XcursorImages *
338_XcursorLibraryLoadImages (Display *dpy, const char *file)
339{
340    int		    size = XcursorGetDefaultSize (dpy);
341    char	    *theme = XcursorGetTheme (dpy);
342    XcursorBool     resized = XcursorGetResizable (dpy);
343    FILE	    *f = NULL;
344    XcursorImages   *images = NULL;
345
346    if (!file)
347	return NULL;
348
349    if (theme)
350	f = XcursorScanTheme (theme, file);
351    if (!f)
352	f = XcursorScanTheme ("default", file);
353    if (f != NULL && f != XCURSOR_SCAN_CORE)
354    {
355	images = _XcursorFileLoadImages (f, size, resized);
356	if (images)
357	    XcursorImagesSetName (images, file);
358	fclose (f);
359    }
360    return images;
361}
362
363XcursorImages *
364XcursorLibraryLoadImages (const char *file, const char *theme, int size)
365{
366    FILE	    *f = NULL;
367    XcursorImages   *images = NULL;
368
369    enterFunc((T_CALLED(XcursorLibraryLoadImages) "(\"%s\", \"%s\", %d)\n",
370	      NonNull(file), NonNull(theme), size));
371
372    if (!file)
373	returnAddr(NULL);
374
375    if (theme)
376	f = XcursorScanTheme (theme, file);
377    if (!f)
378	f = XcursorScanTheme ("default", file);
379    if (f != NULL && f != XCURSOR_SCAN_CORE)
380    {
381	images = XcursorFileLoadImages (f, size);
382	if (images)
383	    XcursorImagesSetName (images, file);
384	fclose (f);
385    }
386    returnAddr(images);
387}
388
389Cursor
390XcursorLibraryLoadCursor (Display *dpy, const char *file)
391{
392    XcursorImages   *images;
393    Cursor	    cursor = 0;
394
395    enterFunc((T_CALLED(XcursorLibraryLoadCursor) "(%p, \"%s\")\n",
396	      (void*)dpy, NonNull(file)));
397
398    if (!file)
399	returnLong(cursor);
400
401    images = _XcursorLibraryLoadImages (dpy, file);
402    if (!images)
403    {
404	int id = XcursorLibraryShape (file);
405
406	if (id >= 0)
407	    cursor = _XcursorCreateFontCursor (dpy, (unsigned) id);
408    }
409    else
410    {
411	cursor = XcursorImagesLoadCursor (dpy, images);
412	XcursorImagesDestroy (images);
413#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
414	XFixesSetCursorName (dpy, cursor, file);
415#endif
416    }
417    returnLong(cursor);
418}
419
420XcursorCursors *
421XcursorLibraryLoadCursors (Display *dpy, const char *file)
422{
423    XcursorImages   *images;
424    XcursorCursors  *cursors;
425
426    enterFunc((T_CALLED(XcursorLibraryLoadCursors) "(%p, \"%s\")\n",
427	      (void*)dpy, NonNull(file)));
428
429    if (!file)
430	returnAddr(NULL);
431
432    images = _XcursorLibraryLoadImages (dpy, file);
433    if (!images)
434    {
435	int id = XcursorLibraryShape (file);
436
437	if (id >= 0)
438	{
439	    cursors = XcursorCursorsCreate (dpy, 1);
440	    if (cursors)
441	    {
442		cursors->cursors[0] = _XcursorCreateFontCursor (dpy, (unsigned) id);
443		if (cursors->cursors[0] == None)
444		{
445		    XcursorCursorsDestroy (cursors);
446		    cursors = NULL;
447		}
448		else
449		    cursors->ncursor = 1;
450	    }
451	}
452	else
453	    cursors = NULL;
454    }
455    else
456    {
457	cursors = XcursorImagesLoadCursors (dpy, images);
458	XcursorImagesDestroy (images);
459    }
460    returnAddr(cursors);
461}
462
463static const char _XcursorStandardNames[] =
464	"X_cursor\0"
465	"arrow\0"
466	"based_arrow_down\0"
467	"based_arrow_up\0"
468	"boat\0"
469	"bogosity\0"
470	"bottom_left_corner\0"
471	"bottom_right_corner\0"
472	"bottom_side\0"
473	"bottom_tee\0"
474	"box_spiral\0"
475	"center_ptr\0"
476	"circle\0"
477	"clock\0"
478	"coffee_mug\0"
479	"cross\0"
480	"cross_reverse\0"
481	"crosshair\0"
482	"diamond_cross\0"
483	"dot\0"
484	"dotbox\0"
485	"double_arrow\0"
486	"draft_large\0"
487	"draft_small\0"
488	"draped_box\0"
489	"exchange\0"
490	"fleur\0"
491	"gobbler\0"
492	"gumby\0"
493	"hand1\0"
494	"hand2\0"
495	"heart\0"
496	"icon\0"
497	"iron_cross\0"
498	"left_ptr\0"
499	"left_side\0"
500	"left_tee\0"
501	"leftbutton\0"
502	"ll_angle\0"
503	"lr_angle\0"
504	"man\0"
505	"middlebutton\0"
506	"mouse\0"
507	"pencil\0"
508	"pirate\0"
509	"plus\0"
510	"question_arrow\0"
511	"right_ptr\0"
512	"right_side\0"
513	"right_tee\0"
514	"rightbutton\0"
515	"rtl_logo\0"
516	"sailboat\0"
517	"sb_down_arrow\0"
518	"sb_h_double_arrow\0"
519	"sb_left_arrow\0"
520	"sb_right_arrow\0"
521	"sb_up_arrow\0"
522	"sb_v_double_arrow\0"
523	"shuttle\0"
524	"sizing\0"
525	"spider\0"
526	"spraycan\0"
527	"star\0"
528	"target\0"
529	"tcross\0"
530	"top_left_arrow\0"
531	"top_left_corner\0"
532	"top_right_corner\0"
533	"top_side\0"
534	"top_tee\0"
535	"trek\0"
536	"ul_angle\0"
537	"umbrella\0"
538	"ur_angle\0"
539	"watch\0"
540	"xterm";
541
542static const unsigned short _XcursorStandardNameOffsets[] = {
543	0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158,
544	169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287,
545	295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392,
546	396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510,
547	524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651,
548	666, 682, 699, 708, 716, 721, 730, 739, 748, 754
549};
550
551#define NUM_STANDARD_NAMES  (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0])
552
553#define STANDARD_NAME(id) \
554    _XcursorStandardNames + _XcursorStandardNameOffsets[id]
555
556XcursorImage *
557XcursorShapeLoadImage (unsigned int shape, const char *theme, int size)
558{
559    unsigned int    id = shape >> 1;
560    XcursorImage   *result = NULL;
561
562    enterFunc((T_CALLED(XcursorShapeLoadImage) "(%u, \"%s\", %d)\n",
563	      shape, NonNull(theme), size));
564
565    if (id < NUM_STANDARD_NAMES)
566	result = XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size);
567
568    returnAddr(result);
569}
570
571XcursorImages *
572_XcursorShapeLoadImages (Display *dpy, unsigned int shape)
573{
574    unsigned int    id = shape >> 1;
575    XcursorImages  *result = NULL;
576
577    enterFunc((T_CALLED(_XcursorShapeLoadImages) "(%p, %u)\n",
578	      (void*)dpy, shape));
579
580    if (id < NUM_STANDARD_NAMES)
581	result = _XcursorLibraryLoadImages (dpy, STANDARD_NAME (id));
582
583    returnAddr(result);
584}
585
586XcursorImages *
587XcursorShapeLoadImages (unsigned int shape, const char *theme, int size)
588{
589    unsigned int    id = shape >> 1;
590    XcursorImages  *result = NULL;
591
592    enterFunc((T_CALLED(XcursorShapeLoadImages) "(%u, \"%s\", %d)\n",
593	      shape, NonNull(theme), size));
594
595    if (id < NUM_STANDARD_NAMES)
596	result = XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size);
597
598    returnAddr(result);
599}
600
601Cursor
602XcursorShapeLoadCursor (Display *dpy, unsigned int shape)
603{
604    unsigned int    id = shape >> 1;
605    Cursor          result = None;
606
607    enterFunc((T_CALLED(XcursorShapeLoadCursor) "(%p, %u)\n",
608	      (void*)dpy, shape));
609
610    if (id < NUM_STANDARD_NAMES)
611	result = XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id));
612
613    returnLong(result);
614}
615
616XcursorCursors *
617XcursorShapeLoadCursors (Display *dpy, unsigned int shape)
618{
619    unsigned int    id = shape >> 1;
620    XcursorCursors *result = NULL;
621
622    enterFunc((T_CALLED(XcursorShapeLoadCursors) "(%p, %u)\n",
623	      (void*)dpy, shape));
624
625    if (id < NUM_STANDARD_NAMES)
626	result = XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id));
627
628    returnAddr(result);
629}
630
631int
632XcursorLibraryShape (const char *library)
633{
634    int	low, high;
635
636    enterFunc((T_CALLED(XcursorLibraryShape) "(%s)\n", NonNull(library)));
637
638    low = 0;
639    high = NUM_STANDARD_NAMES - 1;
640    while (low < high - 1)
641    {
642	int mid = (low + high) >> 1;
643	int c = strcmp (library, STANDARD_NAME (mid));
644	if (c == 0)
645	    returnCode(mid << 1);
646	if (c > 0)
647	    low = mid;
648	else
649	    high = mid;
650    }
651    while (low <= high)
652    {
653	if (!strcmp (library, STANDARD_NAME (low)))
654	    return (low << 1);
655	low++;
656    }
657    returnCode(-1);
658}
659