/* * Copyright © 2002 Keith Packard * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #define _GNU_SOURCE #include "xcursor.h" #include #include #include #include #include /* * Cursor files start with a header. The header * contains a magic number, a version number and a * table of contents which has type and offset information * for the remaining tables in the file. * * File minor versions increment for compatible changes * File major versions increment for incompatible changes (never, we hope) * * Chunks of the same type are always upward compatible. Incompatible * changes are made with new chunk types; the old data can remain under * the old type. Upward compatible changes can add header data as the * header lengths are specified in the file. * * File: * FileHeader * LISTofChunk * * FileHeader: * CARD32 magic magic number * CARD32 header bytes in file header * CARD32 version file version * CARD32 ntoc number of toc entries * LISTofFileToc toc table of contents * * FileToc: * CARD32 type entry type * CARD32 subtype entry subtype (size for images) * CARD32 position absolute file position */ #define XCURSOR_MAGIC 0x72756358 /* "Xcur" LSBFirst */ /* * This version number is stored in cursor files; changes to the * file format require updating this version number */ #define XCURSOR_FILE_MAJOR 1 #define XCURSOR_FILE_MINOR 0 #define XCURSOR_FILE_VERSION ((XCURSOR_FILE_MAJOR << 16) | (XCURSOR_FILE_MINOR)) #define XCURSOR_FILE_HEADER_LEN (4 * 4) #define XCURSOR_FILE_TOC_LEN (3 * 4) struct xcursor_file_toc { uint32_t type; /* chunk type */ uint32_t subtype; /* subtype (size for images) */ uint32_t position; /* absolute position in file */ }; struct xcursor_file_header { uint32_t magic; /* magic number */ uint32_t header; /* byte length of header */ uint32_t version; /* file version number */ uint32_t ntoc; /* number of toc entries */ struct xcursor_file_toc *tocs; /* table of contents */ }; /* * The rest of the file is a list of chunks, each tagged by type * and version. * * Chunk: * ChunkHeader * * * * ChunkHeader: * CARD32 header bytes in chunk header + type header * CARD32 type chunk type * CARD32 subtype chunk subtype * CARD32 version chunk type version */ #define XCURSOR_CHUNK_HEADER_LEN (4 * 4) struct xcursor_chunk_header { uint32_t header; /* bytes in chunk header */ uint32_t type; /* chunk type */ uint32_t subtype; /* chunk subtype (size for images) */ uint32_t version; /* version of this type */ }; /* * Each cursor image occupies a separate image chunk. * The length of the image header follows the chunk header * so that future versions can extend the header without * breaking older applications * * Image: * ChunkHeader header chunk header * CARD32 width actual width * CARD32 height actual height * CARD32 xhot hot spot x * CARD32 yhot hot spot y * CARD32 delay animation delay * LISTofCARD32 pixels ARGB pixels */ #define XCURSOR_IMAGE_TYPE 0xfffd0002 #define XCURSOR_IMAGE_VERSION 1 #define XCURSOR_IMAGE_HEADER_LEN (XCURSOR_CHUNK_HEADER_LEN + (5*4)) #define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ /* * From libXcursor/src/file.c */ static struct xcursor_image * xcursor_image_create(int width, int height) { struct xcursor_image *image; if (width < 0 || height < 0) return NULL; if (width > XCURSOR_IMAGE_MAX_SIZE || height > XCURSOR_IMAGE_MAX_SIZE) return NULL; image = malloc(sizeof(struct xcursor_image) + width * height * sizeof(uint32_t)); if (!image) return NULL; image->version = XCURSOR_IMAGE_VERSION; image->pixels = (uint32_t *) (image + 1); image->size = width > height ? width : height; image->width = width; image->height = height; image->delay = 0; return image; } static void xcursor_image_destroy(struct xcursor_image *image) { free(image); } static struct xcursor_images * xcursor_images_create(int size) { struct xcursor_images *images; images = malloc(sizeof(struct xcursor_images) + size * sizeof(struct xcursor_image *)); if (!images) return NULL; images->nimage = 0; images->images = (struct xcursor_image **) (images + 1); images->name = NULL; return images; } void xcursor_images_destroy(struct xcursor_images *images) { int n; if (!images) return; for (n = 0; n < images->nimage; n++) xcursor_image_destroy(images->images[n]); free(images->name); free(images); } static bool xcursor_read_uint(FILE *file, uint32_t *u) { unsigned char bytes[4]; if (!file || !u) return false; if (fread(bytes, 1, 4, file) != 4) return false; *u = ((uint32_t)(bytes[0]) << 0) | ((uint32_t)(bytes[1]) << 8) | ((uint32_t)(bytes[2]) << 16) | ((uint32_t)(bytes[3]) << 24); return true; } static void xcursor_file_header_destroy(struct xcursor_file_header *file_header) { free(file_header); } static struct xcursor_file_header * xcursor_file_header_create(uint32_t ntoc) { struct xcursor_file_header *file_header; if (ntoc > 0x10000) return NULL; file_header = malloc(sizeof(struct xcursor_file_header) + ntoc * sizeof(struct xcursor_file_toc)); if (!file_header) return NULL; file_header->magic = XCURSOR_MAGIC; file_header->header = XCURSOR_FILE_HEADER_LEN; file_header->version = XCURSOR_FILE_VERSION; file_header->ntoc = ntoc; file_header->tocs = (struct xcursor_file_toc *) (file_header + 1); return file_header; } static struct xcursor_file_header * xcursor_read_file_header(FILE *file) { struct xcursor_file_header head, *file_header; uint32_t skip; unsigned int n; if (!file) return NULL; if (!xcursor_read_uint(file, &head.magic)) return NULL; if (head.magic != XCURSOR_MAGIC) return NULL; if (!xcursor_read_uint(file, &head.header)) return NULL; if (!xcursor_read_uint(file, &head.version)) return NULL; if (!xcursor_read_uint(file, &head.ntoc)) return NULL; skip = head.header - XCURSOR_FILE_HEADER_LEN; if (skip) if (fseek(file, skip, SEEK_CUR) == EOF) return NULL; file_header = xcursor_file_header_create(head.ntoc); if (!file_header) return NULL; file_header->magic = head.magic; file_header->header = head.header; file_header->version = head.version; file_header->ntoc = head.ntoc; for (n = 0; n < file_header->ntoc; n++) { if (!xcursor_read_uint(file, &file_header->tocs[n].type)) break; if (!xcursor_read_uint(file, &file_header->tocs[n].subtype)) break; if (!xcursor_read_uint(file, &file_header->tocs[n].position)) break; } if (n != file_header->ntoc) { xcursor_file_header_destroy(file_header); return NULL; } return file_header; } static bool xcursor_seek_to_toc(FILE *file, struct xcursor_file_header *file_header, int toc) { if (!file || !file_header || fseek(file, file_header->tocs[toc].position, SEEK_SET) == EOF) return false; return true; } static bool xcursor_file_read_chunk_header(FILE *file, struct xcursor_file_header *file_header, int toc, struct xcursor_chunk_header *chunk_header) { if (!file || !file_header || !chunk_header) return false; if (!xcursor_seek_to_toc(file, file_header, toc)) return false; if (!xcursor_read_uint(file, &chunk_header->header)) return false; if (!xcursor_read_uint(file, &chunk_header->type)) return false; if (!xcursor_read_uint(file, &chunk_header->subtype)) return false; if (!xcursor_read_uint(file, &chunk_header->version)) return false; /* sanity check */ if (chunk_header->type != file_header->tocs[toc].type || chunk_header->subtype != file_header->tocs[toc].subtype) return false; return true; } static uint32_t dist(uint32_t a, uint32_t b) { return a > b ? a - b : b - a; } static uint32_t xcursor_file_best_size(struct xcursor_file_header *file_header, uint32_t size, int *nsizesp) { unsigned int n; int nsizes = 0; uint32_t best_size = 0; uint32_t this_size; if (!file_header || !nsizesp) return 0; for (n = 0; n < file_header->ntoc; n++) { if (file_header->tocs[n].type != XCURSOR_IMAGE_TYPE) continue; this_size = file_header->tocs[n].subtype; if (!best_size || dist(this_size, size) < dist(best_size, size)) { best_size = this_size; nsizes = 1; } else if (this_size == best_size) { nsizes++; } } *nsizesp = nsizes; return best_size; } static int xcursor_find_image_toc(struct xcursor_file_header *file_header, uint32_t size, int count) { unsigned int toc; uint32_t this_size; if (!file_header) return 0; for (toc = 0; toc < file_header->ntoc; toc++) { if (file_header->tocs[toc].type != XCURSOR_IMAGE_TYPE) continue; this_size = file_header->tocs[toc].subtype; if (this_size != size) continue; if (!count) break; count--; } if (toc == file_header->ntoc) return -1; return toc; } static struct xcursor_image * xcursor_read_image(FILE *file, struct xcursor_file_header *file_header, int toc) { struct xcursor_chunk_header chunk_header; struct xcursor_image head; struct xcursor_image *image; int n; uint32_t *p; if (!file || !file_header) return NULL; if (!xcursor_file_read_chunk_header(file, file_header, toc, &chunk_header)) return NULL; if (!xcursor_read_uint(file, &head.width)) return NULL; if (!xcursor_read_uint(file, &head.height)) return NULL; if (!xcursor_read_uint(file, &head.xhot)) return NULL; if (!xcursor_read_uint(file, &head.yhot)) return NULL; if (!xcursor_read_uint(file, &head.delay)) return NULL; /* sanity check data */ if (head.width > XCURSOR_IMAGE_MAX_SIZE || head.height > XCURSOR_IMAGE_MAX_SIZE) return NULL; if (head.width == 0 || head.height == 0) return NULL; if (head.xhot > head.width || head.yhot > head.height) return NULL; /* Create the image and initialize it */ image = xcursor_image_create(head.width, head.height); if (image == NULL) return NULL; if (chunk_header.version < image->version) image->version = chunk_header.version; image->size = chunk_header.subtype; image->xhot = head.xhot; image->yhot = head.yhot; image->delay = head.delay; n = image->width * image->height; p = image->pixels; while (n--) { if (!xcursor_read_uint(file, p)) { xcursor_image_destroy(image); return NULL; } p++; } return image; } static struct xcursor_images * xcursor_xc_file_load_images(FILE *file, int size) { struct xcursor_file_header *file_header; uint32_t best_size; int nsize; struct xcursor_images *images; int n; int toc; if (!file || size < 0) return NULL; file_header = xcursor_read_file_header(file); if (!file_header) return NULL; best_size = xcursor_file_best_size(file_header, (uint32_t) size, &nsize); if (!best_size) { xcursor_file_header_destroy(file_header); return NULL; } images = xcursor_images_create(nsize); if (!images) { xcursor_file_header_destroy(file_header); return NULL; } for (n = 0; n < nsize; n++) { toc = xcursor_find_image_toc(file_header, best_size, n); if (toc < 0) break; images->images[images->nimage] = xcursor_read_image(file, file_header, toc); if (!images->images[images->nimage]) break; images->nimage++; } xcursor_file_header_destroy(file_header); if (images->nimage != nsize) { xcursor_images_destroy(images); images = NULL; } return images; } /* * From libXcursor/src/library.c */ #ifndef ICONDIR #define ICONDIR "/usr/X11R6/lib/X11/icons" #endif #ifndef XCURSORPATH #define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:~/.cursors:/usr/share/cursors/xorg-x11:"ICONDIR #endif #define XDG_DATA_HOME_FALLBACK "~/.local/share" #define CURSORDIR "/icons" /** Get search path for cursor themes * * This function builds the list of directories to look for cursor * themes in. The format is PATH-like: directories are separated by * colons. * * The memory block returned by this function is allocated on the heap * and must be freed by the caller. */ static char * xcursor_library_path(void) { const char *env_var, *suffix; char *path; size_t path_size; env_var = getenv("XCURSOR_PATH"); if (env_var) return strdup(env_var); env_var = getenv("XDG_DATA_HOME"); if (!env_var || env_var[0] != '/') env_var = XDG_DATA_HOME_FALLBACK; suffix = CURSORDIR ":" XCURSORPATH; path_size = strlen(env_var) + strlen(suffix) + 1; path = malloc(path_size); if (!path) return NULL; snprintf(path, path_size, "%s%s", env_var, suffix); return path; } static char * xcursor_build_theme_dir(const char *dir, const char *theme) { const char *colon; const char *tcolon; char *full; const char *home, *homesep; int dirlen; int homelen; int themelen; size_t full_size; if (!dir || !theme) return NULL; colon = strchr(dir, ':'); if (!colon) colon = dir + strlen(dir); dirlen = colon - dir; tcolon = strchr(theme, ':'); if (!tcolon) tcolon = theme + strlen(theme); themelen = tcolon - theme; home = ""; homelen = 0; homesep = ""; if (*dir == '~') { home = getenv("HOME"); if (!home) return NULL; homelen = strlen(home); homesep = "/"; dir++; dirlen--; } /* * add space for any needed directory separators, one per component, * and one for the trailing null */ full_size = 1 + homelen + 1 + dirlen + 1 + themelen + 1; full = malloc(full_size); if (!full) return NULL; snprintf(full, full_size, "%s%s%.*s/%.*s", home, homesep, dirlen, dir, themelen, theme); return full; } static char * xcursor_build_fullname(const char *dir, const char *subdir, const char *file) { char *full; size_t full_size; int ret; if (!dir || !subdir || !file) return NULL; full_size = strlen(dir) + 1 + strlen(subdir) + 1 + strlen(file) + 1; full = malloc(full_size); if (!full) return NULL; ret = snprintf(full, full_size, "%s/%s/%s", dir, subdir, file); if (ret < 0) { free(full); return NULL; } return full; } static const char * xcursor_next_path(const char *path) { char *colon = strchr(path, ':'); if (!colon) return NULL; return colon + 1; } static bool xcursor_white(char c) { return c == ' ' || c == '\t' || c == '\n'; } static bool xcursor_sep(char c) { return c == ';' || c == ','; } static char * xcursor_theme_inherits(const char *full) { char *line = NULL; size_t line_size = 0; char *result = NULL; FILE *f; if (!full) return NULL; f = fopen(full, "r"); if (!f) return NULL; while (getline(&line, &line_size, f) >= 0) { const char *l; char *r; if (strncmp(line, "Inherits", 8)) continue; l = line + 8; while (*l == ' ') l++; if (*l != '=') continue; l++; while (*l == ' ') l++; result = malloc(strlen(l) + 1); if (!result) break; r = result; while (*l) { while (xcursor_sep(*l) || xcursor_white(*l)) l++; if (!*l) break; if (r != result) *r++ = ':'; while (*l && !xcursor_white(*l) && !xcursor_sep(*l)) *r++ = *l++; } *r++ = '\0'; break; } fclose(f); free(line); return result; } static void load_all_cursors_from_dir(const char *path, int size, void (*load_callback)(struct xcursor_images *, void *), void *user_data) { FILE *f; DIR *dir = opendir(path); struct dirent *ent; char *full; struct xcursor_images *images; if (!dir) return; for (ent = readdir(dir); ent; ent = readdir(dir)) { #ifdef _DIRENT_HAVE_D_TYPE if (ent->d_type != DT_UNKNOWN && ent->d_type != DT_REG && ent->d_type != DT_LNK) continue; #endif full = xcursor_build_fullname(path, "", ent->d_name); if (!full) continue; f = fopen(full, "r"); if (!f) { free(full); continue; } images = xcursor_xc_file_load_images(f, size); if (images) { images->name = strdup(ent->d_name); load_callback(images, user_data); } fclose(f); free(full); } closedir(dir); } /** Load all the cursor of a theme * * This function loads all the cursor images of a given theme and its * inherited themes. Each cursor is loaded into an struct xcursor_images object * which is passed to the caller's load callback. If a cursor appears * more than once across all the inherited themes, the load callback * will be called multiple times, with possibly different struct xcursor_images * object which have the same name. The user is expected to destroy the * struct xcursor_images objects passed to the callback with * xcursor_images_destroy(). * * \param theme The name of theme that should be loaded * \param size The desired size of the cursor images * \param load_callback A callback function that will be called * for each cursor loaded. The first parameter is the struct xcursor_images * object representing the loaded cursor and the second is a pointer * to data provided by the user. * \param user_data The data that should be passed to the load callback */ void xcursor_load_theme(const char *theme, int size, void (*load_callback)(struct xcursor_images *, void *), void *user_data) { char *full, *dir; char *inherits = NULL; const char *path, *i; char *xcursor_path; if (!theme) theme = "default"; xcursor_path = xcursor_library_path(); for (path = xcursor_path; path; path = xcursor_next_path(path)) { dir = xcursor_build_theme_dir(path, theme); if (!dir) continue; full = xcursor_build_fullname(dir, "cursors", ""); load_all_cursors_from_dir(full, size, load_callback, user_data); free(full); if (!inherits) { full = xcursor_build_fullname(dir, "", "index.theme"); inherits = xcursor_theme_inherits(full); free(full); } free(dir); } for (i = inherits; i; i = xcursor_next_path(i)) xcursor_load_theme(i, size, load_callback, user_data); free(inherits); free(xcursor_path); }