library.c revision 6b7436ae
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} 659