diff options
author | dec05eba <dec05eba@protonmail.com> | 2023-12-13 20:17:13 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2023-12-13 20:36:41 +0100 |
commit | bc5a4cf5fcc70e30028ff8fa91dca075896b2764 (patch) | |
tree | 24b7fe9cc18a8deb3b30ee6f1265152ba8ad628f /depends/libxcb-cursor/xcb | |
parent | 4d639d934ec56b53e8f4776a7a0794f05b5451b6 (diff) |
Include all libxcb utils (wm, util, image, errors, cursor)
Diffstat (limited to 'depends/libxcb-cursor/xcb')
-rw-r--r-- | depends/libxcb-cursor/xcb/cursor.c | 179 | ||||
-rw-r--r-- | depends/libxcb-cursor/xcb/cursor.h | 169 | ||||
-rw-r--r-- | depends/libxcb-cursor/xcb/load_cursor.c | 290 | ||||
-rw-r--r-- | depends/libxcb-cursor/xcb/parse_cursor_file.c | 198 | ||||
-rw-r--r-- | depends/libxcb-cursor/xcb/xcb_cursor.h | 124 |
5 files changed, 960 insertions, 0 deletions
diff --git a/depends/libxcb-cursor/xcb/cursor.c b/depends/libxcb-cursor/xcb/cursor.c new file mode 100644 index 0000000..2f56f19 --- /dev/null +++ b/depends/libxcb-cursor/xcb/cursor.c @@ -0,0 +1,179 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * Copyright © 2013 Michael Stapelberg + * + * 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 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 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. + * + * Except as contained in this notice, the names of the authors or their + * institutions shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization from the authors. + * + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <xcb/xcb.h> +#include <xcb/xcb_renderutil.h> + +#include "cursor.h" +#include "xcb_cursor.h" + +/* + * Parses the root window’s RESOURCE_MANAGER atom contents and stores the + * attributes declared above in resource_manager_val. + * + */ +static void parse_resource_manager(xcb_cursor_context_t *c, const xcb_get_property_reply_t *rm_reply) { + int rm_length; + char *rm = NULL; + char *saveptr = NULL; + char *line = NULL; + char *sep = NULL; + + if (rm_reply == NULL || (rm_length = xcb_get_property_value_length(rm_reply)) == 0) + return; + + if (asprintf(&rm, "%.*s", rm_length, (char*)xcb_get_property_value(rm_reply)) == -1) + return; + + for (char *str = rm; ; str = NULL) { + if ((line = strtok_r(str, "\n", &saveptr)) == NULL) + break; + /* Split the string at the delimiting : */ + if ((sep = strchr(line, ':')) == NULL) { + /* Invalid line?! */ + free(rm); + return; + } + *(sep++) = '\0'; + while (isspace(*sep)) + sep++; + /* strdup() may return NULL, which is interpreted later as the key not + * being available. */ + if (strcmp(line, "Xcursor.theme") == 0) { + free(c->rm[RM_XCURSOR_THEME]); + c->rm[RM_XCURSOR_THEME] = strdup(sep); + } else if (strcmp(line, "Xcursor.size") == 0) { + free(c->rm[RM_XCURSOR_SIZE]); + c->rm[RM_XCURSOR_SIZE] = strdup(sep); + } else if (strcmp(line, "Xft.dpi") == 0) { + free(c->rm[RM_XFT_DPI]); + c->rm[RM_XFT_DPI] = strdup(sep); + } + } + + free(rm); +} + +/* + * Tries to figure out the cursor size by checking: + * 1. The environment variable XCURSOR_SIZE + * 2. The RESOURCE_MANAGER entry Xcursor.size + * 3. Guess with the RESOURCE_MANAGER entry Xft.dpi * 16 / 72 + * 4. Guess with the display size. + * + */ +static uint32_t get_default_size(xcb_cursor_context_t *c, xcb_screen_t *screen) { + char *env; + uint16_t dim; + + if ((env = getenv("XCURSOR_SIZE")) != NULL) + return atoi(env); + + if (c->rm[RM_XCURSOR_SIZE] != NULL) + return atoi(c->rm[RM_XCURSOR_SIZE]); + + if (c->rm[RM_XFT_DPI] != NULL) { + const int dpi = atoi(c->rm[RM_XFT_DPI]); + if (dpi > 0) + return dpi * 16 / 72; + } + + if (screen->height_in_pixels < screen->width_in_pixels) + dim = screen->height_in_pixels; + else + dim = screen->width_in_pixels; + + return dim / 48; +} + +int xcb_cursor_context_new(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_context_t **ctx) { + xcb_cursor_context_t *c; + const xcb_query_extension_reply_t *ext; + xcb_get_property_cookie_t rm_cookie; + xcb_get_property_reply_t *rm_reply; + xcb_render_query_pict_formats_cookie_t pf_cookie; + xcb_render_query_version_cookie_t ver_cookie; + + if ((*ctx = calloc(1, sizeof(struct xcb_cursor_context_t))) == NULL) + return -errno; + + c = *ctx; + c->conn = conn; + c->root = screen->root; + c->render_version = RV_NONE; + + ext = xcb_get_extension_data(conn, &xcb_render_id); + + // XXX: Is it maybe necessary to ever use long_offset != 0? + // XXX: proper length? xlib seems to use 100 MB o_O + rm_cookie = xcb_get_property(conn, 0, c->root, XCB_ATOM_RESOURCE_MANAGER, XCB_ATOM_STRING, 0, 16 * 1024); + if (ext && ext->present) { + ver_cookie = xcb_render_query_version(conn, XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION); + pf_cookie = xcb_render_query_pict_formats(conn); + } + c->cursor_font = xcb_generate_id(conn); + xcb_open_font(conn, c->cursor_font, strlen("cursor"), "cursor"); + + rm_reply = xcb_get_property_reply(conn, rm_cookie, NULL); + parse_resource_manager(c, rm_reply); + free(rm_reply); + + if (ext && ext->present) { + xcb_render_query_version_reply_t *reply = xcb_render_query_version_reply(conn, ver_cookie, NULL); + + if (reply && (reply->major_version >= 1 || reply->minor_version >= 8)) + c->render_version = RV_ANIM_CURSOR; + else if (reply && (reply->major_version >= 1 || reply->minor_version >= 5)) + c->render_version = RV_CURSOR; + free(reply); + + c->pf_reply = xcb_render_query_pict_formats_reply(conn, pf_cookie, NULL); + c->pict_format = xcb_render_util_find_standard_format(c->pf_reply, XCB_PICT_STANDARD_ARGB_32); + } + + c->size = get_default_size(c, screen); + + return 0; +} + +void xcb_cursor_context_free(xcb_cursor_context_t *c) { + free(c->rm[RM_XCURSOR_THEME]); + free(c->rm[RM_XCURSOR_SIZE]); + free(c->rm[RM_XFT_DPI]); + free(c->pf_reply); + free(c); +} diff --git a/depends/libxcb-cursor/xcb/cursor.h b/depends/libxcb-cursor/xcb/cursor.h new file mode 100644 index 0000000..455dc34 --- /dev/null +++ b/depends/libxcb-cursor/xcb/cursor.h @@ -0,0 +1,169 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * Copyright © 2013 Michael Stapelberg + * + * 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 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 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. + * + * Except as contained in this notice, the names of the authors or their + * institutions shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization from the authors. + * + */ +#ifndef CURSOR_H +#define CURSOR_H + +#include <xcb/render.h> + +#include "xcb_cursor.h" + +enum { + RM_XCURSOR_THEME = 0, + RM_XCURSOR_SIZE, + RM_XFT_DPI, + RM_MAX, +}; + +enum render_version { + RV_NONE = 0, + /* RENDER's CreateCursor was added in RENDER 0.5 */ + RV_CURSOR, + /* RENDER's CreateAnimCursor was added in RENDER 0.8 */ + RV_ANIM_CURSOR +}; + +struct xcb_cursor_context_t { + xcb_connection_t *conn; + xcb_window_t root; + + xcb_font_t cursor_font; + + xcb_render_query_pict_formats_reply_t *pf_reply; + + /* This is a pointer into pf_reply. */ + xcb_render_pictforminfo_t *pict_format; + + /* Specific values of the root window’s RESOURCE_MANAGER atom contents. */ + char *rm[RM_MAX]; + + /* Best cursor size. If a file contains multiple cursor images, the images + * which match the size best will be loaded. */ + uint32_t size; + + const char *home; + const char *path; + + enum render_version render_version; +}; + +/* + * 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 + */ + +/* little-endian */ +#define XCURSOR_MAGIC 0x72756358 + +typedef struct xcint_file_header_t { + uint32_t magic; + uint32_t header; + uint32_t version; + uint32_t ntoc; +} __attribute__((packed)) xcint_file_header_t; + +typedef struct xcint_file_toc_t { + uint32_t type; + uint32_t subtype; + uint32_t position; +} __attribute__((packed)) xcint_file_toc_t; + +typedef struct xcint_cursor_file_t { + xcint_file_header_t header; + xcint_file_toc_t *tocs; +} xcint_cursor_file_t; + +/* + * The rest of the file is a list of chunks, each tagged by type + * and version. + * + * Chunk: + * ChunkHeader + * <extra type-specific header fields> + * <type-specific data> + * + * ChunkHeader: + * CARD32 header bytes in chunk header + type header + * CARD32 type chunk type + * CARD32 subtype chunk subtype + * CARD32 version chunk type version + */ + +typedef struct xcint_chunk_header_t { + uint32_t header; + uint32_t type; + uint32_t subtype; + uint32_t version; +} __attribute__((packed)) xcint_chunk_header_t; + +#define XCURSOR_IMAGE_TYPE 0xfffd0002 +#define XCURSOR_IMAGE_VERSION 1 +#define XCURSOR_IMAGE_MAX_SIZE 0x7fff /* 32767x32767 max cursor size */ + +typedef struct xcint_image_t { + uint32_t width; + uint32_t height; + uint32_t xhot; + uint32_t yhot; + uint32_t delay; + uint32_t *pixels; +} __attribute__((packed)) xcint_image_t; + +/* shape_to_id.c */ +const int cursor_shape_to_id(const char *name); + +/* parse_cursor_file.c */ +int parse_cursor_file(xcb_cursor_context_t *c, const int fd, xcint_image_t **images, int *nimg); + +#endif diff --git a/depends/libxcb-cursor/xcb/load_cursor.c b/depends/libxcb-cursor/xcb/load_cursor.c new file mode 100644 index 0000000..0e96f8a --- /dev/null +++ b/depends/libxcb-cursor/xcb/load_cursor.c @@ -0,0 +1,290 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * Copyright © 2013 Michael Stapelberg + * 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 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 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. + * + * Except as contained in this notice, the names of the authors or their + * institutions shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization from the authors. + * + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <xcb/xcb.h> +#include <xcb/render.h> +#include <xcb/xcb_image.h> + +#include "cursor.h" +#include "xcb_cursor.h" + +#ifdef O_CLOEXEC +#define FOPEN_CLOEXEC "e" +#else +#define FOPEN_CLOEXEC "" +#define O_CLOEXEC 0 +#endif + +#define XCURSORPATH "~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons" + +static const char *cursor_path(struct xcb_cursor_context_t *c) { + if (c->path == NULL) { + c->path = getenv("XCURSOR_PATH"); + if (c->path == NULL) + c->path = XCURSORPATH; + } + return c->path; +} + +static const char *next_path(const char *path) { + const char *colon = strchr(path, ':'); + return (colon ? colon + 1 : NULL); +} + +/* + * _XcursorThemeInherits was directly copied from libxcursor so as to not break + * compatibility. + * + */ +#define XcursorWhite(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') +#define XcursorSep(c) ((c) == ';' || (c) == ',') + +static char * +_XcursorThemeInherits (const char *full) +{ + char line[8192]; + char *result = NULL; + FILE *f; + + if (!full) + return NULL; + + f = fopen (full, "r" FOPEN_CLOEXEC); + if (f) + { + while (fgets (line, sizeof (line), f)) + { + if (!strncmp (line, "Inherits", 8)) + { + char *l = line + 8; + while (*l == ' ') l++; + if (*l != '=') continue; + l++; + while (*l == ' ') l++; + result = malloc (strlen (l) + 1); + if (result) + { + char *r = result; + while (*l) + { + while (XcursorSep(*l) || XcursorWhite (*l)) l++; + if (!*l) + break; + if (r != result) + *r++ = ':'; + while (*l && !XcursorWhite(*l) && + !XcursorSep(*l)) + *r++ = *l++; + } + *r++ = '\0'; + } + break; + } + } + fclose (f); + } + return result; +} + +/* + * Tries to open the cursor file “name” in the “theme”/cursors subfolder of + * each component of cursor_path(). When the file cannot be found, but a file + * “index.theme” in the component is present, the Inherits= key will be + * extracted and open_cursor_file calls itself recursively to search the + * specified inherited themes, too. + * + */ +static int open_cursor_file(xcb_cursor_context_t *c, const char *theme, const char *name, int *scan_core) { + int fd = -1; + char *inherits = NULL; + + *scan_core = -1; + + if (strcmp(theme, "core") == 0 && + (*scan_core = cursor_shape_to_id(name)) >= 0) { + return -1; + } + + if (c->home == NULL) + if ((c->home = getenv("HOME")) == NULL) + return -1; + + for (const char *path = cursor_path(c); + (path != NULL && fd == -1); + ) { + const char *sep = strchr(path, ':'); + const int pathlen = (sep ? (sep - path) : strlen(path)); + char *themedir = NULL; + char *full = NULL; + if (*path == '~') { + if (asprintf(&themedir, "%s%.*s/%s", c->home, pathlen - 1, path + 1, theme) == -1) + return -1; + } else { + if (asprintf(&themedir, "%.*s/%s", pathlen, path, theme) == -1) + return -1; + } + if (asprintf(&full, "%s/%s/%s", themedir, "cursors", name) == -1) { + free(themedir); + return -1; + } + fd = open(full, O_RDONLY | O_CLOEXEC); + free(full); + if (fd == -1 && inherits == NULL) { + if (asprintf(&full, "%s/index.theme", themedir) == -1) { + free(themedir); + return -1; + } + inherits = _XcursorThemeInherits(full); + free(full); + } + free(themedir); + path = (sep ? sep + 1 : NULL); + } + + for (const char *path = inherits; + (path != NULL && fd == -1); + (path = next_path(path))) { + fd = open_cursor_file(c, path, name, scan_core); + } + + if (inherits != NULL) + free(inherits); + + return fd; +} + +xcb_cursor_t xcb_cursor_load_cursor(xcb_cursor_context_t *c, const char *name) { + /* The character id of the X11 "cursor" font when falling back to un-themed + * cursors. */ + int core_char = -1; + int fd = -1; + xcint_image_t *images; + int nimg = 0; + xcb_pixmap_t pixmap = XCB_NONE; + xcb_gcontext_t gc = XCB_NONE; + uint32_t last_width = 0; + uint32_t last_height = 0; + xcb_cursor_t cid = XCB_NONE; + + // NB: if !render_present, fd will be -1 and thus the next if statement + // will trigger the fallback. + if (c->render_version != RV_NONE) { + if (c->rm[RM_XCURSOR_THEME]) + fd = open_cursor_file(c, c->rm[RM_XCURSOR_THEME], name, &core_char); + + if (fd == -1 && core_char == -1) + fd = open_cursor_file(c, "default", name, &core_char); + } + + if (fd == -1 || core_char > -1) { + if (core_char == -1) + core_char = cursor_shape_to_id(name); + if (core_char == -1) + return XCB_NONE; + + cid = xcb_generate_id(c->conn); + xcb_create_glyph_cursor(c->conn, cid, c->cursor_font, c->cursor_font, core_char, core_char + 1, 0, 0, 0, 65535, 65535, 65535); + return cid; + } + + if (parse_cursor_file(c, fd, &images, &nimg) < 0) { + close(fd); + return XCB_NONE; + } + + close(fd); + + /* create a cursor from it */ + xcb_render_animcursorelt_t elements[nimg]; + xcb_render_picture_t pic = xcb_generate_id(c->conn); + + for (int n = 0; n < nimg; n++) { + xcint_image_t *i = &(images[n]); + xcb_image_t *img = xcb_image_create_native(c->conn, i->width, i->height, XCB_IMAGE_FORMAT_Z_PIXMAP, 32, NULL, (i->width * i->height * sizeof(uint32_t)), (uint8_t*)i->pixels); + + if (pixmap == XCB_NONE || + (i->width != last_width) || + (i->height != last_height)) { + if (pixmap == XCB_NONE) { + pixmap = xcb_generate_id(c->conn); + gc = xcb_generate_id(c->conn); + } else { + xcb_free_pixmap(c->conn, pixmap); + xcb_free_gc(c->conn, gc); + } + + xcb_create_pixmap(c->conn, 32, pixmap, c->root, i->width, i->height); + xcb_create_gc(c->conn, gc, pixmap, 0, NULL); + + last_width = i->width; + last_height = i->height; + } + + xcb_image_put(c->conn, pixmap, gc, img, 0, 0, 0); + + xcb_render_create_picture(c->conn, pic, pixmap, c->pict_format->id, 0, NULL); + + elements[n].cursor = xcb_generate_id(c->conn); + elements[n].delay = i->delay; + + xcb_render_create_cursor(c->conn, elements[n].cursor, pic, i->xhot, i->yhot); + + xcb_render_free_picture(c->conn, pic); + xcb_image_destroy(img); + free(i->pixels); + } + + xcb_free_pixmap(c->conn, pixmap); + xcb_free_gc(c->conn, gc); + free(images); + + if (nimg == 1 || c->render_version == RV_CURSOR) { + /* non-animated cursor or no support for animated cursors */ + return elements[0].cursor; + } else { + cid = xcb_generate_id(c->conn); + xcb_render_create_anim_cursor (c->conn, cid, nimg, elements); + + for (int n = 0; n < nimg; n++) { + xcb_free_cursor(c->conn, elements[n].cursor); + } + + return cid; + } +} diff --git a/depends/libxcb-cursor/xcb/parse_cursor_file.c b/depends/libxcb-cursor/xcb/parse_cursor_file.c new file mode 100644 index 0000000..21f72bf --- /dev/null +++ b/depends/libxcb-cursor/xcb/parse_cursor_file.c @@ -0,0 +1,198 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * Copyright © 2013 Michael Stapelberg + * + * 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 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 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. + * + * Except as contained in this notice, the names of the authors or their + * institutions shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization from the authors. + * + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdbool.h> + +#ifdef HAVE_ENDIAN_H +#include <endian.h> +#endif +#ifdef HAVE_SYS_ENDIAN_H +#include <sys/endian.h> +#endif +#ifdef HAVE_SYS_BYTEORDER_H +#include <sys/byteorder.h> +# ifndef HAVE_LE32TOH +# define le32toh(x) LE_32(x) +# endif +#elif defined(HAVE_LIBKERN_OSBYTEORDER_H) +#include <libkern/OSByteOrder.h> +#define le32toh(x) OSSwapLittleToHostInt32(x) +#endif + +#include <xcb/xcb.h> + +#include "cursor.h" +#include "xcb_cursor.h" + +static uint32_t dist(const uint32_t a, const uint32_t b) { + return (a > b ? (a - b) : (b - a)); +} + +static uint32_t find_best_size(xcint_cursor_file_t *cf, const uint32_t target, uint32_t *nsizesp) { + uint32_t best = 0; + /* Amount of cursors with the best size */ + uint32_t nsizes = 0; + for (int n = 0; n < cf->header.ntoc; n++) { + const uint32_t size = cf->tocs[n].subtype; + + if (cf->tocs[n].type != XCURSOR_IMAGE_TYPE) + continue; + + /* If the distance is less to the target size, this is a better fit. */ + if (best == 0 || dist(size, target) < dist(best, target)) { + best = size; + nsizes = 0; + } + + if (size == best) + nsizes++; + } + + *nsizesp = nsizes; + return best; +} + +/* Returns true if and only if the read() call read the entirety of the data it + * was supposed to read. */ +static bool read_entirely(int fd, void *buf, size_t count) { + return read(fd, buf, count) == count; +} + +int parse_cursor_file(xcb_cursor_context_t *c, const int fd, xcint_image_t **images, int *nimg) { + /* Read the header, verify the magic value. */ + xcint_cursor_file_t cf; + uint32_t nsizes = 0; + uint32_t best = 0; + uint32_t skip = 0; + /* The amount of images stored in 'images', used when cleaning up. */ + int cnt = 0; + + if (!read_entirely(fd, &(cf.header), sizeof(xcint_file_header_t))) + return -EINVAL; + + cf.header.magic = le32toh(cf.header.magic); + cf.header.header = le32toh(cf.header.header); + cf.header.version = le32toh(cf.header.version); + cf.header.ntoc = le32toh(cf.header.ntoc); + + if (cf.header.magic != XCURSOR_MAGIC) + return -EINVAL; + + if ((skip = (cf.header.header - sizeof(xcint_file_header_t))) > 0) + if (lseek(fd, skip, SEEK_CUR) == EOF) + return -EINVAL; + + if (cf.header.ntoc > 0x10000) + return -EINVAL; + + /* Read the table of contents */ + cf.tocs = malloc(cf.header.ntoc * sizeof(xcint_file_toc_t)); + if (!read_entirely(fd, cf.tocs, cf.header.ntoc * sizeof(xcint_file_toc_t))) + goto error; + + for (int n = 0; n < cf.header.ntoc; n++) { + cf.tocs[n].type = le32toh(cf.tocs[n].type); + cf.tocs[n].subtype = le32toh(cf.tocs[n].subtype); + cf.tocs[n].position = le32toh(cf.tocs[n].position); + } + + /* No images? Invalid file. */ + if ((best = find_best_size(&cf, c->size, &nsizes)) == 0 || nsizes == 0) + goto error; + + *nimg = nsizes; + if ((*images = calloc(nsizes, sizeof(xcint_image_t))) == NULL) + goto error; + + for (int n = 0; n < cf.header.ntoc; n++) { + xcint_chunk_header_t chunk; + /* for convenience */ + xcint_image_t *i = &((*images)[cnt]); + uint32_t numpixels = 0; + uint32_t *p = NULL; + + if (cf.tocs[n].type != XCURSOR_IMAGE_TYPE || + cf.tocs[n].subtype != best) + continue; + + lseek(fd, cf.tocs[n].position, SEEK_SET); + if (!read_entirely(fd, &chunk, sizeof(xcint_chunk_header_t))) + goto error2; + chunk.header = le32toh(chunk.header); + chunk.type = le32toh(chunk.type); + chunk.subtype = le32toh(chunk.subtype); + chunk.version = le32toh(chunk.version); + /* Sanity check, as libxcursor does it. */ + if (chunk.type != cf.tocs[n].type || + chunk.subtype != cf.tocs[n].subtype) + goto error2; + if (!read_entirely(fd, i, sizeof(xcint_image_t) - sizeof(uint32_t*))) // TODO: better type + goto error2; + i->width = le32toh(i->width); + i->height = le32toh(i->height); + i->xhot = le32toh(i->xhot); + i->yhot = le32toh(i->yhot); + i->delay = le32toh(i->delay); + + /* Read the actual image data and convert it to host byte order */ + /* Catch integer overflows */ + if (((uint64_t)i->width) * i->height > UINT32_MAX) + goto error2; + numpixels = i->width * i->height; + i->pixels = malloc(numpixels * sizeof(uint32_t)); + /* With the malloc, one more image is eligible for cleanup later. */ + cnt++; + if (!read_entirely(fd, i->pixels, numpixels * sizeof(uint32_t))) + goto error2; + p = i->pixels; + for (int j = 0; j < numpixels; j++, p++) + *p = le32toh(*p); + } + + free(cf.tocs); + return 0; + +error2: + /* Free the memory for all images that were read so far. */ + for (int n = 0; n < cnt; n++) + free((*images)[n].pixels); + free(*images); +error: + *images = NULL; + free(cf.tocs); + return -EINVAL; +} diff --git a/depends/libxcb-cursor/xcb/xcb_cursor.h b/depends/libxcb-cursor/xcb/xcb_cursor.h new file mode 100644 index 0000000..8752a9c --- /dev/null +++ b/depends/libxcb-cursor/xcb/xcb_cursor.h @@ -0,0 +1,124 @@ +#ifndef XCB_CURSOR_H +#define XCB_CURSOR_H + +/* Copyright © 2013 Michael Stapelberg + * + * 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 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 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. + * + * Except as contained in this notice, the names of the authors or their + * institutions shall not be used in advertising or otherwise to promote the + * sale, use or other dealings in this Software without prior written + * authorization from the authors. + */ + +#include <xcb/xcb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup xcb__cursor_context_t XCB Cursor Functions + * + * These functions are the equivalent of libXcursor, but re-implemented for + * XCB. They respect the user’s configured cursor theme when loading cursors, + * specified by the X resources setting "Xcursor.theme". + * + * Here is how you would use these functions to change the X11 root window + * cursor to "watch": + * @code + * int screennr; + * xcb_connection_t *conn = xcb_connect(NULL, &screennr); + * if (conn == NULL || xcb_connection_has_error(conn)) + * err(EXIT_FAILURE, "Could not connect to X11"); + * + * xcb_screen_t *screen = xcb_aux_get_screen(conn, screennr); + * xcb_cursor_context_t *ctx; + * if (xcb_cursor_context_new(conn, screen, &ctx) < 0) + * err(EXIT_FAILURE, "Could not initialize xcb-cursor"); + * + * xcb_cursor_t cid = xcb_cursor_load_cursor(ctx, "watch"); + * + * xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + * xcb_change_window_attributes(conn, screen->root, XCB_CW_CURSOR, (uint32_t[]){ cid }); + * xcb_free_cursor(conn, cid); + * xcb_flush(conn); + * + * xcb_cursor_context_free(ctx); + * xcb_disconnect(conn); + * @endcode + * + * @{ + */ + +/** + * @struct xcb_cursor_context_t + * Describes a context for using this library. + * + * Create a context with @ref xcb_cursor_context_new (), then load one or more + * cursors with @ref xcb_cursor_load_cursor () and destroy the context with @ref + * xcb_cursor_context_free (). + */ +typedef struct xcb_cursor_context_t xcb_cursor_context_t; + +/** + * Create a new @ref xcb_cursor_context_t. + * + * @param conn A working XCB connection, which will be used until you destroy + * the context with @ref xcb_cursor_context_free (). + * @param screen The xcb_screen_t to use (e.g. for getting the RESOURCE_MANAGER + * contents, for creating cursors on, for using the size as fallback when + * calculating the best cursor size). + * @param ctx A pointer to an xcb_cursor_context_t* which will be modified to + * refer to the newly created context. + * @return 0 on success, a negative error code otherwise. + * + * @ingroup xcb_cursor_context_t + */ +int xcb_cursor_context_new(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_context_t **ctx); + +/** + * Loads the specified cursor, either from the cursor theme or by falling back + * to the X11 "cursor" font. + * + * @param ctx A cursor context, created with @ref xcb_cursor_context_new () + * @param name The name of the cursor to load, e.g. "watch". + * @returns The ID of the created cursor. When you are done using it, use + * xcb_free_cursor. Calling @ref xcb_cursor_context_free () will NOT free the + * created cursor. + * + */ +xcb_cursor_t xcb_cursor_load_cursor(xcb_cursor_context_t *ctx, const char *name); + +/** + * Frees the @ref xcb_cursor_context_t. + * + * @param ctx The context to free. + * + */ +void xcb_cursor_context_free(xcb_cursor_context_t *ctx); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif |