diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2021-11-17 13:45:42 +0100 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2021-11-17 13:49:15 +0100 | 
| commit | db7a28b187aa4ef8fee1a325bc493a16694a850b (patch) | |
| tree | 96ebced6de94cdf3ad01b829a892d097f8c0691f | |
| parent | e8754f43a602f76fdbd35cdd24f9cfeeb4cdcf2b (diff) | |
| download | fiv-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.adoc | 2 | ||||
| -rw-r--r-- | fastiv-io.c | 140 | ||||
| -rw-r--r-- | fastiv-view.c | 5 | ||||
| -rw-r--r-- | fastiv.desktop | 2 | ||||
| -rw-r--r-- | meson.build | 3 | ||||
| -rw-r--r-- | meson_options.txt | 2 | 
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')  | 
