library.c revision 4d939ec7
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#define XcursorWhite(c)	((c) == ' ' || (c) == '\t' || (c) == '\n')
163#define XcursorSep(c) ((c) == ';' || (c) == ',')
164
165static char *
166_XcursorThemeInherits (const char *full)
167{
168    char    line[8192];
169    char    *result = NULL;
170    FILE    *f;
171
172    if (!full)
173	return NULL;
174
175    f = fopen (full, "r");
176    if (f)
177    {
178	while (fgets (line, sizeof (line), f))
179	{
180	    if (!strncmp (line, "Inherits", 8))
181	    {
182		char    *l = line + 8;
183		while (*l == ' ') l++;
184		if (*l != '=') continue;
185		l++;
186		while (*l == ' ') l++;
187		result = malloc (strlen (l) + 1);
188		if (result)
189		{
190		    char *r = result;
191		    while (*l)
192		    {
193			while (XcursorSep(*l) || XcursorWhite (*l)) l++;
194			if (!*l)
195			    break;
196			if (r != result)
197			    *r++ = ':';
198			while (*l && !XcursorWhite(*l) &&
199			       !XcursorSep(*l))
200			    *r++ = *l++;
201		    }
202		    *r++ = '\0';
203		}
204		break;
205	    }
206	}
207	fclose (f);
208    }
209    return result;
210}
211
212#define XCURSOR_SCAN_CORE   ((FILE *) 1)
213#define MAX_INHERITS_DEPTH  32
214
215static FILE *
216XcursorScanTheme (const char *theme, const char *name)
217{
218    FILE		*f = NULL;
219    char		*full;
220    char		*dir;
221    const char		*path;
222    XcursorInherit	 inherits[MAX_INHERITS_DEPTH + 1];
223    int			 d;
224
225    if (!theme || !name)
226	return NULL;
227
228    /*
229     * XCURSOR_CORE_THEME is a magic name; cursors from the core set
230     * are never found in any directory.  Instead, a magic value is
231     * returned which truncates any search so that overlying functions
232     * can switch to equivalent core cursors
233     */
234    if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0)
235	return XCURSOR_SCAN_CORE;
236
237    memset (inherits, 0, sizeof (inherits));
238
239    d = 0;
240    inherits[d].theme = theme;
241
242    while (f == NULL && d >= 0 && inherits[d].theme != NULL)
243    {
244	/*
245	 * Scan this theme
246	 */
247	for (path = XcursorLibraryPath ();
248	     path && f == NULL;
249	     path = _XcursorNextPath (path))
250	{
251	    dir = _XcursorBuildThemeDir (path, inherits[d].theme);
252	    if (dir)
253	    {
254		full = _XcursorBuildFullname (dir, "cursors", name);
255		if (full)
256		{
257		    f = fopen (full, "r");
258		    free (full);
259		}
260		if (!f && inherits[d + 1].line == NULL)
261		{
262		    if (d + 1 >= MAX_INHERITS_DEPTH)
263		    {
264			free (dir);
265			goto finish;
266		    }
267		    full = _XcursorBuildFullname (dir, "", "index.theme");
268		    if (full)
269		    {
270			inherits[d + 1].line = _XcursorThemeInherits (full);
271			inherits[d + 1].theme = inherits[d + 1].line;
272			free (full);
273		    }
274		}
275		free (dir);
276	    }
277	}
278
279	d++;
280	while (d > 0 && inherits[d].theme == NULL)
281	{
282	    free (inherits[d].line);
283	    inherits[d].line = NULL;
284
285	    if (--d == 0)
286		inherits[d].theme = NULL;
287	    else
288		inherits[d].theme = _XcursorNextPath (inherits[d].theme);
289	}
290
291	/*
292	 * Detect and break self reference loop early on.
293	 */
294	if (inherits[d].theme != NULL && strcmp (inherits[d].theme, theme) == 0)
295	    break;
296    }
297
298finish:
299    for (d = 1; d <= MAX_INHERITS_DEPTH; d++)
300	free (inherits[d].line);
301
302    return f;
303}
304
305XcursorImage *
306XcursorLibraryLoadImage (const char *file, const char *theme, int size)
307{
308    FILE	    *f = NULL;
309    XcursorImage    *image = NULL;
310
311    if (!file)
312	return NULL;
313
314    if (theme)
315	f = XcursorScanTheme (theme, file);
316    if (!f)
317	f = XcursorScanTheme ("default", file);
318    if (f != NULL && f != XCURSOR_SCAN_CORE)
319    {
320	image = XcursorFileLoadImage (f, size);
321	fclose (f);
322    }
323    return image;
324}
325
326XcursorImages *
327XcursorLibraryLoadImages (const char *file, const char *theme, int size)
328{
329    FILE	    *f = NULL;
330    XcursorImages   *images = NULL;
331
332    if (!file)
333	return NULL;
334
335    if (theme)
336	f = XcursorScanTheme (theme, file);
337    if (!f)
338	f = XcursorScanTheme ("default", file);
339    if (f != NULL && f != XCURSOR_SCAN_CORE)
340    {
341	images = XcursorFileLoadImages (f, size);
342	if (images)
343	    XcursorImagesSetName (images, file);
344	fclose (f);
345    }
346    return images;
347}
348
349Cursor
350XcursorLibraryLoadCursor (Display *dpy, const char *file)
351{
352    int		    size = XcursorGetDefaultSize (dpy);
353    char	    *theme = XcursorGetTheme (dpy);
354    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
355    Cursor	    cursor;
356
357    if (!file)
358	return 0;
359
360    if (!images)
361    {
362	int id = XcursorLibraryShape (file);
363
364	if (id >= 0)
365	    return _XcursorCreateFontCursor (dpy, (unsigned) id);
366	else
367	    return 0;
368    }
369    cursor = XcursorImagesLoadCursor (dpy, images);
370    XcursorImagesDestroy (images);
371#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
372    XFixesSetCursorName (dpy, cursor, file);
373#endif
374    return cursor;
375}
376
377XcursorCursors *
378XcursorLibraryLoadCursors (Display *dpy, const char *file)
379{
380    int		    size = XcursorGetDefaultSize (dpy);
381    char	    *theme = XcursorGetTheme (dpy);
382    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
383    XcursorCursors  *cursors;
384
385    if (!file)
386	return NULL;
387
388    if (!images)
389    {
390	int id = XcursorLibraryShape (file);
391
392	if (id >= 0)
393	{
394	    cursors = XcursorCursorsCreate (dpy, 1);
395	    if (cursors)
396	    {
397		cursors->cursors[0] = _XcursorCreateFontCursor (dpy, (unsigned) id);
398		if (cursors->cursors[0] == None)
399		{
400		    XcursorCursorsDestroy (cursors);
401		    cursors = NULL;
402		}
403		else
404		    cursors->ncursor = 1;
405	    }
406	}
407	else
408	    cursors = NULL;
409    }
410    else
411    {
412	cursors = XcursorImagesLoadCursors (dpy, images);
413	XcursorImagesDestroy (images);
414    }
415    return cursors;
416}
417
418static const char _XcursorStandardNames[] =
419	"X_cursor\0"
420	"arrow\0"
421	"based_arrow_down\0"
422	"based_arrow_up\0"
423	"boat\0"
424	"bogosity\0"
425	"bottom_left_corner\0"
426	"bottom_right_corner\0"
427	"bottom_side\0"
428	"bottom_tee\0"
429	"box_spiral\0"
430	"center_ptr\0"
431	"circle\0"
432	"clock\0"
433	"coffee_mug\0"
434	"cross\0"
435	"cross_reverse\0"
436	"crosshair\0"
437	"diamond_cross\0"
438	"dot\0"
439	"dotbox\0"
440	"double_arrow\0"
441	"draft_large\0"
442	"draft_small\0"
443	"draped_box\0"
444	"exchange\0"
445	"fleur\0"
446	"gobbler\0"
447	"gumby\0"
448	"hand1\0"
449	"hand2\0"
450	"heart\0"
451	"icon\0"
452	"iron_cross\0"
453	"left_ptr\0"
454	"left_side\0"
455	"left_tee\0"
456	"leftbutton\0"
457	"ll_angle\0"
458	"lr_angle\0"
459	"man\0"
460	"middlebutton\0"
461	"mouse\0"
462	"pencil\0"
463	"pirate\0"
464	"plus\0"
465	"question_arrow\0"
466	"right_ptr\0"
467	"right_side\0"
468	"right_tee\0"
469	"rightbutton\0"
470	"rtl_logo\0"
471	"sailboat\0"
472	"sb_down_arrow\0"
473	"sb_h_double_arrow\0"
474	"sb_left_arrow\0"
475	"sb_right_arrow\0"
476	"sb_up_arrow\0"
477	"sb_v_double_arrow\0"
478	"shuttle\0"
479	"sizing\0"
480	"spider\0"
481	"spraycan\0"
482	"star\0"
483	"target\0"
484	"tcross\0"
485	"top_left_arrow\0"
486	"top_left_corner\0"
487	"top_right_corner\0"
488	"top_side\0"
489	"top_tee\0"
490	"trek\0"
491	"ul_angle\0"
492	"umbrella\0"
493	"ur_angle\0"
494	"watch\0"
495	"xterm";
496
497static const unsigned short _XcursorStandardNameOffsets[] = {
498	0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158,
499	169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287,
500	295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392,
501	396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510,
502	524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651,
503	666, 682, 699, 708, 716, 721, 730, 739, 748, 754
504};
505
506#define NUM_STANDARD_NAMES  (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0])
507
508#define STANDARD_NAME(id) \
509    _XcursorStandardNames + _XcursorStandardNameOffsets[id]
510
511XcursorImage *
512XcursorShapeLoadImage (unsigned int shape, const char *theme, int size)
513{
514    unsigned int    id = shape >> 1;
515
516    if (id < NUM_STANDARD_NAMES)
517	return XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size);
518    else
519	return NULL;
520}
521
522XcursorImages *
523XcursorShapeLoadImages (unsigned int shape, const char *theme, int size)
524{
525    unsigned int    id = shape >> 1;
526
527    if (id < NUM_STANDARD_NAMES)
528	return XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size);
529    else
530	return NULL;
531}
532
533Cursor
534XcursorShapeLoadCursor (Display *dpy, unsigned int shape)
535{
536    unsigned int    id = shape >> 1;
537
538    if (id < NUM_STANDARD_NAMES)
539	return XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id));
540    else
541	return 0;
542}
543
544XcursorCursors *
545XcursorShapeLoadCursors (Display *dpy, unsigned int shape)
546{
547    unsigned int    id = shape >> 1;
548
549    if (id < NUM_STANDARD_NAMES)
550	return XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id));
551    else
552	return NULL;
553}
554
555int
556XcursorLibraryShape (const char *library)
557{
558    int	low, high;
559
560    low = 0;
561    high = NUM_STANDARD_NAMES - 1;
562    while (low < high - 1)
563    {
564	int mid = (low + high) >> 1;
565	int c = strcmp (library, STANDARD_NAME (mid));
566	if (c == 0)
567	    return (mid << 1);
568	if (c > 0)
569	    low = mid;
570	else
571	    high = mid;
572    }
573    while (low <= high)
574    {
575	if (!strcmp (library, STANDARD_NAME (low)))
576	    return (low << 1);
577	low++;
578    }
579    return -1;
580}
581