library.c revision 4d939ec7
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#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') 163#define XcursorSep(c) ((c) == ';' || (c) == ',') 164 165static char * 166_XcursorThemeInherits (const char *full) 167{ 168 char line[8192]; 169 char *result = NULL; 170 FILE *f; 171 172 if (!full) 173 return NULL; 174 175 f = fopen (full, "r"); 176 if (f) 177 { 178 while (fgets (line, sizeof (line), f)) 179 { 180 if (!strncmp (line, "Inherits", 8)) 181 { 182 char *l = line + 8; 183 while (*l == ' ') l++; 184 if (*l != '=') continue; 185 l++; 186 while (*l == ' ') l++; 187 result = malloc (strlen (l) + 1); 188 if (result) 189 { 190 char *r = result; 191 while (*l) 192 { 193 while (XcursorSep(*l) || XcursorWhite (*l)) l++; 194 if (!*l) 195 break; 196 if (r != result) 197 *r++ = ':'; 198 while (*l && !XcursorWhite(*l) && 199 !XcursorSep(*l)) 200 *r++ = *l++; 201 } 202 *r++ = '\0'; 203 } 204 break; 205 } 206 } 207 fclose (f); 208 } 209 return result; 210} 211 212#define XCURSOR_SCAN_CORE ((FILE *) 1) 213#define MAX_INHERITS_DEPTH 32 214 215static FILE * 216XcursorScanTheme (const char *theme, const char *name) 217{ 218 FILE *f = NULL; 219 char *full; 220 char *dir; 221 const char *path; 222 XcursorInherit inherits[MAX_INHERITS_DEPTH + 1]; 223 int d; 224 225 if (!theme || !name) 226 return NULL; 227 228 /* 229 * XCURSOR_CORE_THEME is a magic name; cursors from the core set 230 * are never found in any directory. Instead, a magic value is 231 * returned which truncates any search so that overlying functions 232 * can switch to equivalent core cursors 233 */ 234 if (!strcmp (theme, XCURSOR_CORE_THEME) && XcursorLibraryShape (name) >= 0) 235 return XCURSOR_SCAN_CORE; 236 237 memset (inherits, 0, sizeof (inherits)); 238 239 d = 0; 240 inherits[d].theme = theme; 241 242 while (f == NULL && d >= 0 && inherits[d].theme != NULL) 243 { 244 /* 245 * Scan this theme 246 */ 247 for (path = XcursorLibraryPath (); 248 path && f == NULL; 249 path = _XcursorNextPath (path)) 250 { 251 dir = _XcursorBuildThemeDir (path, inherits[d].theme); 252 if (dir) 253 { 254 full = _XcursorBuildFullname (dir, "cursors", name); 255 if (full) 256 { 257 f = fopen (full, "r"); 258 free (full); 259 } 260 if (!f && inherits[d + 1].line == NULL) 261 { 262 if (d + 1 >= MAX_INHERITS_DEPTH) 263 { 264 free (dir); 265 goto finish; 266 } 267 full = _XcursorBuildFullname (dir, "", "index.theme"); 268 if (full) 269 { 270 inherits[d + 1].line = _XcursorThemeInherits (full); 271 inherits[d + 1].theme = inherits[d + 1].line; 272 free (full); 273 } 274 } 275 free (dir); 276 } 277 } 278 279 d++; 280 while (d > 0 && inherits[d].theme == NULL) 281 { 282 free (inherits[d].line); 283 inherits[d].line = NULL; 284 285 if (--d == 0) 286 inherits[d].theme = NULL; 287 else 288 inherits[d].theme = _XcursorNextPath (inherits[d].theme); 289 } 290 291 /* 292 * Detect and break self reference loop early on. 293 */ 294 if (inherits[d].theme != NULL && strcmp (inherits[d].theme, theme) == 0) 295 break; 296 } 297 298finish: 299 for (d = 1; d <= MAX_INHERITS_DEPTH; d++) 300 free (inherits[d].line); 301 302 return f; 303} 304 305XcursorImage * 306XcursorLibraryLoadImage (const char *file, const char *theme, int size) 307{ 308 FILE *f = NULL; 309 XcursorImage *image = NULL; 310 311 if (!file) 312 return NULL; 313 314 if (theme) 315 f = XcursorScanTheme (theme, file); 316 if (!f) 317 f = XcursorScanTheme ("default", file); 318 if (f != NULL && f != XCURSOR_SCAN_CORE) 319 { 320 image = XcursorFileLoadImage (f, size); 321 fclose (f); 322 } 323 return image; 324} 325 326XcursorImages * 327XcursorLibraryLoadImages (const char *file, const char *theme, int size) 328{ 329 FILE *f = NULL; 330 XcursorImages *images = NULL; 331 332 if (!file) 333 return NULL; 334 335 if (theme) 336 f = XcursorScanTheme (theme, file); 337 if (!f) 338 f = XcursorScanTheme ("default", file); 339 if (f != NULL && f != XCURSOR_SCAN_CORE) 340 { 341 images = XcursorFileLoadImages (f, size); 342 if (images) 343 XcursorImagesSetName (images, file); 344 fclose (f); 345 } 346 return images; 347} 348 349Cursor 350XcursorLibraryLoadCursor (Display *dpy, const char *file) 351{ 352 int size = XcursorGetDefaultSize (dpy); 353 char *theme = XcursorGetTheme (dpy); 354 XcursorImages *images = XcursorLibraryLoadImages (file, theme, size); 355 Cursor cursor; 356 357 if (!file) 358 return 0; 359 360 if (!images) 361 { 362 int id = XcursorLibraryShape (file); 363 364 if (id >= 0) 365 return _XcursorCreateFontCursor (dpy, (unsigned) id); 366 else 367 return 0; 368 } 369 cursor = XcursorImagesLoadCursor (dpy, images); 370 XcursorImagesDestroy (images); 371#if defined HAVE_XFIXES && XFIXES_MAJOR >= 2 372 XFixesSetCursorName (dpy, cursor, file); 373#endif 374 return cursor; 375} 376 377XcursorCursors * 378XcursorLibraryLoadCursors (Display *dpy, const char *file) 379{ 380 int size = XcursorGetDefaultSize (dpy); 381 char *theme = XcursorGetTheme (dpy); 382 XcursorImages *images = XcursorLibraryLoadImages (file, theme, size); 383 XcursorCursors *cursors; 384 385 if (!file) 386 return NULL; 387 388 if (!images) 389 { 390 int id = XcursorLibraryShape (file); 391 392 if (id >= 0) 393 { 394 cursors = XcursorCursorsCreate (dpy, 1); 395 if (cursors) 396 { 397 cursors->cursors[0] = _XcursorCreateFontCursor (dpy, (unsigned) id); 398 if (cursors->cursors[0] == None) 399 { 400 XcursorCursorsDestroy (cursors); 401 cursors = NULL; 402 } 403 else 404 cursors->ncursor = 1; 405 } 406 } 407 else 408 cursors = NULL; 409 } 410 else 411 { 412 cursors = XcursorImagesLoadCursors (dpy, images); 413 XcursorImagesDestroy (images); 414 } 415 return cursors; 416} 417 418static const char _XcursorStandardNames[] = 419 "X_cursor\0" 420 "arrow\0" 421 "based_arrow_down\0" 422 "based_arrow_up\0" 423 "boat\0" 424 "bogosity\0" 425 "bottom_left_corner\0" 426 "bottom_right_corner\0" 427 "bottom_side\0" 428 "bottom_tee\0" 429 "box_spiral\0" 430 "center_ptr\0" 431 "circle\0" 432 "clock\0" 433 "coffee_mug\0" 434 "cross\0" 435 "cross_reverse\0" 436 "crosshair\0" 437 "diamond_cross\0" 438 "dot\0" 439 "dotbox\0" 440 "double_arrow\0" 441 "draft_large\0" 442 "draft_small\0" 443 "draped_box\0" 444 "exchange\0" 445 "fleur\0" 446 "gobbler\0" 447 "gumby\0" 448 "hand1\0" 449 "hand2\0" 450 "heart\0" 451 "icon\0" 452 "iron_cross\0" 453 "left_ptr\0" 454 "left_side\0" 455 "left_tee\0" 456 "leftbutton\0" 457 "ll_angle\0" 458 "lr_angle\0" 459 "man\0" 460 "middlebutton\0" 461 "mouse\0" 462 "pencil\0" 463 "pirate\0" 464 "plus\0" 465 "question_arrow\0" 466 "right_ptr\0" 467 "right_side\0" 468 "right_tee\0" 469 "rightbutton\0" 470 "rtl_logo\0" 471 "sailboat\0" 472 "sb_down_arrow\0" 473 "sb_h_double_arrow\0" 474 "sb_left_arrow\0" 475 "sb_right_arrow\0" 476 "sb_up_arrow\0" 477 "sb_v_double_arrow\0" 478 "shuttle\0" 479 "sizing\0" 480 "spider\0" 481 "spraycan\0" 482 "star\0" 483 "target\0" 484 "tcross\0" 485 "top_left_arrow\0" 486 "top_left_corner\0" 487 "top_right_corner\0" 488 "top_side\0" 489 "top_tee\0" 490 "trek\0" 491 "ul_angle\0" 492 "umbrella\0" 493 "ur_angle\0" 494 "watch\0" 495 "xterm"; 496 497static const unsigned short _XcursorStandardNameOffsets[] = { 498 0, 9, 15, 32, 47, 52, 61, 80, 100, 112, 123, 134, 145, 152, 158, 499 169, 175, 189, 199, 213, 217, 224, 237, 249, 261, 272, 281, 287, 500 295, 301, 307, 313, 319, 324, 335, 344, 354, 363, 374, 383, 392, 501 396, 409, 415, 422, 429, 434, 449, 459, 470, 480, 492, 501, 510, 502 524, 542, 556, 571, 583, 601, 609, 616, 623, 632, 637, 644, 651, 503 666, 682, 699, 708, 716, 721, 730, 739, 748, 754 504}; 505 506#define NUM_STANDARD_NAMES (sizeof _XcursorStandardNameOffsets / sizeof _XcursorStandardNameOffsets[0]) 507 508#define STANDARD_NAME(id) \ 509 _XcursorStandardNames + _XcursorStandardNameOffsets[id] 510 511XcursorImage * 512XcursorShapeLoadImage (unsigned int shape, const char *theme, int size) 513{ 514 unsigned int id = shape >> 1; 515 516 if (id < NUM_STANDARD_NAMES) 517 return XcursorLibraryLoadImage (STANDARD_NAME (id), theme, size); 518 else 519 return NULL; 520} 521 522XcursorImages * 523XcursorShapeLoadImages (unsigned int shape, const char *theme, int size) 524{ 525 unsigned int id = shape >> 1; 526 527 if (id < NUM_STANDARD_NAMES) 528 return XcursorLibraryLoadImages (STANDARD_NAME (id), theme, size); 529 else 530 return NULL; 531} 532 533Cursor 534XcursorShapeLoadCursor (Display *dpy, unsigned int shape) 535{ 536 unsigned int id = shape >> 1; 537 538 if (id < NUM_STANDARD_NAMES) 539 return XcursorLibraryLoadCursor (dpy, STANDARD_NAME (id)); 540 else 541 return 0; 542} 543 544XcursorCursors * 545XcursorShapeLoadCursors (Display *dpy, unsigned int shape) 546{ 547 unsigned int id = shape >> 1; 548 549 if (id < NUM_STANDARD_NAMES) 550 return XcursorLibraryLoadCursors (dpy, STANDARD_NAME (id)); 551 else 552 return NULL; 553} 554 555int 556XcursorLibraryShape (const char *library) 557{ 558 int low, high; 559 560 low = 0; 561 high = NUM_STANDARD_NAMES - 1; 562 while (low < high - 1) 563 { 564 int mid = (low + high) >> 1; 565 int c = strcmp (library, STANDARD_NAME (mid)); 566 if (c == 0) 567 return (mid << 1); 568 if (c > 0) 569 low = mid; 570 else 571 high = mid; 572 } 573 while (low <= high) 574 { 575 if (!strcmp (library, STANDARD_NAME (low))) 576 return (low << 1); 577 low++; 578 } 579 return -1; 580} 581