library.c revision cdce750a
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
35const char *
36XcursorLibraryPath (void)
37{
38    static const char	*path;
39
40    if (!path)
41    {
42	path = getenv ("XCURSOR_PATH");
43	if (!path)
44	    path = XCURSORPATH;
45    }
46    return path;
47}
48
49static  void
50_XcursorAddPathElt (char *path, const char *elt, int len)
51{
52    size_t    pathlen = strlen (path);
53
54    /* append / if the path doesn't currently have one */
55    if (path[0] == '\0' || path[pathlen - 1] != '/')
56    {
57	strcat (path, "/");
58	pathlen++;
59    }
60    if (len == -1)
61	len = strlen (elt);
62    /* strip leading slashes */
63    while (len && elt[0] == '/')
64    {
65	elt++;
66	len--;
67    }
68    strncpy (path + pathlen, elt, len);
69    path[pathlen + len] = '\0';
70}
71
72static char *
73_XcursorBuildThemeDir (const char *dir, const char *theme)
74{
75    const char	    *colon;
76    const char	    *tcolon;
77    char	    *full;
78    char	    *home;
79    int		    dirlen;
80    int		    homelen;
81    int		    themelen;
82    int		    len;
83
84    if (!dir || !theme)
85        return NULL;
86
87    colon = strchr (dir, ':');
88    if (!colon)
89	colon = dir + strlen (dir);
90
91    dirlen = colon - dir;
92
93    tcolon = strchr (theme, ':');
94    if (!tcolon)
95	tcolon = theme + strlen (theme);
96
97    themelen = tcolon - theme;
98
99    home = NULL;
100    homelen = 0;
101    if (*dir == '~')
102    {
103	home = getenv ("HOME");
104	if (!home)
105	    return NULL;
106	homelen = strlen (home);
107	dir++;
108	dirlen--;
109    }
110
111    /*
112     * add space for any needed directory separators, one per component,
113     * and one for the trailing null
114     */
115    len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
116
117    full = malloc (len);
118    if (!full)
119	return NULL;
120    full[0] = '\0';
121
122    if (home)
123	_XcursorAddPathElt (full, home, -1);
124    _XcursorAddPathElt (full, dir, dirlen);
125    _XcursorAddPathElt (full, theme, themelen);
126    return full;
127}
128
129static char *
130_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
131{
132    char    *full;
133
134    if (!dir || !subdir || !file)
135        return NULL;
136
137    full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
138    if (!full)
139	return NULL;
140    full[0] = '\0';
141    _XcursorAddPathElt (full, dir, -1);
142    _XcursorAddPathElt (full, subdir, -1);
143    _XcursorAddPathElt (full, file, -1);
144    return full;
145}
146
147static const char *
148_XcursorNextPath (const char *path)
149{
150    char    *colon = strchr (path, ':');
151
152    if (!colon)
153	return NULL;
154    return colon + 1;
155}
156
157#define XcursorWhite(c)	((c) == ' ' || (c) == '\t' || (c) == '\n')
158#define XcursorSep(c) ((c) == ';' || (c) == ',')
159
160static char *
161_XcursorThemeInherits (const char *full)
162{
163    char    line[8192];
164    char    *result = NULL;
165    FILE    *f;
166
167    if (!full)
168        return NULL;
169
170    f = fopen (full, "r");
171    if (f)
172    {
173	while (fgets (line, sizeof (line), f))
174	{
175	    if (!strncmp (line, "Inherits", 8))
176	    {
177		char    *l = line + 8;
178		char    *r;
179		while (*l == ' ') l++;
180		if (*l != '=') continue;
181		l++;
182		while (*l == ' ') l++;
183		result = malloc (strlen (l) + 1);
184		if (result)
185		{
186		    r = result;
187		    while (*l)
188		    {
189			while (XcursorSep(*l) || XcursorWhite (*l)) l++;
190			if (!*l)
191			    break;
192			if (r != result)
193			    *r++ = ':';
194			while (*l && !XcursorWhite(*l) &&
195			       !XcursorSep(*l))
196			    *r++ = *l++;
197		    }
198		    *r++ = '\0';
199		}
200		break;
201	    }
202	}
203	fclose (f);
204    }
205    return result;
206}
207
208#define XCURSOR_SCAN_CORE   ((FILE *) 1)
209
210static FILE *
211XcursorScanTheme (const char *theme, const char *name)
212{
213    FILE	*f = NULL;
214    char	*full;
215    char	*dir;
216    const char  *path;
217    char	*inherits = NULL;
218    const char	*i;
219
220    if (!theme || !name)
221        return NULL;
222
223    /*
224     * XCURSOR_CORE_THEME is a magic name; cursors from the core set
225     * are never found in any directory.  Instead, a magic value is
226     * returned which truncates any search so that overlying functions
227     * can switch to equivalent core cursors
228     */
229    if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0)
230	return XCURSOR_SCAN_CORE;
231    /*
232     * Scan this theme
233     */
234    for (path = XcursorLibraryPath ();
235	 path && f == NULL;
236	 path = _XcursorNextPath (path))
237    {
238	dir = _XcursorBuildThemeDir (path, theme);
239	if (dir)
240	{
241	    full = _XcursorBuildFullname (dir, "cursors", name);
242	    if (full)
243	    {
244		f = fopen (full, "r");
245		free (full);
246	    }
247	    if (!f && !inherits)
248	    {
249		full = _XcursorBuildFullname (dir, "", "index.theme");
250		if (full)
251		{
252		    inherits = _XcursorThemeInherits (full);
253		    free (full);
254		}
255	    }
256	    free (dir);
257	}
258    }
259    /*
260     * Recurse to scan inherited themes
261     */
262    for (i = inherits; i && f == NULL; i = _XcursorNextPath (i))
263    {
264        if (strcmp(i, theme) != 0)
265            f = XcursorScanTheme (i, name);
266        else
267            printf("Not calling XcursorScanTheme because of circular dependency: %s. %s", i, name);
268    }
269    if (inherits != NULL)
270	free (inherits);
271    return f;
272}
273
274XcursorImage *
275XcursorLibraryLoadImage (const char *file, const char *theme, int size)
276{
277    FILE	    *f = NULL;
278    XcursorImage    *image = NULL;
279
280    if (!file)
281        return NULL;
282
283    if (theme)
284	f = XcursorScanTheme (theme, file);
285    if (!f)
286	f = XcursorScanTheme ("default", file);
287    if (f == XCURSOR_SCAN_CORE)
288	return NULL;
289    if (f)
290    {
291	image = XcursorFileLoadImage (f, size);
292	fclose (f);
293    }
294    return image;
295}
296
297XcursorImages *
298XcursorLibraryLoadImages (const char *file, const char *theme, int size)
299{
300    FILE	    *f = NULL;
301    XcursorImages   *images = NULL;
302
303    if (!file)
304        return NULL;
305
306    if (theme)
307	f = XcursorScanTheme (theme, file);
308    if (!f)
309	f = XcursorScanTheme ("default", file);
310    if (f == XCURSOR_SCAN_CORE)
311	return NULL;
312    if (f)
313    {
314	images = XcursorFileLoadImages (f, size);
315	if (images)
316	    XcursorImagesSetName (images, file);
317	fclose (f);
318    }
319    return images;
320}
321
322Cursor
323XcursorLibraryLoadCursor (Display *dpy, const char *file)
324{
325    int		    size = XcursorGetDefaultSize (dpy);
326    char	    *theme = XcursorGetTheme (dpy);
327    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
328    Cursor	    cursor;
329
330    if (!file)
331        return 0;
332
333    if (!images)
334    {
335	int id = XcursorLibraryShape (file);
336
337	if (id >= 0)
338	    return _XcursorCreateFontCursor (dpy, id);
339	else
340	    return 0;
341    }
342    cursor = XcursorImagesLoadCursor (dpy, images);
343    XcursorImagesDestroy (images);
344#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
345    XFixesSetCursorName (dpy, cursor, file);
346#endif
347    return cursor;
348}
349
350XcursorCursors *
351XcursorLibraryLoadCursors (Display *dpy, const char *file)
352{
353    int		    size = XcursorGetDefaultSize (dpy);
354    char	    *theme = XcursorGetTheme (dpy);
355    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
356    XcursorCursors  *cursors;
357
358    if (!file)
359        return NULL;
360
361    if (!images)
362    {
363	int id = XcursorLibraryShape (file);
364
365	if (id >= 0)
366	{
367	    cursors = XcursorCursorsCreate (dpy, 1);
368	    if (cursors)
369	    {
370		cursors->cursors[0] = _XcursorCreateFontCursor (dpy, id);
371		if (cursors->cursors[0] == None)
372		{
373		    XcursorCursorsDestroy (cursors);
374		    cursors = NULL;
375		}
376		else
377		    cursors->ncursor = 1;
378	    }
379	}
380	else
381	    cursors = NULL;
382    }
383    else
384    {
385	cursors = XcursorImagesLoadCursors (dpy, images);
386	XcursorImagesDestroy (images);
387    }
388    return cursors;
389}
390
391static const char _XcursorStandardNames[] =
392	"X_cursor\0"
393	"arrow\0"
394	"based_arrow_down\0"
395	"based_arrow_up\0"
396	"boat\0"
397	"bogosity\0"
398	"bottom_left_corner\0"
399	"bottom_right_corner\0"
400	"bottom_side\0"
401	"bottom_tee\0"
402	"box_spiral\0"
403	"center_ptr\0"
404	"circle\0"
405	"clock\0"
406	"coffee_mug\0"
407	"cross\0"
408	"cross_reverse\0"
409	"crosshair\0"
410	"diamond_cross\0"
411	"dot\0"
412	"dotbox\0"
413	"double_arrow\0"
414	"draft_large\0"
415	"draft_small\0"
416	"draped_box\0"
417	"exchange\0"
418	"fleur\0"
419	"gobbler\0"
420	"gumby\0"
421	"hand1\0"
422	"hand2\0"
423	"heart\0"
424	"icon\0"
425	"iron_cross\0"
426	"left_ptr\0"
427	"left_side\0"
428	"left_tee\0"
429	"leftbutton\0"
430	"ll_angle\0"
431	"lr_angle\0"
432	"man\0"
433	"middlebutton\0"
434	"mouse\0"
435	"pencil\0"
436	"pirate\0"
437	"plus\0"
438	"question_arrow\0"
439	"right_ptr\0"
440	"right_side\0"
441	"right_tee\0"
442	"rightbutton\0"
443	"rtl_logo\0"
444	"sailboat\0"
445	"sb_down_arrow\0"
446	"sb_h_double_arrow\0"
447	"sb_left_arrow\0"
448	"sb_right_arrow\0"
449	"sb_up_arrow\0"
450	"sb_v_double_arrow\0"
451	"shuttle\0"
452	"sizing\0"
453	"spider\0"
454	"spraycan\0"
455	"star\0"
456	"target\0"
457	"tcross\0"
458	"top_left_arrow\0"
459	"top_left_corner\0"
460	"top_right_corner\0"
461	"top_side\0"
462	"top_tee\0"
463	"trek\0"
464	"ul_angle\0"
465	"umbrella\0"
466	"ur_angle\0"
467	"watch\0"
468	"xterm";
469
470static const unsigned short _XcursorStandardNameOffsets[] = {
471	0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158,
472	169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287,
473	295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392,
474	396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510,
475	524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651,
476	666, 682, 699, 708, 716, 721, 730, 739, 748, 754
477};
478
479#define NUM_STANDARD_NAMES  (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0])
480
481#define STANDARD_NAME(id) \
482    _XcursorStandardNames + _XcursorStandardNameOffsets[id]
483
484XcursorImage *
485XcursorShapeLoadImage (unsigned int shape, const char *theme, int size)
486{
487    unsigned int    id = shape >> 1;
488
489    if (id < NUM_STANDARD_NAMES)
490	return XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size);
491    else
492	return NULL;
493}
494
495XcursorImages *
496XcursorShapeLoadImages (unsigned int shape, const char *theme, int size)
497{
498    unsigned int    id = shape >> 1;
499
500    if (id < NUM_STANDARD_NAMES)
501	return XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size);
502    else
503	return NULL;
504}
505
506Cursor
507XcursorShapeLoadCursor (Display *dpy, unsigned int shape)
508{
509    unsigned int    id = shape >> 1;
510
511    if (id < NUM_STANDARD_NAMES)
512	return XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id));
513    else
514	return 0;
515}
516
517XcursorCursors *
518XcursorShapeLoadCursors (Display *dpy, unsigned int shape)
519{
520    unsigned int    id = shape >> 1;
521
522    if (id < NUM_STANDARD_NAMES)
523	return XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id));
524    else
525	return NULL;
526}
527
528int
529XcursorLibraryShape (const char *library)
530{
531    int	low, high;
532    int	mid;
533    int	c;
534
535    low = 0;
536    high = NUM_STANDARD_NAMES - 1;
537    while (low < high - 1)
538    {
539	mid = (low + high) >> 1;
540	c = strcmp (library, STANDARD_NAME (mid));
541	if (c == 0)
542	    return (mid << 1);
543	if (c > 0)
544	    low = mid;
545	else
546	    high = mid;
547    }
548    while (low <= high)
549    {
550	if (!strcmp (library, STANDARD_NAME (low)))
551	    return (low << 1);
552	low++;
553    }
554    return -1;
555}
556