library.c revision 0ea508b1
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 35typedef struct XcursorInherit { 36 char *line; 37 const char *theme; 38} XcursorInherit; 39 40const char * 41XcursorLibraryPath (void) 42{ 43 static const char *path; 44 45 if (!path) 46 { 47 path = getenv ("XCURSOR_PATH"); 48 if (!path) 49 path = XCURSORPATH; 50 } 51 return path; 52} 53 54static void 55_XcursorAddPathElt (char *path, const char *elt, int len) 56{ 57 size_t pathlen = strlen (path); 58 59 /* append / if the path doesn't currently have one */ 60 if (path[0] == '\0' || path[pathlen - 1] != '/') 61 { 62 strcat (path, "/"); 63 pathlen++; 64 } 65 if (len == -1) 66 len = (int) strlen (elt); 67 /* strip leading slashes */ 68 while (len && elt[0] == '/') 69 { 70 elt++; 71 len--; 72 } 73 strncpy (path + pathlen, elt, (size_t) len); 74 path[pathlen + (size_t) len] = '\0'; 75} 76 77static char * 78_XcursorBuildThemeDir (const char *dir, const char *theme) 79{ 80 const char *colon; 81 const char *tcolon; 82 char *full; 83 char *home; 84 int dirlen; 85 int homelen; 86 int themelen; 87 int len; 88 89 if (!dir || !theme) 90 return NULL; 91 92 colon = strchr (dir, ':'); 93 if (!colon) 94 colon = dir + strlen (dir); 95 96 dirlen = (int) (colon - dir); 97 98 tcolon = strchr (theme, ':'); 99 if (!tcolon) 100 tcolon = theme + strlen (theme); 101 102 themelen = (int) (tcolon - theme); 103 104 home = NULL; 105 homelen = 0; 106 if (*dir == '~') 107 { 108 home = getenv ("HOME"); 109 if (!home) 110 return NULL; 111 homelen = (int) strlen (home); 112 dir++; 113 dirlen--; 114 } 115 116 /* 117 * add space for any needed directory separators, one per component, 118 * and one for the trailing null 119 */ 120 len = 1 + homelen + 1 + dirlen + 1 + themelen + 1; 121 122 full = malloc ((size_t)len); 123 if (!full) 124 return NULL; 125 full[0] = '\0'; 126 127 if (home) 128 _XcursorAddPathElt (full, home, -1); 129 _XcursorAddPathElt (full, dir, dirlen); 130 _XcursorAddPathElt (full, theme, themelen); 131 return full; 132} 133 134static char * 135_XcursorBuildFullname (const char *dir, const char *subdir, const char *file) 136{ 137 char *full; 138 139 if (!dir || !subdir || !file) 140 return NULL; 141 142 full = malloc (strlen (dir) + 1 + strlen (subdir) + 1 + strlen (file) + 1); 143 if (!full) 144 return NULL; 145 full[0] = '\0'; 146 _XcursorAddPathElt (full, dir, -1); 147 _XcursorAddPathElt (full, subdir, -1); 148 _XcursorAddPathElt (full, file, -1); 149 return full; 150} 151 152static const char * 153_XcursorNextPath (const char *path) 154{ 155 char *colon = strchr (path, ':'); 156 157 if (!colon) 158 return NULL; 159 return colon + 1; 160} 161 162/* 163 * _XcursorThemeInherits, XcursorWhite, & XcursorSep are copied in 164 * libxcb-cursor/cursor/load_cursor.c. Please update that copy to 165 * include any changes made to the code for those here. 166 */ 167 168#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') 169#define XcursorSep(c) ((c) == ';' || (c) == ',') 170 171static char * 172_XcursorThemeInherits (const char *full) 173{ 174 char line[8192]; 175 char *result = NULL; 176 FILE *f; 177 178 if (!full) 179 return NULL; 180 181 f = fopen (full, "r" FOPEN_CLOEXEC); 182 if (f) 183 { 184 while (fgets (line, sizeof (line), f)) 185 { 186 if (!strncmp (line, "Inherits", 8)) 187 { 188 char *l = line + 8; 189 while (*l == ' ') l++; 190 if (*l != '=') continue; 191 l++; 192 while (*l == ' ') l++; 193 result = malloc (strlen (l) + 1); 194 if (result) 195 { 196 char *r = result; 197 while (*l) 198 { 199 while (XcursorSep(*l) || XcursorWhite (*l)) l++; 200 if (!*l) 201 break; 202 if (r != result) 203 *r++ = ':'; 204 while (*l && !XcursorWhite(*l) && 205 !XcursorSep(*l)) 206 *r++ = *l++; 207 } 208 *r++ = '\0'; 209 } 210 break; 211 } 212 } 213 fclose (f); 214 } 215 return result; 216} 217 218#define XCURSOR_SCAN_CORE ((FILE *) 1) 219#define MAX_INHERITS_DEPTH 32 220 221static FILE * 222XcursorScanTheme (const char *theme, const char *name) 223{ 224 FILE *f = NULL; 225 char *full; 226 char *dir; 227 const char *path; 228 XcursorInherit inherits[MAX_INHERITS_DEPTH + 1]; 229 int d; 230 231 if (!theme || !name) 232 return NULL; 233 234 /* 235 * XCURSOR_CORE_THEME is a magic name; cursors from the core set 236 * are never found in any directory. Instead, a magic value is 237 * returned which truncates any search so that overlying functions 238 * can switch to equivalent core cursors 239 */ 240 if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0) 241 return XCURSOR_SCAN_CORE; 242 243 memset (inherits, 0, sizeof (inherits)); 244 245 d = 0; 246 inherits[d].theme = theme; 247 248 while (f == NULL && d >= 0 && inherits[d].theme != NULL) 249 { 250 /* 251 * Scan this theme 252 */ 253 for (path = XcursorLibraryPath (); 254 path && f == NULL; 255 path = _XcursorNextPath (path)) 256 { 257 dir = _XcursorBuildThemeDir (path, inherits[d].theme); 258 if (dir) 259 { 260 full = _XcursorBuildFullname (dir, "cursors", name); 261 if (full) 262 { 263 f = fopen (full, "r" FOPEN_CLOEXEC); 264 free (full); 265 } 266 if (!f && inherits[d + 1].line == NULL) 267 { 268 if (d + 1 >= MAX_INHERITS_DEPTH) 269 { 270 free (dir); 271 goto finish; 272 } 273 full = _XcursorBuildFullname (dir, "", "index.theme"); 274 if (full) 275 { 276 inherits[d + 1].line = _XcursorThemeInherits (full); 277 inherits[d + 1].theme = inherits[d + 1].line; 278 free (full); 279 } 280 } 281 free (dir); 282 } 283 } 284 285 d++; 286 while (d > 0 && inherits[d].theme == NULL) 287 { 288 free (inherits[d].line); 289 inherits[d].line = NULL; 290 291 if (--d == 0) 292 inherits[d].theme = NULL; 293 else 294 inherits[d].theme = _XcursorNextPath (inherits[d].theme); 295 } 296 297 /* 298 * Detect and break self reference loop early on. 299 */ 300 if (inherits[d].theme != NULL && strcmp (inherits[d].theme, theme) == 0) 301 break; 302 } 303 304finish: 305 for (d = 1; d <= MAX_INHERITS_DEPTH; d++) 306 free (inherits[d].line); 307 308 return f; 309} 310 311XcursorImage * 312XcursorLibraryLoadImage (const char *file, const char *theme, int size) 313{ 314 FILE *f = NULL; 315 XcursorImage *image = NULL; 316 317 if (!file) 318 return NULL; 319 320 if (theme) 321 f = XcursorScanTheme (theme, file); 322 if (!f) 323 f = XcursorScanTheme ("default", file); 324 if (f != NULL && f != XCURSOR_SCAN_CORE) 325 { 326 image = XcursorFileLoadImage (f, size); 327 fclose (f); 328 } 329 return image; 330} 331 332XcursorImages * 333XcursorLibraryLoadImages (const char *file, const char *theme, int size) 334{ 335 FILE *f = NULL; 336 XcursorImages *images = NULL; 337 338 if (!file) 339 return NULL; 340 341 if (theme) 342 f = XcursorScanTheme (theme, file); 343 if (!f) 344 f = XcursorScanTheme ("default", file); 345 if (f != NULL && f != XCURSOR_SCAN_CORE) 346 { 347 images = XcursorFileLoadImages (f, size); 348 if (images) 349 XcursorImagesSetName (images, file); 350 fclose (f); 351 } 352 return images; 353} 354 355Cursor 356XcursorLibraryLoadCursor (Display *dpy, const char *file) 357{ 358 int size = XcursorGetDefaultSize (dpy); 359 char *theme = XcursorGetTheme (dpy); 360 XcursorImages *images = XcursorLibraryLoadImages (file, theme, size); 361 Cursor cursor; 362 363 if (!file) 364 return 0; 365 366 if (!images) 367 { 368 int id = XcursorLibraryShape (file); 369 370 if (id >= 0) 371 return _XcursorCreateFontCursor (dpy, (unsigned) id); 372 else 373 return 0; 374 } 375 cursor = XcursorImagesLoadCursor (dpy, images); 376 XcursorImagesDestroy (images); 377#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2 378 XFixesSetCursorName (dpy, cursor, file); 379#endif 380 return cursor; 381} 382 383XcursorCursors * 384XcursorLibraryLoadCursors (Display *dpy, const char *file) 385{ 386 int size = XcursorGetDefaultSize (dpy); 387 char *theme = XcursorGetTheme (dpy); 388 XcursorImages *images = XcursorLibraryLoadImages (file, theme, size); 389 XcursorCursors *cursors; 390 391 if (!file) 392 return NULL; 393 394 if (!images) 395 { 396 int id = XcursorLibraryShape (file); 397 398 if (id >= 0) 399 { 400 cursors = XcursorCursorsCreate (dpy, 1); 401 if (cursors) 402 { 403 cursors->cursors[0] = _XcursorCreateFontCursor (dpy, (unsigned) id); 404 if (cursors->cursors[0] == None) 405 { 406 XcursorCursorsDestroy (cursors); 407 cursors = NULL; 408 } 409 else 410 cursors->ncursor = 1; 411 } 412 } 413 else 414 cursors = NULL; 415 } 416 else 417 { 418 cursors = XcursorImagesLoadCursors (dpy, images); 419 XcursorImagesDestroy (images); 420 } 421 return cursors; 422} 423 424static const char _XcursorStandardNames[] = 425 "X_cursor\0" 426 "arrow\0" 427 "based_arrow_down\0" 428 "based_arrow_up\0" 429 "boat\0" 430 "bogosity\0" 431 "bottom_left_corner\0" 432 "bottom_right_corner\0" 433 "bottom_side\0" 434 "bottom_tee\0" 435 "box_spiral\0" 436 "center_ptr\0" 437 "circle\0" 438 "clock\0" 439 "coffee_mug\0" 440 "cross\0" 441 "cross_reverse\0" 442 "crosshair\0" 443 "diamond_cross\0" 444 "dot\0" 445 "dotbox\0" 446 "double_arrow\0" 447 "draft_large\0" 448 "draft_small\0" 449 "draped_box\0" 450 "exchange\0" 451 "fleur\0" 452 "gobbler\0" 453 "gumby\0" 454 "hand1\0" 455 "hand2\0" 456 "heart\0" 457 "icon\0" 458 "iron_cross\0" 459 "left_ptr\0" 460 "left_side\0" 461 "left_tee\0" 462 "leftbutton\0" 463 "ll_angle\0" 464 "lr_angle\0" 465 "man\0" 466 "middlebutton\0" 467 "mouse\0" 468 "pencil\0" 469 "pirate\0" 470 "plus\0" 471 "question_arrow\0" 472 "right_ptr\0" 473 "right_side\0" 474 "right_tee\0" 475 "rightbutton\0" 476 "rtl_logo\0" 477 "sailboat\0" 478 "sb_down_arrow\0" 479 "sb_h_double_arrow\0" 480 "sb_left_arrow\0" 481 "sb_right_arrow\0" 482 "sb_up_arrow\0" 483 "sb_v_double_arrow\0" 484 "shuttle\0" 485 "sizing\0" 486 "spider\0" 487 "spraycan\0" 488 "star\0" 489 "target\0" 490 "tcross\0" 491 "top_left_arrow\0" 492 "top_left_corner\0" 493 "top_right_corner\0" 494 "top_side\0" 495 "top_tee\0" 496 "trek\0" 497 "ul_angle\0" 498 "umbrella\0" 499 "ur_angle\0" 500 "watch\0" 501 "xterm"; 502 503static const unsigned short _XcursorStandardNameOffsets[] = { 504 0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158, 505 169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287, 506 295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392, 507 396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510, 508 524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651, 509 666, 682, 699, 708, 716, 721, 730, 739, 748, 754 510}; 511 512#define NUM_STANDARD_NAMES (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0]) 513 514#define STANDARD_NAME(id) \ 515 _XcursorStandardNames + _XcursorStandardNameOffsets[id] 516 517XcursorImage * 518XcursorShapeLoadImage (unsigned int shape, const char *theme, int size) 519{ 520 unsigned int id = shape >> 1; 521 522 if (id < NUM_STANDARD_NAMES) 523 return XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size); 524 else 525 return NULL; 526} 527 528XcursorImages * 529XcursorShapeLoadImages (unsigned int shape, const char *theme, int size) 530{ 531 unsigned int id = shape >> 1; 532 533 if (id < NUM_STANDARD_NAMES) 534 return XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size); 535 else 536 return NULL; 537} 538 539Cursor 540XcursorShapeLoadCursor (Display *dpy, unsigned int shape) 541{ 542 unsigned int id = shape >> 1; 543 544 if (id < NUM_STANDARD_NAMES) 545 return XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id)); 546 else 547 return 0; 548} 549 550XcursorCursors * 551XcursorShapeLoadCursors (Display *dpy, unsigned int shape) 552{ 553 unsigned int id = shape >> 1; 554 555 if (id < NUM_STANDARD_NAMES) 556 return XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id)); 557 else 558 return NULL; 559} 560 561int 562XcursorLibraryShape (const char *library) 563{ 564 int low, high; 565 566 low = 0; 567 high = NUM_STANDARD_NAMES - 1; 568 while (low < high - 1) 569 { 570 int mid = (low + high) >> 1; 571 int c = strcmp (library, STANDARD_NAME (mid)); 572 if (c == 0) 573 return (mid << 1); 574 if (c > 0) 575 low = mid; 576 else 577 high = mid; 578 } 579 while (low <= high) 580 { 581 if (!strcmp (library, STANDARD_NAME (low))) 582 return (low << 1); 583 low++; 584 } 585 return -1; 586} 587