summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/c.yml10
-rw-r--r--ChangeLog42
-rw-r--r--man/feh.pre62
-rw-r--r--src/feh.h10
-rw-r--r--src/filelist.c11
-rw-r--r--src/imlib.c194
-rw-r--r--src/index.c2
-rw-r--r--src/menu.c17
-rw-r--r--src/slideshow.c93
-rw-r--r--src/thumbnail.c2
-rw-r--r--src/timers.c33
-rw-r--r--src/timers.h2
-rw-r--r--src/winwidget.c7
-rw-r--r--test/feh.t2
14 files changed, 390 insertions, 97 deletions
diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml
index 941075e..e7a4207 100644
--- a/.github/workflows/c.yml
+++ b/.github/workflows/c.yml
@@ -21,16 +21,18 @@ jobs:
steps:
- uses: actions/checkout@v2
+ - name: APT
+ run: sudo apt-get -y update
- name: Install Dependencies
- run: sudo apt install build-essential libx11-dev libxt-dev libimlib2-dev libtest-command-perl libtest-simple-perl
+ run: sudo apt-get -y install build-essential libx11-dev libxt-dev libimlib2-dev libtest-command-perl libtest-simple-perl
- name: Install libcurl
if: matrix.curl
- run: sudo apt install libcurl4-openssl-dev
+ run: sudo apt-get -y install libcurl4-openssl-dev
- name: Install libexif
if: matrix.exif
- run: sudo apt install libexif-dev
+ run: sudo apt-get -y install libexif-dev
- name: Install Xinerama
if: matrix.xinerama
- run: sudo apt install libxinerama-dev
+ run: sudo apt-get -y install libxinerama-dev
- name: Build and Test
run: for inotify in 0 1; do for verscmp in 0 1; do make curl=${{ matrix.curl }} exif=${{ matrix.exif }} inotify=$inotify verscmp=$verscmp xinerama=${{ matrix.xinerama }} && make test && make clean; done; done
diff --git a/ChangeLog b/ChangeLog
index b8291a7..79ea578 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,45 @@
+Sat, 09 Jan 2021 12:28:06 +0100 Daniel Friesel <derf+feh@finalrewind.org>
+
+* Release v3.6.2
+ * Fix save_filelist not respecting --output-dir
+ * Fix file descriptor leak when attempting to load truncated image files.
+ The issue was introduced in v3.6.
+
+Sun, 06 Dec 2020 08:34:15 +0100 Daniel Friesel <derf+feh@finalrewind.org>
+
+* Release v3.6.1
+ * Fix excessive memory consumption when showing long-running slideshows
+ with thousands to tens of thousands of images and feh has been compiled
+ with exif=1 (see https://github.com/derf/feh/issues/553)
+ * Fix memory leak when showing long-running slideshows with relatively few
+ images and feh has been compiled with exif=1 (ibid.)
+ * Fix memory leak when reloading an image and feh has been compiled with
+ exif=1
+ * Fix memory leak in --draw-exif
+ * Fix memory leak when reloading HTTP files with --no-conversion-cache
+
+Mon, 30 Nov 2020 19:44:47 +0100 Daniel Friesel <derf+feh@finalrewind.org>
+* Release v3.6
+ * Add flip and rotate options to the menu
+ * Improve unloadable image detection time (e.g. for large video files) by
+ checking a file's header before passing it to Imlib2. For rarely used
+ image formats, there is a very small chance that an image which could be
+ loaded by feh 3.5 is reported as unloadable by feh 3.6 due to this
+ change. Set FEH_SKIP_MAGIC=1 to bypass the header check in this case. See
+ <https://phab.enlightenment.org/T8739> and
+ <https://github.com/derf/feh/issues/505> for details.
+
+
+Sat, 29 Aug 2020 08:49:08 +0200 Daniel Friesel <derf+feh@finalrewind.org>
+
+* Release v3.5
+ * Enable --version-sort on systems without strverscmp support. This
+ works by bundling the strverscmp of musl libc with feh and using it
+ when feh is compiled without the verscmp flag (i.e., when the system
+ libc does not provide the verscmp function). Patch by Tim van der Molen
+ * Add %a format specifier (slideshow state: "playing" / "paused")
+ * Fix crashes when combining --reload and --multiwindow
+
Fri, 29 May 2020 23:52:35 +0200 Daniel Friesel <derf+feh@finalrewind.org>
* Release v3.4.1
diff --git a/man/feh.pre b/man/feh.pre
index da6fafa..0d78390 100644
--- a/man/feh.pre
+++ b/man/feh.pre
@@ -13,6 +13,7 @@
.
.Nm
.Op Ar options
+.Op Cm --
.Op Ar files | Ar directories | Ar URLs ...
.
.
@@ -29,13 +30,13 @@ Compile-time switches in this build:
.Bl -bullet -compact
.
.It
-remote file support: libcurl $MAN_CURL$
+libcurl remote file support $MAN_CURL$
.
.It
Xinerama multi-monitor support $MAN_XINERAMA$
.
.It
-builtin EXIF reader $MAN_EXIF$
+libexif builtin EXIF reader $MAN_EXIF$
.
.It
inotify-based auto-reload of changed files $MAN_INOTIFY$
@@ -162,6 +163,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
.
@@ -218,7 +236,7 @@ Use format specifiers to refer to image info, see
.Sx FORMAT SPECIFIERS
for details.
Example usage:
-.Qq feh -A Qo mv ~/images/%N Qc * .
+.Qq feh -A Qo mv %F ~/images/%N Qc * .
.
.It Cm --action1 No .. Cm --action9 Oo Ar flag Oc Ns Oo [ Ar title ] Oc Ns Ar action
.
@@ -236,6 +254,10 @@ disabled by a preceding
.Cm --reload=0
option.
.
+.Pp
+.
+Automatic reload is not supported in montage, index, or thumbnail mode.
+.
.It Cm --auto-rotate
.
.Pq optional feature, $MAN_EXIF$ in this build
@@ -581,7 +603,8 @@ When loading images via HTTP, ImageMagick or dcraw,
.Nm
will only load/convert them once and re-use the cached file on subsequent
slideshow passes.
-This option disables the cache. It is also disabled when
+This option disables the cache.
+It is also disabled when
.Cm --reload
is used.
Use it if you rely on frequently changing files loaded via one of these
@@ -649,7 +672,7 @@ Save files to
.Ar directory
when using
.Cm --keep-http
-or the save_image command.
+or the save_image or save_filelist command.
By default, files are saved in the current working directory.
.
.It Cm -p , --preload
@@ -708,6 +731,7 @@ will continue to try loading it.
.Pp
.
Setting this option causes inotify-based auto-reload to be disabled.
+Reload is not supported in montage, index, or thumbnail mode.
.
.It Cm -n , --reverse
.
@@ -1558,7 +1582,10 @@ will keep zoom and X, Y offset when switching images.
.It L Bq save_filelist
.
Save the current filelist as
-.Qq feh_PID_ID_filelist
+.Qq feh_PID_ID_filelist .
+It is saved in the directory specified by
+.Cm --output-dir ,
+if set, and in the current working directory otherwise.
.
.It m Bq toggle_menu
.
@@ -1594,7 +1621,10 @@ Useful for webcams
.It s Bq save_image
.
Save the current image as
-.Qq feh_PID_ID_FILENAME
+.Qq feh_PID_ID_FILENAME .
+It is saved in the directory specified by
+.Cm --output-dir ,
+if set, and in the current working directory otherwise.
.
.It w Bq size_to_image
.
@@ -2118,23 +2148,11 @@ Please include the feh version
steps to reproduce the bug and, if necessary, images to reproduce it.
.
.
-.Sh FUTURE PLANS
-.
-Plans for the following releases:
-.
-.Bl -bullet -compact
-.
-.It
-Make zoom options more intuitive
-.
-.El
-.
-.
.Sh LICENSE
.
Copyright (C) 1999, 2000 by Paul Duncan.
-Copyright (C) 1999, 2000 by Tom Gilbert (and various contributors).
-Copyright (C) 2010-2020 by Daniel Friesel (and even more contributors).
+Copyright (C) 1999, 2000 by Tom Gilbert and contributors.
+Copyright (C) 2010-2020 by Daniel Friesel and contributors.
.
.Pp
.
@@ -2176,5 +2194,5 @@ Tom Gilbert
.
.Pp
.
-See also:
+Website:
https://feh.finalrewind.org
diff --git a/src/feh.h b/src/feh.h
index e735871..0b7b627 100644
--- a/src/feh.h
+++ b/src/feh.h
@@ -115,6 +115,14 @@ enum slide_change { SLIDE_NEXT, SLIDE_PREV, SLIDE_RAND, SLIDE_FIRST, SLIDE_LAST,
SLIDE_JUMP_PREV_DIR
};
+enum feh_load_error {
+ LOAD_ERROR_IMLIB = 0,
+ LOAD_ERROR_IMAGEMAGICK,
+ LOAD_ERROR_CURL,
+ LOAD_ERROR_DCRAW,
+ LOAD_ERROR_MAGICBYTES
+};
+
#define INPLACE_EDIT_FLIP -1
#define INPLACE_EDIT_MIRROR -2
@@ -172,7 +180,7 @@ void feh_display_status(char stat);
void real_loadables_mode(int loadable);
void feh_reload_image(winwidget w, int resize, int force_new);
void feh_filelist_image_remove(winwidget winwid, char do_delete);
-void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err);
+void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum feh_load_error feh_err);
void slideshow_save_image(winwidget win);
void feh_edit_inplace(winwidget w, int orientation);
void feh_edit_inplace_lossless(winwidget w, int orientation);
diff --git a/src/filelist.c b/src/filelist.c
index 361ac19..ae8d7b2 100644
--- a/src/filelist.c
+++ b/src/filelist.c
@@ -659,8 +659,17 @@ char *feh_absolute_path(char *path)
void feh_save_filelist()
{
char *tmpname;
+ char *base_dir = "";
- tmpname = feh_unique_filename("", "filelist");
+ if (opt.output_dir) {
+ base_dir = estrjoin("", opt.output_dir, "/", NULL);
+ }
+
+ tmpname = feh_unique_filename(base_dir, "filelist");
+
+ if (opt.output_dir) {
+ free(base_dir);
+ }
if (opt.verbose)
fprintf(stderr, "saving filelist to filename '%s'\n", tmpname);
diff --git a/src/imlib.c b/src/imlib.c
index e06a813..9ee4c08 100644
--- a/src/imlib.c
+++ b/src/imlib.c
@@ -164,11 +164,31 @@ int feh_load_image_char(Imlib_Image * im, char *filename)
return(i);
}
-void feh_imlib_print_load_error(char *file, winwidget w, Imlib_Load_Error err)
+void feh_print_load_error(char *file, winwidget w, Imlib_Load_Error err, enum feh_load_error feh_err)
{
if (err == IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS)
eprintf("%s - Out of file descriptors while loading", file);
else if (!opt.quiet || w) {
+ switch (feh_err) {
+ case LOAD_ERROR_IMLIB:
+ // handled in the next switch/case statement
+ break;
+ case LOAD_ERROR_IMAGEMAGICK:
+ im_weprintf(w, "%s - No ImageMagick loader for that file format", file);
+ break;
+ case LOAD_ERROR_CURL:
+ im_weprintf(w, "%s - libcurl was unable to retrieve the file", file);
+ break;
+ case LOAD_ERROR_DCRAW:
+ im_weprintf(w, "%s - Unable to open preview via dcraw", file);
+ break;
+ case LOAD_ERROR_MAGICBYTES:
+ im_weprintf(w, "%s - Does not look like an image (magic bytes missing)", file);
+ break;
+ }
+ if (feh_err != LOAD_ERROR_IMLIB) {
+ return;
+ }
switch (err) {
case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
im_weprintf(w, "%s - File does not exist", file);
@@ -215,9 +235,104 @@ 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) {
+ fclose(fh);
+ 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;
+ }
+ if (!memcmp(buf + 4, "ftyphei", 7) || !memcmp(buf + 4, "ftypmif1", 8)) {
+ // HEIC/HEIF - note that this is only supported in imlib2-heic. Ordinary
+ // imlib2 releases do not support heic/heif images as of 2021-01.
+ 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;
+ enum feh_load_error feh_err = LOAD_ERROR_IMLIB;
enum { SRC_IMLIB, SRC_HTTP, SRC_MAGICK, SRC_DCRAW } image_source = SRC_IMLIB;
char *tmpname = NULL;
char *real_filename = NULL;
@@ -230,23 +345,37 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
if (path_is_url(file->filename)) {
image_source = SRC_HTTP;
- if ((tmpname = feh_http_load_image(file->filename)) == NULL)
+ if ((tmpname = feh_http_load_image(file->filename)) == NULL) {
+ feh_err = LOAD_ERROR_CURL;
err = IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST;
+ }
}
- else if (opt.conversion_timeout >= 0 && feh_file_is_raw(file->filename)) {
- image_source = SRC_DCRAW;
- tmpname = feh_dcraw_load_image(file->filename);
- if (!tmpname)
+ else {
+ if (feh_is_image(file)) {
+ *im = imlib_load_image_with_error_return(file->filename, &err);
+ } else {
+ feh_err = LOAD_ERROR_MAGICBYTES;
err = IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT;
+ }
}
- else
- *im = imlib_load_image_with_error_return(file->filename, &err);
if (opt.conversion_timeout >= 0 && (
(err == IMLIB_LOAD_ERROR_UNKNOWN) ||
(err == IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT))) {
- image_source = SRC_MAGICK;
- tmpname = feh_magick_load_image(file->filename);
+ if (feh_file_is_raw(file->filename)) {
+ image_source = SRC_DCRAW;
+ tmpname = feh_dcraw_load_image(file->filename);
+ if (!tmpname) {
+ feh_err = LOAD_ERROR_DCRAW;
+ }
+ } else {
+ image_source = SRC_MAGICK;
+ feh_err = LOAD_ERROR_IMLIB;
+ tmpname = feh_magick_load_image(file->filename);
+ if (!tmpname) {
+ feh_err = LOAD_ERROR_IMAGEMAGICK;
+ }
+ }
}
if (tmpname) {
@@ -269,7 +398,14 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
file->filename = real_filename;
#ifdef HAVE_LIBEXIF
- file->ed = exif_get_data(tmpname);
+ /*
+ * if we're called from within feh_reload_image, file->ed is already
+ * populated.
+ */
+ if (file->ed) {
+ exif_data_unref(file->ed);
+ }
+ file->ed = exif_data_new_from_file(tmpname);
#endif
}
if (!opt.use_conversion_cache && ((image_source != SRC_HTTP) || !opt.keep_http))
@@ -279,8 +415,19 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
// add_file_to_rm_filelist duplicates tmpname
add_file_to_rm_filelist(tmpname);
- if (image_source != SRC_HTTP && !opt.use_conversion_cache)
+ if (!opt.use_conversion_cache)
free(tmpname);
+ } else if (im) {
+#ifdef HAVE_LIBEXIF
+ /*
+ * if we're called from within feh_reload_image, file->ed is already
+ * populated.
+ */
+ if (file->ed) {
+ exif_data_unref(file->ed);
+ }
+ file->ed = exif_data_new_from_file(file->filename);
+#endif
}
if ((err) || (!im)) {
@@ -288,7 +435,7 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
fputs("\n", stderr);
reset_output = 1;
}
- feh_imlib_print_load_error(file->filename, NULL, err);
+ feh_print_load_error(file->filename, NULL, err, feh_err);
D(("Load *failed*\n"));
return(0);
}
@@ -305,14 +452,12 @@ int feh_load_image(Imlib_Image * im, feh_file * file)
#ifdef HAVE_LIBEXIF
int orientation = 0;
- ExifData *exifData = exif_data_new_from_file(file->filename);
- if (exifData) {
- ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
- ExifEntry *exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
+ if (file->ed) {
+ ExifByteOrder byteOrder = exif_data_get_byte_order(file->ed);
+ ExifEntry *exifEntry = exif_data_get_entry(file->ed, EXIF_TAG_ORIENTATION);
if (exifEntry && opt.auto_rotate)
orientation = exif_get_short(exifEntry->data, byteOrder);
}
- file->ed = exifData;
if (orientation == 2)
gib_imlib_image_flip_horizontal(*im);
@@ -430,11 +575,9 @@ static int feh_file_is_raw(char *filename)
}
if (childpid == 0) {
- if (opt.quiet) {
- int devnull = open("/dev/null", O_WRONLY);
- dup2(devnull, 1);
- dup2(devnull, 2);
- }
+ int devnull = open("/dev/null", O_WRONLY);
+ dup2(devnull, 1);
+ dup2(devnull, 2);
execlp("dcraw", "dcraw", "-i", filename, NULL);
_exit(1);
} else {
@@ -1087,6 +1230,7 @@ void feh_draw_exif(winwidget w)
info_buf[i], IMLIB_TEXT_TO_RIGHT, 0, 0, 0, 255);
gib_imlib_text_draw(im, fn, NULL, 1, (i * line_height) + 1,
info_buf[i], IMLIB_TEXT_TO_RIGHT, 255, 255, 255, 255);
+ free(info_buf[i]);
}
@@ -1417,8 +1561,8 @@ void feh_edit_inplace(winwidget w, int op)
FEH_FILE(w->file->data)->filename, &err);
gib_imlib_free_image(old);
if (err)
- feh_imlib_print_load_error(FEH_FILE(w->file->data)->filename,
- w, err);
+ feh_print_load_error(FEH_FILE(w->file->data)->filename,
+ w, err, LOAD_ERROR_IMLIB);
feh_reload_image(w, 1, 1);
} else {
/*
diff --git a/src/index.c b/src/index.c
index 3633c96..85a3ee8 100644
--- a/src/index.c
+++ b/src/index.c
@@ -332,7 +332,7 @@ void init_index_mode(void)
gib_imlib_save_image_with_error_return(im_main, output_buf, &err);
if (err) {
- feh_imlib_print_load_error(output_buf, im_main, err);
+ feh_print_load_error(output_buf, im_main, err, LOAD_ERROR_IMLIB);
}
else if (opt.verbose) {
int tw, th;
diff --git a/src/menu.c b/src/menu.c
index aca3435..b1deedb 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -80,6 +80,8 @@ enum {
CB_OPT_FREEZE_WINDOW,
CB_OPT_FULLSCREEN,
CB_EDIT_ROTATE,
+ CB_EDIT_MIRROR,
+ CB_EDIT_FLIP,
CB_OPT_AUTO_ZOOM,
CB_OPT_KEEP_ZOOM_VP
};
@@ -920,7 +922,12 @@ void feh_menu_init_main(void)
feh_menu_add_entry(m, "Reload", NULL, CB_RELOAD, 0, NULL);
feh_menu_add_entry(m, "Save Image", NULL, CB_SAVE_IMAGE, 0, NULL);
feh_menu_add_entry(m, "Save List", NULL, CB_SAVE_FILELIST, 0, NULL);
- feh_menu_add_entry(m, "Edit in Place", "EDIT", 0, 0, NULL);
+ if (opt.edit) {
+ feh_menu_add_entry(m, "Edit in Place", "EDIT", 0, 0, NULL);
+ }
+ else {
+ feh_menu_add_entry(m, "Change View", "EDIT", 0, 0, NULL);
+ }
feh_menu_add_entry(m, "Background", "BACKGROUND", 0, 0, NULL);
feh_menu_add_entry(m, NULL, NULL, 0, 0, NULL);
feh_menu_add_entry(m, "Hide", NULL, CB_REMOVE, 0, NULL);
@@ -963,6 +970,8 @@ void feh_menu_init_common()
feh_menu_add_entry(m, "Rotate 90 CW", NULL, CB_EDIT_ROTATE, 1, NULL);
feh_menu_add_entry(m, "Rotate 180", NULL, CB_EDIT_ROTATE, 2, NULL);
feh_menu_add_entry(m, "Rotate 90 CCW", NULL, CB_EDIT_ROTATE, 3, NULL);
+ feh_menu_add_entry(m, "Mirror", NULL, CB_EDIT_MIRROR, 0, NULL);
+ feh_menu_add_entry(m, "Flip", NULL, CB_EDIT_FLIP, 0, NULL);
menu_bg = feh_menu_new();
menu_bg->name = estrdup("BACKGROUND");
@@ -1309,6 +1318,12 @@ void feh_menu_cb(feh_menu * m, feh_menu_item * i, int action, unsigned short dat
case CB_EDIT_ROTATE:
feh_edit_inplace(m->fehwin, data);
break;
+ case CB_EDIT_MIRROR:
+ feh_edit_inplace(m->fehwin, INPLACE_EDIT_MIRROR);
+ break;
+ case CB_EDIT_FLIP:
+ feh_edit_inplace(m->fehwin, INPLACE_EDIT_FLIP);
+ break;
case CB_SAVE_IMAGE:
slideshow_save_image(m->fehwin);
break;
diff --git a/src/slideshow.c b/src/slideshow.c
index 03e8e06..3a3cd0a 100644
--- a/src/slideshow.c
+++ b/src/slideshow.c
@@ -135,47 +135,54 @@ void cb_reload_timer(void *data)
winwidget w = (winwidget) data;
- /* save the current filename for refinding it in new list */
- current_filename = estrdup(FEH_FILE(current_file->data)->filename);
+ /*
+ * multi-window mode has no concept of a "current file" and
+ * dynamically adding/removing windows is not implemented at the moment.
+ * So don't reload filelists in multi-window mode.
+ */
+ if (current_file != NULL) {
+ /* save the current filename for refinding it in new list */
+ current_filename = estrdup(FEH_FILE(current_file->data)->filename);
- for (l = filelist; l; l = l->next) {
- feh_file_free(l->data);
- l->data = NULL;
- }
- gib_list_free_and_data(filelist);
- filelist = NULL;
- filelist_len = 0;
- current_file = NULL;
-
- /* rebuild filelist from original_file_items */
- if (gib_list_length(original_file_items) > 0)
- for (l = gib_list_last(original_file_items); l; l = l->prev)
- add_file_to_filelist_recursively(l->data, FILELIST_FIRST);
- else if (!opt.filelistfile && !opt.bgmode)
- add_file_to_filelist_recursively(".", FILELIST_FIRST);
-
- if (opt.filelistfile) {
- filelist = gib_list_cat(filelist, feh_read_filelist(opt.filelistfile));
- }
-
- if (!(filelist_len = gib_list_length(filelist))) {
- eprintf("No files found to reload.");
- }
+ for (l = filelist; l; l = l->next) {
+ feh_file_free(l->data);
+ l->data = NULL;
+ }
+ gib_list_free_and_data(filelist);
+ filelist = NULL;
+ filelist_len = 0;
+ current_file = NULL;
+
+ /* rebuild filelist from original_file_items */
+ if (gib_list_length(original_file_items) > 0)
+ for (l = gib_list_last(original_file_items); l; l = l->prev)
+ add_file_to_filelist_recursively(l->data, FILELIST_FIRST);
+ else if (!opt.filelistfile && !opt.bgmode)
+ add_file_to_filelist_recursively(".", FILELIST_FIRST);
+
+ if (opt.filelistfile) {
+ filelist = gib_list_cat(filelist, feh_read_filelist(opt.filelistfile));
+ }
+
+ if (!(filelist_len = gib_list_length(filelist))) {
+ eprintf("No files found to reload.");
+ }
- feh_prepare_filelist();
+ feh_prepare_filelist();
- /* find the previously current file */
- for (l = filelist; l; l = l->next)
- if (strcmp(FEH_FILE(l->data)->filename, current_filename) == 0) {
- current_file = l;
- break;
- }
+ /* find the previously current file */
+ for (l = filelist; l; l = l->next)
+ if (strcmp(FEH_FILE(l->data)->filename, current_filename) == 0) {
+ current_file = l;
+ break;
+ }
- free(current_filename);
+ free(current_filename);
- if (!current_file)
- current_file = filelist;
- w->file = current_file;
+ if (!current_file)
+ current_file = filelist;
+ w->file = current_file;
+ }
feh_reload_image(w, 1, 0);
feh_add_unique_timer(cb_reload_timer, w, opt.reload);
@@ -218,6 +225,18 @@ void slideshow_change_image(winwidget winwid, int change, int render)
/* The for loop prevents us looping infinitely */
for (i = 0; i < our_filelist_len; i++) {
winwidget_free_image(winwid);
+#ifdef HAVE_LIBEXIF
+ /*
+ * An EXIF data chunk requires up to 50 kB of space. For large and
+ * long-running slideshows, this would acculumate gigabytes of
+ * EXIF data after a few days. We therefore do not cache EXIF data
+ * in slideshows.
+ */
+ if (FEH_FILE(winwid->file->data)->ed) {
+ exif_data_unref(FEH_FILE(winwid->file->data)->ed);
+ FEH_FILE(winwid->file->data)->ed = NULL;
+ }
+#endif
switch (change) {
case SLIDE_NEXT:
current_file = feh_list_jump(filelist, current_file, FORWARD, 1);
@@ -619,7 +638,7 @@ void slideshow_save_image(winwidget win)
gib_imlib_save_image_with_error_return(win->im, tmpname, &err);
if (err)
- feh_imlib_print_load_error(tmpname, win, err);
+ feh_print_load_error(tmpname, win, err, LOAD_ERROR_IMLIB);
free(tmpname);
return;
diff --git a/src/thumbnail.c b/src/thumbnail.c
index 41bd1a0..e7b05a8 100644
--- a/src/thumbnail.c
+++ b/src/thumbnail.c
@@ -392,7 +392,7 @@ void init_thumbnail_mode(void)
}
gib_imlib_save_image_with_error_return(td.im_main, output_buf, &err);
if (err) {
- feh_imlib_print_load_error(output_buf, td.im_main, err);
+ feh_print_load_error(output_buf, td.im_main, err, LOAD_ERROR_IMLIB);
}
else if (opt.verbose) {
int tw, th;
diff --git a/src/timers.c b/src/timers.c
index 4bdd64e..b2cdbc7 100644
--- a/src/timers.c
+++ b/src/timers.c
@@ -58,7 +58,37 @@ double feh_get_time(void)
return((double) timev.tv_sec + (((double) timev.tv_usec) / 1000000));
}
-void feh_remove_timer(char *name)
+void feh_remove_timer_by_data(void *data)
+{
+ fehtimer ft, ptr, pptr;
+
+ D(("removing timer for %p\n", data));
+ pptr = NULL;
+ ptr = first_timer;
+ while (ptr) {
+ D(("Stepping through event list\n"));
+ ft = ptr;
+ if (ft->data == data) {
+ D(("Found it. Removing\n"));
+ if (pptr)
+ pptr->next = ft->next;
+ else
+ first_timer = ft->next;
+ if (ft->next)
+ ft->next->in += ft->in;
+ if (ft->name)
+ free(ft->name);
+ if (ft)
+ free(ft);
+ return;
+ }
+ pptr = ptr;
+ ptr = ptr->next;
+ }
+ return;
+}
+
+static void feh_remove_timer(char *name)
{
fehtimer ft, ptr, pptr;
@@ -88,6 +118,7 @@ void feh_remove_timer(char *name)
return;
}
+
void feh_add_timer(void (*func) (void *data), void *data, double in, char *name)
{
fehtimer ft, ptr, pptr;
diff --git a/src/timers.h b/src/timers.h
index a4243ca..e95d9b5 100644
--- a/src/timers.h
+++ b/src/timers.h
@@ -37,7 +37,7 @@ struct __fehtimer {
void feh_handle_timer(void);
double feh_get_time(void);
-void feh_remove_timer(char *name);
+void feh_remove_timer_by_data(void *data);
void feh_add_timer(void (*func) (void *data), void *data, double in, char *name);
void feh_add_unique_timer(void (*func) (void *data), void *data, double in);
diff --git a/src/winwidget.c b/src/winwidget.c
index 696f3db..37031ec 100644
--- a/src/winwidget.c
+++ b/src/winwidget.c
@@ -29,6 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "winwidget.h"
#include "options.h"
#include "events.h"
+#include "timers.h"
#ifdef HAVE_INOTIFY
#include <sys/inotify.h>
@@ -694,6 +695,9 @@ void winwidget_destroy(winwidget winwid)
#ifdef HAVE_INOTIFY
winwidget_inotify_remove(winwid);
#endif
+ if (opt.reload > 0 && opt.multiwindow) {
+ feh_remove_timer_by_data(winwid);
+ }
winwidget_destroy_xwin(winwid);
if (winwid->name)
free(winwid->name);
@@ -1031,8 +1035,9 @@ void winwidget_rename(winwidget winwid, char *newname)
void winwidget_free_image(winwidget w)
{
- if (w->im)
+ if (w->im) {
gib_imlib_free_image(w->im);
+ }
w->im = NULL;
w->im_w = 0;
w->im_h = 0;
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};