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.
This commit is contained in:
parent
e8754f43a6
commit
db7a28b187
|
@ -20,7 +20,7 @@ Building and Running
|
||||||
Build dependencies: Meson, pkg-config +
|
Build dependencies: Meson, pkg-config +
|
||||||
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
||||||
spng>=0.7.0, libturbojpeg, LibRaw (optional), librsvg-2.0 (optional),
|
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
|
$ git clone --recursive https://git.janouch.name/p/fastiv.git
|
||||||
$ meson builddir
|
$ meson builddir
|
||||||
|
|
140
fastiv-io.c
140
fastiv-io.c
|
@ -30,6 +30,9 @@
|
||||||
#ifdef HAVE_LIBRSVG
|
#ifdef HAVE_LIBRSVG
|
||||||
#include <librsvg/rsvg.h>
|
#include <librsvg/rsvg.h>
|
||||||
#endif // HAVE_LIBRSVG
|
#endif // HAVE_LIBRSVG
|
||||||
|
#ifdef HAVE_XCURSOR
|
||||||
|
#include <X11/Xcursor/Xcursor.h>
|
||||||
|
#endif // HAVE_XCURSOR
|
||||||
#ifdef HAVE_GDKPIXBUF
|
#ifdef HAVE_GDKPIXBUF
|
||||||
#include <gdk/gdk.h>
|
#include <gdk/gdk.h>
|
||||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||||
|
@ -69,6 +72,9 @@ const char *fastiv_io_supported_media_types[] = {
|
||||||
#ifdef HAVE_LIBRSVG
|
#ifdef HAVE_LIBRSVG
|
||||||
"image/svg+xml",
|
"image/svg+xml",
|
||||||
#endif // HAVE_LIBRSVG
|
#endif // HAVE_LIBRSVG
|
||||||
|
#ifdef HAVE_XCURSOR
|
||||||
|
"image/x-xcursor",
|
||||||
|
#endif // HAVE_XCURSOR
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -566,6 +572,132 @@ open_librsvg(const gchar *data, gsize len, const gchar *path, GError **error)
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // HAVE_LIBRSVG --------------------------------------------------------
|
#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 // ------------------------------------------------------
|
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
|
||||||
|
|
||||||
static cairo_surface_t *
|
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);
|
g_clear_error(error);
|
||||||
}
|
}
|
||||||
#endif // HAVE_LIBRSVG --------------------------------------------------------
|
#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 // ------------------------------------------------------
|
#ifdef HAVE_GDKPIXBUF // ------------------------------------------------------
|
||||||
// This is only used as a last resort, the rest above is special-cased.
|
// This is only used as a last resort, the rest above is special-cased.
|
||||||
if ((surface = open_gdkpixbuf(data, len, error)) ||
|
if ((surface = open_gdkpixbuf(data, len, error)) ||
|
||||||
|
|
|
@ -86,7 +86,10 @@ get_surface_dimensions(FastivView *self, double *width, double *height)
|
||||||
*height = cairo_image_surface_get_height(self->surface);
|
*height = cairo_image_surface_get_height(self->surface);
|
||||||
return;
|
return;
|
||||||
case CAIRO_SURFACE_TYPE_RECORDING:
|
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;
|
*width = extents.width;
|
||||||
*height = extents.height;
|
*height = extents.height;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -8,4 +8,4 @@ Terminal=false
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Categories=Graphics;2DGraphics;Viewer;
|
Categories=Graphics;2DGraphics;Viewer;
|
||||||
# TODO(p): Generate this list from source files.
|
# 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;
|
||||||
|
|
|
@ -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.
|
# TODO(p): Use libraw_r later, when we start parallelizing/preloading.
|
||||||
libraw = dependency('libraw', required : get_option('libraw'))
|
libraw = dependency('libraw', required : get_option('libraw'))
|
||||||
librsvg = dependency('librsvg-2.0', required : get_option('librsvg'))
|
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'))
|
gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))
|
||||||
dependencies = [
|
dependencies = [
|
||||||
dependency('gtk+-3.0'),
|
dependency('gtk+-3.0'),
|
||||||
|
@ -12,6 +13,7 @@ dependencies = [
|
||||||
dependency('pixman-1'),
|
dependency('pixman-1'),
|
||||||
libraw,
|
libraw,
|
||||||
librsvg,
|
librsvg,
|
||||||
|
xcursor,
|
||||||
gdkpixbuf,
|
gdkpixbuf,
|
||||||
meson.get_compiler('c').find_library('m', required : false),
|
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_quoted('PROJECT_VERSION', meson.project_version())
|
||||||
conf.set('HAVE_LIBRAW', libraw.found())
|
conf.set('HAVE_LIBRAW', libraw.found())
|
||||||
conf.set('HAVE_LIBRSVG', librsvg.found())
|
conf.set('HAVE_LIBRSVG', librsvg.found())
|
||||||
|
conf.set('HAVE_XCURSOR', xcursor.found())
|
||||||
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
|
conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found())
|
||||||
configure_file(
|
configure_file(
|
||||||
output : 'config.h',
|
output : 'config.h',
|
||||||
|
|
|
@ -2,5 +2,7 @@ option('libraw', type : 'feature', value : 'auto',
|
||||||
description : 'Build with RAW support, requires LibRaw')
|
description : 'Build with RAW support, requires LibRaw')
|
||||||
option('librsvg', type : 'feature', value : 'auto',
|
option('librsvg', type : 'feature', value : 'auto',
|
||||||
description : 'Build with SVG support, requires librsvg')
|
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',
|
option('gdk-pixbuf', type : 'feature', value : 'auto',
|
||||||
description : 'Build with a fallback to the gdk-pixbuf library')
|
description : 'Build with a fallback to the gdk-pixbuf library')
|
||||||
|
|
Loading…
Reference in New Issue