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 
35 const char *
XcursorLibraryPath(void)36 XcursorLibraryPath (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 
49 static  void
_XcursorAddPathElt(char * path,const char * elt,int len)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 
72 static char *
_XcursorBuildThemeDir(const char * dir,const char * theme)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 
129 static char *
_XcursorBuildFullname(const char * dir,const char * subdir,const char * file)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 
147 static const char *
_XcursorNextPath(const char * path)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 
160 static char *
_XcursorThemeInherits(const char * full)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 
210 static FILE *
XcursorScanTheme(const char * theme,const char * name)211 XcursorScanTheme (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 
274 XcursorImage *
XcursorLibraryLoadImage(const char * file,const char * theme,int size)275 XcursorLibraryLoadImage (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 
297 XcursorImages *
XcursorLibraryLoadImages(const char * file,const char * theme,int size)298 XcursorLibraryLoadImages (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 
322 Cursor
XcursorLibraryLoadCursor(Display * dpy,const char * file)323 XcursorLibraryLoadCursor (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 
350 XcursorCursors *
XcursorLibraryLoadCursors(Display * dpy,const char * file)351 XcursorLibraryLoadCursors (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 
391 static 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 
470 static 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 
484 XcursorImage *
XcursorShapeLoadImage(unsigned int shape,const char * theme,int size)485 XcursorShapeLoadImage (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 
495 XcursorImages *
XcursorShapeLoadImages(unsigned int shape,const char * theme,int size)496 XcursorShapeLoadImages (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 
506 Cursor
XcursorShapeLoadCursor(Display * dpy,unsigned int shape)507 XcursorShapeLoadCursor (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 
517 XcursorCursors *
XcursorShapeLoadCursors(Display * dpy,unsigned int shape)518 XcursorShapeLoadCursors (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 
528 int
XcursorLibraryShape(const char * library)529 XcursorLibraryShape (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