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