From db7a28b187aa4ef8fee1a325bc493a16694a850b Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Wed, 17 Nov 2021 13:45:42 +0100 Subject: Add support for opening Xcursor files Sadly, they don't have a canonical extension, and they don't show up in the browser. We might want to employ some level of sniffing. The first 16 bytes are enough to identify a lot. --- fastiv-io.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) (limited to 'fastiv-io.c') diff --git a/fastiv-io.c b/fastiv-io.c index ee43cd8..fe725a8 100644 --- a/fastiv-io.c +++ b/fastiv-io.c @@ -30,6 +30,9 @@ #ifdef HAVE_LIBRSVG #include #endif // HAVE_LIBRSVG +#ifdef HAVE_XCURSOR +#include +#endif // HAVE_XCURSOR #ifdef HAVE_GDKPIXBUF #include #include @@ -69,6 +72,9 @@ const char *fastiv_io_supported_media_types[] = { #ifdef HAVE_LIBRSVG "image/svg+xml", #endif // HAVE_LIBRSVG +#ifdef HAVE_XCURSOR + "image/x-xcursor", +#endif // HAVE_XCURSOR NULL }; @@ -566,6 +572,132 @@ open_librsvg(const gchar *data, gsize len, const gchar *path, GError **error) } #endif // HAVE_LIBRSVG -------------------------------------------------------- +#ifdef HAVE_XCURSOR //--------------------------------------------------------- + +// fmemopen is part of POSIX-1.2008, this exercise is technically unnecessary. +// libXcursor checks for EOF rather than -1, it may eat your hamster. +struct fastiv_io_xcursor { + XcursorFile parent; + unsigned char *data; + long position, len; +}; + +static int +fastiv_io_xcursor_read(XcursorFile *file, unsigned char *buf, int len) +{ + struct fastiv_io_xcursor *fix = (struct fastiv_io_xcursor *) file; + if (fix->position < 0 || fix->position > fix->len) { + errno = EOVERFLOW; + return -1; + } + long n = MIN(fix->len - fix->position, len); + if (n > G_MAXINT) { + errno = EIO; + return -1; + } + memcpy(buf, fix->data + fix->position, n); + fix->position += n; + return n; +} + +static int +fastiv_io_xcursor_write(G_GNUC_UNUSED XcursorFile *file, + G_GNUC_UNUSED unsigned char *buf, G_GNUC_UNUSED int len) +{ + errno = EBADF; + return -1; +} + +static int +fastiv_io_xcursor_seek(XcursorFile *file, long offset, int whence) +{ + struct fastiv_io_xcursor *fix = (struct fastiv_io_xcursor *) file; + switch (whence) { + case SEEK_SET: + fix->position = offset; + break; + case SEEK_CUR: + fix->position += offset; + break; + case SEEK_END: + fix->position = fix->len + offset; + break; + default: + errno = EINVAL; + return -1; + } + // This is technically too late for fseek(), but libXcursor doesn't care. + if (fix->position < 0) { + errno = EINVAL; + return -1; + } + return fix->position; +} + +static const XcursorFile fastiv_io_xcursor_adaptor = { + .closure = NULL, + .read = fastiv_io_xcursor_read, + .write = fastiv_io_xcursor_write, + .seek = fastiv_io_xcursor_seek, +}; + +static cairo_surface_t * +open_xcursor(const gchar *data, gsize len, GError **error) +{ + if (len > G_MAXLONG) { + set_error(error, "size overflow"); + return NULL; + } + + struct fastiv_io_xcursor file = { + .parent = fastiv_io_xcursor_adaptor, + .data = (unsigned char *) data, + .position = 0, + .len = len, + }; + + XcursorImages *images = XcursorXcFileLoadAllImages(&file.parent); + if (!images) { + set_error(error, "general failure"); + return NULL; + } + + // Let's just arrange the pixel data in a recording surface. + static cairo_user_data_key_t key = {}; + cairo_surface_t *recording = + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, NULL); + cairo_surface_set_user_data( + recording, &key, images, (cairo_destroy_func_t) XcursorImagesDestroy); + cairo_t *cr = cairo_create(recording); + + // Unpack horizontally by animation, vertically by size. + XcursorDim last_nominal = -1; + int x = 0, y = 0, row_height = 0; + for (int i = 0; i < images->nimage; i++) { + XcursorImage *image = images->images[i]; + if (image->size != last_nominal) { + x = 0; + y += row_height; + row_height = 0; + last_nominal = image->size; + } + + // TODO(p): Byte-swap if on big-endian. Wuffs doesn't even build there. + cairo_surface_t *source = cairo_image_surface_create_for_data( + (unsigned char *) image->pixels, CAIRO_FORMAT_ARGB32, + image->width, image->height, image->width * sizeof *image->pixels); + cairo_set_source_surface(cr, source, x, y); + cairo_surface_destroy(source); + cairo_paint(cr); + + x += image->width; + row_height = MAX(row_height, (int) image->height); + } + cairo_destroy(cr); + return recording; +} + +#endif // HAVE_XCURSOR -------------------------------------------------------- #ifdef HAVE_GDKPIXBUF // ------------------------------------------------------ static cairo_surface_t * @@ -663,6 +795,14 @@ fastiv_io_open_from_data(const char *data, size_t len, const gchar *path, g_clear_error(error); } #endif // HAVE_LIBRSVG -------------------------------------------------------- +#ifdef HAVE_XCURSOR //--------------------------------------------------------- + if ((surface = open_xcursor(data, len, error))) + break; + if (error) { + g_debug("%s", (*error)->message); + g_clear_error(error); + } +#endif // HAVE_XCURSOR -------------------------------------------------------- #ifdef HAVE_GDKPIXBUF // ------------------------------------------------------ // This is only used as a last resort, the rest above is special-cased. if ((surface = open_gdkpixbuf(data, len, error)) || -- cgit v1.2.3-54-g00ecf