aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-11-17 13:45:42 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-11-17 13:49:15 +0100
commitdb7a28b187aa4ef8fee1a325bc493a16694a850b (patch)
tree96ebced6de94cdf3ad01b829a892d097f8c0691f
parente8754f43a602f76fdbd35cdd24f9cfeeb4cdcf2b (diff)
downloadfiv-db7a28b187aa4ef8fee1a325bc493a16694a850b.tar.gz
fiv-db7a28b187aa4ef8fee1a325bc493a16694a850b.tar.xz
fiv-db7a28b187aa4ef8fee1a325bc493a16694a850b.zip
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.
-rw-r--r--README.adoc2
-rw-r--r--fastiv-io.c140
-rw-r--r--fastiv-view.c5
-rw-r--r--fastiv.desktop2
-rw-r--r--meson.build3
-rw-r--r--meson_options.txt2
6 files changed, 151 insertions, 3 deletions
diff --git a/README.adoc b/README.adoc
index dde19d4..f678de4 100644
--- a/README.adoc
+++ b/README.adoc
@@ -20,7 +20,7 @@ Building and Running
Build dependencies: Meson, pkg-config +
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
spng>=0.7.0, libturbojpeg, LibRaw (optional), librsvg-2.0 (optional),
-gdk-pixbuf-2.0 (optional)
+xcursor (optional), gdk-pixbuf-2.0 (optional)
$ git clone --recursive https://git.janouch.name/p/fastiv.git
$ meson builddir
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 <librsvg/rsvg.h>
#endif // HAVE_LIBRSVG
+#ifdef HAVE_XCURSOR
+#include <X11/Xcursor/Xcursor.h>
+#endif // HAVE_XCURSOR
#ifdef HAVE_GDKPIXBUF
#include <gdk/gdk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
@@ -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)) ||
diff --git a/fastiv-view.c b/fastiv-view.c
index 83ecabb..b7be9cd 100644
--- a/fastiv-view.c
+++ b/fastiv-view.c
@@ -86,7 +86,10 @@ get_surface_dimensions(FastivView *self, double *width, double *height)
*height = cairo_image_surface_get_height(self->surface);
return;
case CAIRO_SURFACE_TYPE_RECORDING:
- (void) cairo_recording_surface_get_extents(self->surface, &extents);
+ if (!cairo_recording_surface_get_extents(self->surface, &extents))
+ cairo_recording_surface_ink_extents(self->surface,
+ &extents.x, &extents.y, &extents.width, &extents.height);
+
*width = extents.width;
*height = extents.height;
return;
diff --git a/fastiv.desktop b/fastiv.desktop
index 9ef47d0..894c0cf 100644
--- a/fastiv.desktop
+++ b/fastiv.desktop
@@ -8,4 +8,4 @@ Terminal=false
StartupNotify=true
Categories=Graphics;2DGraphics;Viewer;
# TODO(p): Generate this list from source files.
-MimeType=image/png;image/bmp;image/gif;image/jpeg;image/x-dcraw;image/svg+xml;
+MimeType=image/png;image/bmp;image/gif;image/jpeg;image/x-dcraw;image/svg+xml;image/x-xcursor;
diff --git a/meson.build b/meson.build
index 1125cd9..5a89255 100644
--- a/meson.build
+++ b/meson.build
@@ -3,6 +3,7 @@ project('fastiv', 'c', default_options : ['c_std=gnu99'], version : '0.1.0')
# TODO(p): Use libraw_r later, when we start parallelizing/preloading.
libraw = dependency('libraw', required : get_option('libraw'))
librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))
+xcursor = dependency('xcursor', required : get_option('xcursor'))
gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))
dependencies = [
dependency('gtk+-3.0'),
@@ -12,6 +13,7 @@ dependencies = [
dependency('pixman-1'),
libraw,
librsvg,
+ xcursor,
gdkpixbuf,
meson.get_compiler('c').find_library('m', required : false),
]
@@ -21,6 +23,7 @@ conf.set_quoted('PROJECT_NAME', meson.project_name())
conf.set_quoted('PROJECT_VERSION', meson.project_version())
conf.set('HAVE_LIBRAW', libraw.found())
conf.set('HAVE_LIBRSVG', librsvg.found())
+conf.set('HAVE_XCURSOR', xcursor.found())
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
configure_file(
output : 'config.h',
diff --git a/meson_options.txt b/meson_options.txt
index fc53ac5..b97f848 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -2,5 +2,7 @@ option('libraw', type : 'feature', value : 'auto',
description : 'Build with RAW support, requires LibRaw')
option('librsvg', type : 'feature', value : 'auto',
description : 'Build with SVG support, requires librsvg')
+option('xcursor', type : 'feature', value : 'auto',
+ description : 'Build with Xcursor support, requires libXcursor')
option('gdk-pixbuf', type : 'feature', value : 'auto',
description : 'Build with a fallback to the gdk-pixbuf library')