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