library.c revision e6d5e4e0
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 "~/.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    int	    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));
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	f = XcursorScanTheme (i, name);
264    if (inherits != NULL)
265	free (inherits);
266    return f;
267}
268
269XcursorImage *
270XcursorLibraryLoadImage (const char *file, const char *theme, int size)
271{
272    FILE	    *f = NULL;
273    XcursorImage    *image = NULL;
274
275    if (!file)
276        return NULL;
277
278    if (theme)
279	f = XcursorScanTheme (theme, file);
280    if (!f)
281	f = XcursorScanTheme ("default", file);
282    if (f == XCURSOR_SCAN_CORE)
283	return NULL;
284    if (f)
285    {
286	image = XcursorFileLoadImage (f, size);
287	fclose (f);
288    }
289    return image;
290}
291
292XcursorImages *
293XcursorLibraryLoadImages (const char *file, const char *theme, int size)
294{
295    FILE	    *f = NULL;
296    XcursorImages   *images = NULL;
297
298    if (!file)
299        return NULL;
300
301    if (theme)
302	f = XcursorScanTheme (theme, file);
303    if (!f)
304	f = XcursorScanTheme ("default", file);
305    if (f == XCURSOR_SCAN_CORE)
306	return NULL;
307    if (f)
308    {
309	images = XcursorFileLoadImages (f, size);
310	if (images)
311	    XcursorImagesSetName (images, file);
312	fclose (f);
313    }
314    return images;
315}
316
317Cursor
318XcursorLibraryLoadCursor (Display *dpy, const char *file)
319{
320    int		    size = XcursorGetDefaultSize (dpy);
321    char	    *theme = XcursorGetTheme (dpy);
322    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
323    Cursor	    cursor;
324
325    if (!file)
326        return 0;
327
328    if (!images)
329    {
330	int id = XcursorLibraryShape (file);
331
332	if (id >= 0)
333	    return _XcursorCreateFontCursor (dpy, id);
334	else
335	    return 0;
336    }
337    cursor = XcursorImagesLoadCursor (dpy, images);
338    XcursorImagesDestroy (images);
339#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
340    XFixesSetCursorName (dpy, cursor, file);
341#endif
342    return cursor;
343}
344
345XcursorCursors *
346XcursorLibraryLoadCursors (Display *dpy, const char *file)
347{
348    int		    size = XcursorGetDefaultSize (dpy);
349    char	    *theme = XcursorGetTheme (dpy);
350    XcursorImages   *images = XcursorLibraryLoadImages (file, theme, size);
351    XcursorCursors  *cursors;
352
353    if (!file)
354        return NULL;
355
356    if (!images)
357    {
358	int id = XcursorLibraryShape (file);
359
360	if (id >= 0)
361	{
362	    cursors = XcursorCursorsCreate (dpy, 1);
363	    if (cursors)
364	    {
365		cursors->cursors[0] = _XcursorCreateFontCursor (dpy, id);
366		if (cursors->cursors[0] == None)
367		{
368		    XcursorCursorsDestroy (cursors);
369		    cursors = NULL;
370		}
371		else
372		    cursors->ncursor = 1;
373	    }
374	}
375	else
376	    cursors = NULL;
377    }
378    else
379    {
380	cursors = XcursorImagesLoadCursors (dpy, images);
381	XcursorImagesDestroy (images);
382    }
383    return cursors;
384}
385
386static const char _XcursorStandardNames[] =
387	"X_cursor\0"
388	"arrow\0"
389	"based_arrow_down\0"
390	"based_arrow_up\0"
391	"boat\0"
392	"bogosity\0"
393	"bottom_left_corner\0"
394	"bottom_right_corner\0"
395	"bottom_side\0"
396	"bottom_tee\0"
397	"box_spiral\0"
398	"center_ptr\0"
399	"circle\0"
400	"clock\0"
401	"coffee_mug\0"
402	"cross\0"
403	"cross_reverse\0"
404	"crosshair\0"
405	"diamond_cross\0"
406	"dot\0"
407	"dotbox\0"
408	"double_arrow\0"
409	"draft_large\0"
410	"draft_small\0"
411	"draped_box\0"
412	"exchange\0"
413	"fleur\0"
414	"gobbler\0"
415	"gumby\0"
416	"hand1\0"
417	"hand2\0"
418	"heart\0"
419	"icon\0"
420	"iron_cross\0"
421	"left_ptr\0"
422	"left_side\0"
423	"left_tee\0"
424	"leftbutton\0"
425	"ll_angle\0"
426	"lr_angle\0"
427	"man\0"
428	"middlebutton\0"
429	"mouse\0"
430	"pencil\0"
431	"pirate\0"
432	"plus\0"
433	"question_arrow\0"
434	"right_ptr\0"
435	"right_side\0"
436	"right_tee\0"
437	"rightbutton\0"
438	"rtl_logo\0"
439	"sailboat\0"
440	"sb_down_arrow\0"
441	"sb_h_double_arrow\0"
442	"sb_left_arrow\0"
443	"sb_right_arrow\0"
444	"sb_up_arrow\0"
445	"sb_v_double_arrow\0"
446	"shuttle\0"
447	"sizing\0"
448	"spider\0"
449	"spraycan\0"
450	"star\0"
451	"target\0"
452	"tcross\0"
453	"top_left_arrow\0"
454	"top_left_corner\0"
455	"top_right_corner\0"
456	"top_side\0"
457	"top_tee\0"
458	"trek\0"
459	"ul_angle\0"
460	"umbrella\0"
461	"ur_angle\0"
462	"watch\0"
463	"xterm";
464
465static const unsigned short _XcursorStandardNameOffsets[] = {
466	0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158,
467	169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287,
468	295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392,
469	396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510,
470	524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651,
471	666, 682, 699, 708, 716, 721, 730, 739, 748, 754
472};
473
474#define NUM_STANDARD_NAMES  (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0])
475
476#define STANDARD_NAME(id) \
477    _XcursorStandardNames + _XcursorStandardNameOffsets[id]
478
479XcursorImage *
480XcursorShapeLoadImage (unsigned int shape, const char *theme, int size)
481{
482    unsigned int    id = shape >> 1;
483
484    if (id < NUM_STANDARD_NAMES)
485	return XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size);
486    else
487	return NULL;
488}
489
490XcursorImages *
491XcursorShapeLoadImages (unsigned int shape, const char *theme, int size)
492{
493    unsigned int    id = shape >> 1;
494
495    if (id < NUM_STANDARD_NAMES)
496	return XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size);
497    else
498	return NULL;
499}
500
501Cursor
502XcursorShapeLoadCursor (Display *dpy, unsigned int shape)
503{
504    unsigned int    id = shape >> 1;
505
506    if (id < NUM_STANDARD_NAMES)
507	return XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id));
508    else
509	return 0;
510}
511
512XcursorCursors *
513XcursorShapeLoadCursors (Display *dpy, unsigned int shape)
514{
515    unsigned int    id = shape >> 1;
516
517    if (id < NUM_STANDARD_NAMES)
518	return XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id));
519    else
520	return NULL;
521}
522
523int
524XcursorLibraryShape (const char *library)
525{
526    int	low, high;
527    int	mid;
528    int	c;
529
530    low = 0;
531    high = NUM_STANDARD_NAMES - 1;
532    while (low < high - 1)
533    {
534	mid = (low + high) >> 1;
535	c = strcmp (library, STANDARD_NAME (mid));
536	if (c == 0)
537	    return (mid << 1);
538	if (c > 0)
539	    low = mid;
540	else
541	    high = mid;
542    }
543    while (low <= high)
544    {
545	if (!strcmp (library, STANDARD_NAME (low)))
546	    return (low << 1);
547	low++;
548    }
549    return -1;
550}
551