mirror of OpenBSD xenocara tree
github.com/openbsd/xenocara
openbsd
1/*
2 * Copyright © 2024 Thomas E. Dickey
3 * Copyright © 2002 Keith Packard
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of Keith Packard not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission. Keith Packard makes no
12 * representations about the suitability of this software for any purpose. It
13 * is provided "as is" without express or implied warranty.
14 *
15 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23
24#include "xcursorint.h"
25#include <stdlib.h>
26#include <string.h>
27
28#ifndef ICONDIR
29#define ICONDIR "/usr/X11R6/lib/X11/icons"
30#endif
31
32#ifndef XCURSORPATH
33#define XCURSORPATH "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps:"ICONDIR
34#endif
35
36typedef struct XcursorInherit {
37 char *line;
38 const char *theme;
39} XcursorInherit;
40
41const char *
42XcursorLibraryPath (void)
43{
44 static const char *path;
45
46 if (!path)
47 {
48 path = getenv ("XCURSOR_PATH");
49 if (!path)
50 path = XCURSORPATH;
51 traceOpts((T_OPTION(XCURSOR_PATH) ": %s\n", NonNull(path)));
52 }
53 return path;
54}
55
56static void
57_XcursorAddPathElt (char *path, const char *elt, int len)
58{
59 size_t pathlen = strlen (path);
60
61 /* append / if the path doesn't currently have one */
62 if (path[0] == '\0' || path[pathlen - 1] != '/')
63 {
64 strcat (path, "/");
65 pathlen++;
66 }
67 if (len == -1)
68 len = (int) strlen (elt);
69 /* strip leading slashes */
70 while (len && elt[0] == '/')
71 {
72 elt++;
73 len--;
74 }
75 strncpy (path + pathlen, elt, (size_t) len);
76 path[pathlen + (size_t) len] = '\0';
77}
78
79static char *
80_XcursorBuildThemeDir (const char *dir, const char *theme)
81{
82 const char *colon;
83 const char *tcolon;
84 char *full;
85 char *home;
86 int dirlen;
87 int homelen;
88 int themelen;
89 int len;
90
91 if (!dir || !theme)
92 return NULL;
93
94 colon = strchr (dir, ':');
95 if (!colon)
96 colon = dir + strlen (dir);
97
98 dirlen = (int) (colon - dir);
99
100 tcolon = strchr (theme, ':');
101 if (!tcolon)
102 tcolon = theme + strlen (theme);
103
104 themelen = (int) (tcolon - theme);
105
106 home = NULL;
107 homelen = 0;
108 if (*dir == '~')
109 {
110 home = getenv ("HOME");
111 if (!home)
112 return NULL;
113 homelen = (int) strlen (home);
114 dir++;
115 dirlen--;
116 }
117
118 /*
119 * add space for any needed directory separators, one per component,
120 * and one for the trailing null
121 */
122 len = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
123
124 full = malloc ((size_t)len);
125 if (!full)
126 return NULL;
127 full[0] = '\0';
128
129 if (home)
130 _XcursorAddPathElt (full, home, -1);
131 _XcursorAddPathElt (full, dir, dirlen);
132 _XcursorAddPathElt (full, theme, themelen);
133 return full;
134}
135
136static char *
137_XcursorBuildFullname (const char *dir, const char *subdir, const char *file)
138{
139 char *full;
140
141 if (!dir || !subdir || !file)
142 return NULL;
143
144 full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1);
145 if (!full)
146 return NULL;
147 full[0] = '\0';
148 _XcursorAddPathElt (full, dir, -1);
149 _XcursorAddPathElt (full, subdir, -1);
150 _XcursorAddPathElt (full, file, -1);
151 return full;
152}
153
154static const char *
155_XcursorNextPath (const char *path)
156{
157 char *colon = strchr (path, ':');
158
159 if (!colon)
160 return NULL;
161 return colon + 1;
162}
163
164/*
165 * _XcursorThemeInherits, XcursorWhite, & XcursorSep are copied in
166 * libxcb-cursor/cursor/load_cursor.c. Please update that copy to
167 * include any changes made to the code for those here.
168 */
169
170#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
171#define XcursorSep(c) ((c) == ';' || (c) == ',')
172
173static char *
174_XcursorThemeInherits (const char *full)
175{
176 char line[8192];
177 char *result = NULL;
178 FILE *f;
179
180 if (!full)
181 return NULL;
182
183 f = fopen (full, "r" FOPEN_CLOEXEC);
184 if (f)
185 {
186 while (fgets (line, sizeof (line), f))
187 {
188 if (!strncmp (line, "Inherits", 8))
189 {
190 char *l = line + 8;
191 while (*l == ' ') l++;
192 if (*l != '=') continue;
193 l++;
194 while (*l == ' ') l++;
195 result = malloc (strlen (l) + 1);
196 if (result)
197 {
198 char *r = result;
199 while (*l)
200 {
201 while (XcursorSep(*l) || XcursorWhite (*l)) l++;
202 if (!*l)
203 break;
204 if (r != result)
205 *r++ = ':';
206 while (*l && !XcursorWhite(*l) &&
207 !XcursorSep(*l))
208 *r++ = *l++;
209 }
210 *r++ = '\0';
211 }
212 break;
213 }
214 }
215 fclose (f);
216 }
217 return result;
218}
219
220#define XCURSOR_SCAN_CORE ((FILE *) 1)
221#define MAX_INHERITS_DEPTH 32
222
223static FILE *
224XcursorScanTheme (const char *theme, const char *name)
225{
226 FILE *f = NULL;
227 char *full;
228 char *dir;
229 const char *path;
230 XcursorInherit inherits[MAX_INHERITS_DEPTH + 1];
231 int d;
232
233 if (!theme || !name)
234 return NULL;
235
236 /*
237 * XCURSOR_CORE_THEME is a magic name; cursors from the core set
238 * are never found in any directory. Instead, a magic value is
239 * returned which truncates any search so that overlying functions
240 * can switch to equivalent core cursors
241 */
242 if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0)
243 return XCURSOR_SCAN_CORE;
244
245 memset (inherits, 0, sizeof (inherits));
246
247 d = 0;
248 inherits[d].theme = theme;
249
250 while (f == NULL && d >= 0 && inherits[d].theme != NULL)
251 {
252 /*
253 * Scan this theme
254 */
255 for (path = XcursorLibraryPath ();
256 path && f == NULL;
257 path = _XcursorNextPath (path))
258 {
259 dir = _XcursorBuildThemeDir (path, inherits[d].theme);
260 if (dir)
261 {
262 full = _XcursorBuildFullname (dir, "cursors", name);
263 if (full)
264 {
265 f = fopen (full, "r" FOPEN_CLOEXEC);
266 free (full);
267 }
268 if (!f && inherits[d + 1].line == NULL)
269 {
270 if (d + 1 >= MAX_INHERITS_DEPTH)
271 {
272 free (dir);
273 goto finish;
274 }
275 full = _XcursorBuildFullname (dir, "", "index.theme");
276 if (full)
277 {
278 inherits[d + 1].line = _XcursorThemeInherits (full);
279 inherits[d + 1].theme = inherits[d + 1].line;
280 free (full);
281 }
282 }
283 free (dir);
284 }
285 }
286
287 d++;
288 while (d > 0 && inherits[d].theme == NULL)
289 {
290 free (inherits[d].line);
291 inherits[d].line = NULL;
292
293 if (--d == 0)
294 inherits[d].theme = NULL;
295 else
296 inherits[d].theme = _XcursorNextPath (inherits[d].theme);
297 }
298
299 /*
300 * Detect and break self reference loop early on.
301 */
302 if (inherits[d].theme != NULL && strcmp (inherits[d].theme, theme) == 0)
303 break;
304 }
305
306finish:
307 for (d = 1; d <= MAX_INHERITS_DEPTH; d++)
308 free (inherits[d].line);
309
310 return f;
311}
312
313XcursorImage *
314XcursorLibraryLoadImage (const char *file, const char *theme, int size)
315{
316 FILE *f = NULL;
317 XcursorImage *image = NULL;
318
319 enterFunc((T_CALLED(XcursorLibraryLoadImage) "(\"%s\",\"%s\", %d)\n",
320 NonNull(file), NonNull(theme), size));
321
322 if (!file)
323 returnAddr(NULL);
324
325 if (theme)
326 f = XcursorScanTheme (theme, file);
327 if (!f)
328 f = XcursorScanTheme ("default", file);
329 if (f != NULL && f != XCURSOR_SCAN_CORE)
330 {
331 image = XcursorFileLoadImage (f, size);
332 fclose (f);
333 }
334 returnAddr(image);
335}
336
337static XcursorImages *
338_XcursorLibraryLoadImages (Display *dpy, const char *file)
339{
340 int size = XcursorGetDefaultSize (dpy);
341 char *theme = XcursorGetTheme (dpy);
342 XcursorBool resized = XcursorGetResizable (dpy);
343 FILE *f = NULL;
344 XcursorImages *images = NULL;
345
346 if (!file)
347 return NULL;
348
349 if (theme)
350 f = XcursorScanTheme (theme, file);
351 if (!f)
352 f = XcursorScanTheme ("default", file);
353 if (f != NULL && f != XCURSOR_SCAN_CORE)
354 {
355 images = _XcursorFileLoadImages (f, size, resized);
356 if (images)
357 XcursorImagesSetName (images, file);
358 fclose (f);
359 }
360 return images;
361}
362
363XcursorImages *
364XcursorLibraryLoadImages (const char *file, const char *theme, int size)
365{
366 FILE *f = NULL;
367 XcursorImages *images = NULL;
368
369 enterFunc((T_CALLED(XcursorLibraryLoadImages) "(\"%s\", \"%s\", %d)\n",
370 NonNull(file), NonNull(theme), size));
371
372 if (!file)
373 returnAddr(NULL);
374
375 if (theme)
376 f = XcursorScanTheme (theme, file);
377 if (!f)
378 f = XcursorScanTheme ("default", file);
379 if (f != NULL && f != XCURSOR_SCAN_CORE)
380 {
381 images = XcursorFileLoadImages (f, size);
382 if (images)
383 XcursorImagesSetName (images, file);
384 fclose (f);
385 }
386 returnAddr(images);
387}
388
389Cursor
390XcursorLibraryLoadCursor (Display *dpy, const char *file)
391{
392 XcursorImages *images;
393 Cursor cursor = 0;
394
395 enterFunc((T_CALLED(XcursorLibraryLoadCursor) "(%p, \"%s\")\n",
396 (void*)dpy, NonNull(file)));
397
398 if (!file)
399 returnLong(cursor);
400
401 images = _XcursorLibraryLoadImages (dpy, file);
402 if (!images)
403 {
404 int id = XcursorLibraryShape (file);
405
406 if (id >= 0)
407 cursor = _XcursorCreateFontCursor (dpy, (unsigned) id);
408 }
409 else
410 {
411 cursor = XcursorImagesLoadCursor (dpy, images);
412 XcursorImagesDestroy (images);
413#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2
414 XFixesSetCursorName (dpy, cursor, file);
415#endif
416 }
417 returnLong(cursor);
418}
419
420XcursorCursors *
421XcursorLibraryLoadCursors (Display *dpy, const char *file)
422{
423 XcursorImages *images;
424 XcursorCursors *cursors;
425
426 enterFunc((T_CALLED(XcursorLibraryLoadCursors) "(%p, \"%s\")\n",
427 (void*)dpy, NonNull(file)));
428
429 if (!file)
430 returnAddr(NULL);
431
432 images = _XcursorLibraryLoadImages (dpy, file);
433 if (!images)
434 {
435 int id = XcursorLibraryShape (file);
436
437 if (id >= 0)
438 {
439 cursors = XcursorCursorsCreate (dpy, 1);
440 if (cursors)
441 {
442 cursors->cursors[0] = _XcursorCreateFontCursor (dpy, (unsigned) id);
443 if (cursors->cursors[0] == None)
444 {
445 XcursorCursorsDestroy (cursors);
446 cursors = NULL;
447 }
448 else
449 cursors->ncursor = 1;
450 }
451 }
452 else
453 cursors = NULL;
454 }
455 else
456 {
457 cursors = XcursorImagesLoadCursors (dpy, images);
458 XcursorImagesDestroy (images);
459 }
460 returnAddr(cursors);
461}
462
463static const char _XcursorStandardNames[] =
464 "X_cursor\0"
465 "arrow\0"
466 "based_arrow_down\0"
467 "based_arrow_up\0"
468 "boat\0"
469 "bogosity\0"
470 "bottom_left_corner\0"
471 "bottom_right_corner\0"
472 "bottom_side\0"
473 "bottom_tee\0"
474 "box_spiral\0"
475 "center_ptr\0"
476 "circle\0"
477 "clock\0"
478 "coffee_mug\0"
479 "cross\0"
480 "cross_reverse\0"
481 "crosshair\0"
482 "diamond_cross\0"
483 "dot\0"
484 "dotbox\0"
485 "double_arrow\0"
486 "draft_large\0"
487 "draft_small\0"
488 "draped_box\0"
489 "exchange\0"
490 "fleur\0"
491 "gobbler\0"
492 "gumby\0"
493 "hand1\0"
494 "hand2\0"
495 "heart\0"
496 "icon\0"
497 "iron_cross\0"
498 "left_ptr\0"
499 "left_side\0"
500 "left_tee\0"
501 "leftbutton\0"
502 "ll_angle\0"
503 "lr_angle\0"
504 "man\0"
505 "middlebutton\0"
506 "mouse\0"
507 "pencil\0"
508 "pirate\0"
509 "plus\0"
510 "question_arrow\0"
511 "right_ptr\0"
512 "right_side\0"
513 "right_tee\0"
514 "rightbutton\0"
515 "rtl_logo\0"
516 "sailboat\0"
517 "sb_down_arrow\0"
518 "sb_h_double_arrow\0"
519 "sb_left_arrow\0"
520 "sb_right_arrow\0"
521 "sb_up_arrow\0"
522 "sb_v_double_arrow\0"
523 "shuttle\0"
524 "sizing\0"
525 "spider\0"
526 "spraycan\0"
527 "star\0"
528 "target\0"
529 "tcross\0"
530 "top_left_arrow\0"
531 "top_left_corner\0"
532 "top_right_corner\0"
533 "top_side\0"
534 "top_tee\0"
535 "trek\0"
536 "ul_angle\0"
537 "umbrella\0"
538 "ur_angle\0"
539 "watch\0"
540 "xterm";
541
542static const unsigned short _XcursorStandardNameOffsets[] = {
543 0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158,
544 169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287,
545 295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392,
546 396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510,
547 524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651,
548 666, 682, 699, 708, 716, 721, 730, 739, 748, 754
549};
550
551#define NUM_STANDARD_NAMES (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0])
552
553#define STANDARD_NAME(id) \
554 _XcursorStandardNames + _XcursorStandardNameOffsets[id]
555
556XcursorImage *
557XcursorShapeLoadImage (unsigned int shape, const char *theme, int size)
558{
559 unsigned int id = shape >> 1;
560 XcursorImage *result = NULL;
561
562 enterFunc((T_CALLED(XcursorShapeLoadImage) "(%u, \"%s\", %d)\n",
563 shape, NonNull(theme), size));
564
565 if (id < NUM_STANDARD_NAMES)
566 result = XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size);
567
568 returnAddr(result);
569}
570
571XcursorImages *
572_XcursorShapeLoadImages (Display *dpy, unsigned int shape)
573{
574 unsigned int id = shape >> 1;
575 XcursorImages *result = NULL;
576
577 enterFunc((T_CALLED(_XcursorShapeLoadImages) "(%p, %u)\n",
578 (void*)dpy, shape));
579
580 if (id < NUM_STANDARD_NAMES)
581 result = _XcursorLibraryLoadImages (dpy, STANDARD_NAME (id));
582
583 returnAddr(result);
584}
585
586XcursorImages *
587XcursorShapeLoadImages (unsigned int shape, const char *theme, int size)
588{
589 unsigned int id = shape >> 1;
590 XcursorImages *result = NULL;
591
592 enterFunc((T_CALLED(XcursorShapeLoadImages) "(%u, \"%s\", %d)\n",
593 shape, NonNull(theme), size));
594
595 if (id < NUM_STANDARD_NAMES)
596 result = XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size);
597
598 returnAddr(result);
599}
600
601Cursor
602XcursorShapeLoadCursor (Display *dpy, unsigned int shape)
603{
604 unsigned int id = shape >> 1;
605 Cursor result = None;
606
607 enterFunc((T_CALLED(XcursorShapeLoadCursor) "(%p, %u)\n",
608 (void*)dpy, shape));
609
610 if (id < NUM_STANDARD_NAMES)
611 result = XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id));
612
613 returnLong(result);
614}
615
616XcursorCursors *
617XcursorShapeLoadCursors (Display *dpy, unsigned int shape)
618{
619 unsigned int id = shape >> 1;
620 XcursorCursors *result = NULL;
621
622 enterFunc((T_CALLED(XcursorShapeLoadCursors) "(%p, %u)\n",
623 (void*)dpy, shape));
624
625 if (id < NUM_STANDARD_NAMES)
626 result = XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id));
627
628 returnAddr(result);
629}
630
631int
632XcursorLibraryShape (const char *library)
633{
634 int low, high;
635
636 enterFunc((T_CALLED(XcursorLibraryShape) "(%s)\n", NonNull(library)));
637
638 low = 0;
639 high = NUM_STANDARD_NAMES - 1;
640 while (low < high - 1)
641 {
642 int mid = (low + high) >> 1;
643 int c = strcmp (library, STANDARD_NAME (mid));
644 if (c == 0)
645 returnCode(mid << 1);
646 if (c > 0)
647 low = mid;
648 else
649 high = mid;
650 }
651 while (low <= high)
652 {
653 if (!strcmp (library, STANDARD_NAME (low)))
654 return (low << 1);
655 low++;
656 }
657 returnCode(-1);
658}