summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <derf@finalrewind.org>2020-11-30 19:21:43 +0100
committerDaniel Friesel <derf@finalrewind.org>2020-11-30 19:27:15 +0100
commit2d37cd91c6718316188e7fd99088cda0e627db74 (patch)
tree756102b8ac6f56dbf6281a03ead023205fd0bef9
parent2e4fc9018d5776bd63d48c2b4824ef04b3235ebc (diff)
Check magic bytes before passing a file to Imlib2's loader
This works around a regression in Imlib2, which makes (un)loadable file detection quite slow when handling e.g. large video files. See <https://phab.enlightenment.org/T8739> and <https://github.com/derf/feh/issues/505> for details. Closes #505
-rw-r--r--man/feh.pre17
-rw-r--r--src/imlib.c103
-rw-r--r--test/feh.t2
3 files changed, 118 insertions, 4 deletions
diff --git a/man/feh.pre b/man/feh.pre
index d44f600..6dfa9e7 100644
--- a/man/feh.pre
+++ b/man/feh.pre
@@ -162,6 +162,23 @@ Use
.Cm --conversion-timeout Ar timeout
with a non-negative value to enable support for these formats.
.
+.Pp
+.
+As Imlib2 may take several seconds to determine whether it can load a file or
+not
+.Pq e.g. when attempting to open a large video ,
+.Nm
+checks each file's header before loading it.
+If it looks like an image, it is passed on to Imlib2, otherwise, it is
+assumed to be unloadable.
+This greatly improves performance when working in directories with mixed files
+.Pq i.e., directories which do not exclusively contain image files .
+If you think that Imlib2 can load a file which
+.Nm
+has determined to be likely not an image, set the environment variable
+.Qq FEH_SKIP_MAGIC
+to pass all files directly to Imlib2, bypassing the header check.
+The environment variable's value does not matter, it just needs to be set.
.
.Sh OPTIONS
.
diff --git a/src/imlib.c b/src/imlib.c
index e06a813..4e8e790 100644
--- a/src/imlib.c
+++ b/src/imlib.c
@@ -181,7 +181,11 @@ void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err)
break;
case IMLIB_LOAD_ERROR_UNKNOWN:
case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
- im_weprintf(w, "%s - No Imlib2 loader for that file format", file);
+ if (getenv("FEH_SKIP_MAGIC")) {
+ im_weprintf(w, "%s - No Imlib2 loader for that file format", file);
+ } else {
+ im_weprintf(w, "%s - Does not look like an image (magic bytes missing)", file);
+ }
break;
case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
im_weprintf(w, "%s - Path specified is too long", file);
@@ -215,6 +219,94 @@ void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err)
}
}
+/*
+ * This is a workaround for an Imlib2 regression, causing unloadable image
+ * detection to be excessively slow (and, thus, causing feh to hang for a while
+ * when encountering an unloadable image). We use magic byte detection to
+ * avoid calling Imlib2 for files it probably cannot handle. See
+ * <https://phab.enlightenment.org/T8739> and
+ * <https://github.com/derf/feh/issues/505>.
+ *
+ * Note that this drops support for bz2-compressed files, unless
+ * FEH_SKIP_MAGIC is set
+ */
+int feh_is_image(feh_file * file)
+{
+ unsigned char buf[16];
+ FILE *fh = fopen(file->filename, "r");
+ if (!fh) {
+ return 0;
+ }
+ if (fread(buf, 1, 16, fh) != 16) {
+ return 0;
+ }
+ fclose(fh);
+
+ if (buf[0] == 0xff && buf[1] == 0xd8) {
+ // JPEG
+ return 1;
+ }
+ if (!memcmp(buf, "\x89PNG\x0d\x0a\x1a\x0a", 8)) {
+ // PNG
+ return 1;
+ }
+ if (buf[0] == 'A' && buf[1] == 'R' && buf[2] == 'G' && buf[3] == 'B') {
+ // ARGB
+ return 1;
+ }
+ if (buf[0] == 'B' && buf[1] == 'M') {
+ // BMP
+ return 1;
+ }
+ if (!memcmp(buf, "farbfeld", 8)) {
+ // farbfeld
+ return 1;
+ }
+ if (buf[0] == 'G' && buf[1] == 'I' && buf[2] == 'F') {
+ // GIF
+ return 1;
+ }
+ if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] <= 0x02 && buf[3] == 0x00) {
+ // ICO
+ return 1;
+ }
+ if (!memcmp(buf, "FORM", 4)) {
+ // Amiga IFF ILBM
+ return 1;
+ }
+ if (buf[0] == 'P' && buf[1] >= '1' && buf[1] <= '7') {
+ // PNM et al.
+ return 1;
+ }
+ if (strstr(file->filename, ".tga")) {
+ // TGA
+ return 1;
+ }
+ if (!memcmp(buf, "II\x2a\x00", 4) || !memcmp(buf, "MM\x00\x2a", 4)) {
+ // TIFF
+ return 1;
+ }
+ if (!memcmp(buf, "RIFF", 4)) {
+ // might be webp
+ return 1;
+ }
+ buf[15] = 0;
+ if (strstr((char *)buf, "XPM")) {
+ // XPM
+ return 1;
+ }
+ if (strstr(file->filename, ".bz2") || strstr(file->filename, ".gz")) {
+ // Imlib2 supports compressed images. It relies on the filename to
+ // determine the appropriate loader and does not use magic bytes here.
+ return 1;
+ }
+ // moved to the end as this variable won't be set in most cases
+ if (getenv("FEH_SKIP_MAGIC")) {
+ return 1;
+ }
+ return 0;
+}
+
int feh_load_image(Imlib_Image * im, feh_file * file)
{
Imlib_Load_Error err = IMLIB_LOAD_ERROR_NONE;
@@ -239,8 +331,13 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
if (!tmpname)
err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT;
}
- else
- *im = imlib_load_image_with_error_return(file->filename, &err);
+ else {
+ if (feh_is_image(file)) {
+ *im = imlib_load_image_with_error_return(file->filename, &err);
+ } else {
+ err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT;
+ }
+ }
if (opt.conversion_timeout >= 0 && (
(err == IMLIB_LOAD_ERROR_UNKNOWN) ||
diff --git a/test/feh.t b/test/feh.t
index baf5ee4..44f705f 100644
--- a/test/feh.t
+++ b/test/feh.t
@@ -47,7 +47,7 @@ if ( $version =~ m{ Compile-time \s switches : \s .* help }ox ) {
}
my $re_warning
- = qr{${feh_name} WARNING: test/fail/... \- No Imlib2 loader for that file format\n};
+ = qr{${feh_name} WARNING: test/fail/... \- Does not look like an image \(magic bytes missing\)\n};
my $re_loadable = qr{test/ok/...};
my $re_unloadable = qr{test/fail/...};
my $re_list_action = qr{test/ok/... 16x16};