diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c3b3c1..054dd94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,11 +43,15 @@ jobs: run: | brew update # see: https://github.com/actions/setup-python/issues/577 - brew install imlib2 libx11 libxft libexif giflib webp || true + brew install imlib2 libx11 libxft libexif || true - name: build run: | # libinotify-kqueue isn't available on homebrew - make clean && make -s CC=gcc OPT_DEP_DEFAULT=1 HAVE_INOTIFY=0 + make clean && make -s OPT_DEP_DEFAULT=1 HAVE_INOTIFY=0 \ + CPPFLAGS="-I/opt/homebrew/include -I/opt/homebrew/include/freetype2" \ + LDLIBS="-L/opt/homebrew/lib" # force uninstallation with --ignore-dependencies - brew uninstall --ignore-dependencies libxft libexif giflib webp - make clean && make -s CC=gcc OPT_DEP_DEFAULT=0 + brew uninstall --ignore-dependencies libxft libexif + make clean && make -s OPT_DEP_DEFAULT=0 \ + CPPFLAGS="-I/opt/homebrew/include" \ + LDLIBS="-L/opt/homebrew/lib" diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index cf187d4..eef34e1 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -15,7 +15,7 @@ jobs: with: pr-comment: > Hi, thanks for the Pull-Request. - However this repository is a read-only mirror, main development of nsxiv happens over at [CodeBerg](https://codeberg.org/nsxiv/nsxiv). - Please open your Pull-Request over on the CodeBerg Repo. + However this repository is a read-only mirror, main development of nsxiv happens over at [Codeberg](https://codeberg.org/nsxiv/nsxiv). + Please open your Pull-Request over on the Codeberg Repo. Otherwise you may also e-mail the patch (obtained via `git format-patch`) to any of the [active maintainers](https://nsxiv.codeberg.page/man/#CURRENT%20MAINTAINERS) instead. diff --git a/Makefile b/Makefile index 3527741..b7ee37b 100644 --- a/Makefile +++ b/Makefile @@ -8,20 +8,14 @@ lib_fonts_0 = lib_fonts_1 = -lXft -lfontconfig lib_exif_0 = lib_exif_1 = -lexif -lib_gif_0 = -lib_gif_1 = -lgif -lib_webp_0 = -lib_webp_1 = -lwebpdemux -lwebp nsxiv_cppflags = -D_XOPEN_SOURCE=700 \ - -DHAVE_LIBGIF=$(HAVE_LIBGIF) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ - -DHAVE_LIBWEBP=$(HAVE_LIBWEBP) -DHAVE_LIBFONTS=$(HAVE_LIBFONTS) \ + -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) -DHAVE_LIBFONTS=$(HAVE_LIBFONTS) \ -DHAVE_INOTIFY=$(HAVE_INOTIFY) $(inc_fonts_$(HAVE_LIBFONTS)) \ $(CPPFLAGS) nsxiv_ldlibs = -lImlib2 -lX11 \ - $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_LIBGIF)) \ - $(lib_webp_$(HAVE_LIBWEBP)) $(lib_fonts_$(HAVE_LIBFONTS)) \ + $(lib_exif_$(HAVE_LIBEXIF)) $(lib_fonts_$(HAVE_LIBFONTS)) \ $(LDLIBS) objs = autoreload.o commands.o image.o main.o options.o \ diff --git a/README.md b/README.md index 70376d7..e8ce9e9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ [![nsxiv](https://codeberg.org/nsxiv/pages/raw/branch/master/img/logo.png)](https://codeberg.org/nsxiv/nsxiv) -[![CodeBerg](https://img.shields.io/badge/Hosted_at-Codeberg-%232185D0?style=flat-square&logo=CodeBerg)](https://codeberg.org/nsxiv/nsxiv) +[![Codeberg](https://img.shields.io/badge/Hosted_at-Codeberg-%232185D0?style=flat-square&logo=CodeBerg)](https://codeberg.org/nsxiv/nsxiv) [![tags](https://img.shields.io/github/v/tag/nsxiv/nsxiv?style=flat-square)](https://codeberg.org/nsxiv/nsxiv/tags) -[![license](https://img.shields.io/badge/license-GPL--2.0-lightgreen?style=flat-square)](https://codeberg.org/nsxiv/nsxiv/src/branch/master/LICENSE) +[![license](https://img.shields.io/badge/license-GPL--2.0%2B-lightgreen?style=flat-square)](https://codeberg.org/nsxiv/nsxiv/src/branch/master/LICENSE) [![loc](https://img.shields.io/tokei/lines/github/nsxiv/nsxiv?color=red&style=flat-square)](https://codeberg.org/nsxiv/nsxiv) **Neo (or New or Not) Simple (or Small or Suckless) X Image Viewer** @@ -11,10 +11,10 @@ nsxiv is a fork of the now-unmaintained [sxiv](https://github.com/xyb3rt/sxiv) with the purpose of being a (mostly) drop-in replacement for sxiv, maintaining its interface and adding simple, sensible features. nsxiv is free software licensed -under GPLv2 and aims to be easy to modify and customize. +under GPL-2.0-or-later and aims to be easy to modify and customize. Please file a bug report if something does not work as documented or expected on -[Codeberg] after making sure you are using the latest release. Contributions +[Codeberg] after making sure you are using the latest release. Contributions are welcome, see [CONTRIBUTING] to get started. [Codeberg]: https://codeberg.org/nsxiv/nsxiv/issues/new @@ -25,7 +25,7 @@ Features -------- * Basic image operations like zooming, panning, rotating -* Basic support for animated/multi-frame images +* Basic support for animated/multi-frame images (**requires Imlib2 v1.8.0 or above**) * Thumbnail mode: grid of selectable previews of all images * Ability to cache thumbnails for fast re-loading * Automatically refreshing modified images @@ -64,10 +64,10 @@ Dependencies nsxiv requires the following software to be installed: - * Imlib2 * X11 + * Imlib2 (built with X11 support) -The following dependencies are optional. +The following dependencies are optional: * `inotify`\*: Used for auto-reloading images on change. Disabled via `HAVE_INOTIFY=0`. @@ -76,22 +76,13 @@ The following dependencies are optional. * `libexif`: Used for auto-orientation and exif thumbnails. Disable via `HAVE_LIBEXIF=0`. -The following dependencies are only used if your imlib2 version is lower than -v1.8.0. If your imlib2 version is v1.8.0 (or above) then the following -dependencies are unused and won't be built (even if you enable it explicitly). - - * `giflib`: Used for animated gif playback. Disabled via `HAVE_LIBGIF=0`. - * `libwebp`: Used for animated webp playback. - (***NOTE***: animated webp also requires Imlib2 v1.7.5 or above) - Disabled via `HAVE_LIBWEBP=0`. - Please make sure to install the corresponding development packages in case that you want to build nsxiv on a distribution with separate runtime and development packages (e.g. \*-dev on Debian). \* [inotify][] is a Linux-specific API for monitoring filesystem changes. - It's not natively available on `*BSD` systems but can be enabed via installing - and linking against [libinotify-kqueue][]. + It's not natively available on `*BSD` systems but can be enabled via + installing and linking against [libinotify-kqueue][]. [inotify]: https://www.man7.org/linux/man-pages/man7/inotify.7.html [libinotify-kqueue]: https://github.com/libinotify-kqueue/libinotify-kqueue @@ -206,7 +197,7 @@ Download -------- You can [browse](https://codeberg.org/nsxiv/nsxiv) the source code repository -on CodeBerg or get a copy using git with the following command: +on Codeberg or get a copy using git with the following command: $ git clone https://codeberg.org/nsxiv/nsxiv.git diff --git a/commands.c b/commands.c index 97cadf2..3257b1e 100644 --- a/commands.c +++ b/commands.c @@ -83,9 +83,6 @@ bool cg_switch_mode(arg_t _) load_image(fileidx); mode = MODE_IMAGE; } - close_info(); - open_info(); - title_dirty = true; return true; } @@ -415,12 +412,7 @@ bool ci_slideshow(arg_t _) bool ct_move_sel(arg_t dir) { - bool dirty = tns_move_selection(&tns, dir, prefix); - if (dirty) { - close_info(); - open_info(); - } - return dirty; + return tns_move_selection(&tns, dir, prefix); } bool ct_reload_all(arg_t _) diff --git a/config.mk b/config.mk index 20bb2b2..49a1652 100644 --- a/config.mk +++ b/config.mk @@ -1,5 +1,5 @@ # nsxiv version -VERSION = 31 +VERSION = 32 # PREFIX for install PREFIX = /usr/local @@ -16,13 +16,6 @@ HAVE_INOTIFY = $(OPT_DEP_DEFAULT) HAVE_LIBFONTS = $(OPT_DEP_DEFAULT) HAVE_LIBEXIF = $(OPT_DEP_DEFAULT) -# unused if imlib2 version is 1.8.0 or higher. -# these options will be removed eventually. -HAVE_LIBGIF = $(OPT_DEP_DEFAULT) -HAVE_LIBWEBP = $(OPT_DEP_DEFAULT) - -# Compiler and linker -CC = c99 # CFLAGS, any additional compiler flags goes here CFLAGS = -Wall -pedantic -O2 -DNDEBUG # Uncomment for a debug build using gcc/clang @@ -35,8 +28,8 @@ ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png # Uncomment on OpenBSD # HAVE_INOTIFY = 0 # lib_fonts_bsd_0 = -# lib_fonts_bsd_1 = -lfreetype +# lib_fonts_bsd_1 = -lfreetype -L/usr/X11R6/lib/freetype2 # inc_fonts_bsd_0 = # inc_fonts_bsd_1 = -I/usr/X11R6/include/freetype2 -# LDLIBS = -lz -L/usr/local/lib -L/usr/X11R6/lib $(lib_fonts_bsd_$(HAVE_LIBFONTS)) -# CPPFLAGS = -I/usr/local/include -I/usr/X11R6/include $(inc_fonts_bsd_$(HAVE_LIBFONTS)) +# CPPFLAGS = -I/usr/X11R6/include -I/usr/local/include $(inc_fonts_bsd_$(HAVE_LIBFONTS)) +# LDLIBS = -L/usr/X11R6/lib -L/usr/local/lib $(lib_fonts_bsd_$(HAVE_LIBFONTS)) diff --git a/etc/CHANGELOG.md b/etc/CHANGELOG.md index 5ea1259..5af260f 100644 --- a/etc/CHANGELOG.md +++ b/etc/CHANGELOG.md @@ -8,6 +8,60 @@ there may be more changes. Please use `git log` to view them. - - - +**[v32](https://codeberg.org/nsxiv/nsxiv/archive/v32.tar.gz)** +*(October 01, 2023)* + +* Changes: + + * Removed legacy multi-frame loaders. Animated image support now requires + Imlib2 v1.8.0 or above. + * Move loading/caching messages to right side bar [#446] + * Set a default delay if delay is 0 in a multi-frame image [#445] + * `config.mk`: default to `-O2` [#435] + * `config.mk` no longer explicitly sets `CC` to `c99` [#455] + * Assertions are now opt-in and requires explicitly defining `DEBUG` [#447] + +* Added: + + * Added a pick-quit key-binding [#432] + * Ability to configure Xresources class name in `config.h` [#427] + * `--version` output now also includes compiled-in feature list [#462] + * Document handling of empty X resources values [#428] + * Experimental flag `--bg-cache` to generate thumbnail cache in a background + process [#438] + +* Fixes: + + * Changing brightness/contrast on multi-frame images [#440] + * Brightness keybindings on manpage [#467] + * Various autoreload bugs [#437], [#459], [#460] + * `*-info` scripts not updating when selecting thumbnail with mouse [#477] + * Updated openbsd configuration in `config.mk` [#453] + * Memory leak in `win_draw_bar` [#444] + * Thumbnail leak when removing the last file [#423] + +[#423]: https://codeberg.org/nsxiv/nsxiv/pulls/423 +[#427]: https://codeberg.org/nsxiv/nsxiv/pulls/427 +[#428]: https://codeberg.org/nsxiv/nsxiv/pulls/428 +[#432]: https://codeberg.org/nsxiv/nsxiv/pulls/432 +[#435]: https://codeberg.org/nsxiv/nsxiv/pulls/435 +[#437]: https://codeberg.org/nsxiv/nsxiv/pulls/437 +[#438]: https://codeberg.org/nsxiv/nsxiv/pulls/438 +[#440]: https://codeberg.org/nsxiv/nsxiv/pulls/440 +[#444]: https://codeberg.org/nsxiv/nsxiv/pulls/444 +[#445]: https://codeberg.org/nsxiv/nsxiv/pulls/445 +[#446]: https://codeberg.org/nsxiv/nsxiv/pulls/446 +[#447]: https://codeberg.org/nsxiv/nsxiv/pulls/447 +[#453]: https://codeberg.org/nsxiv/nsxiv/pulls/453 +[#455]: https://codeberg.org/nsxiv/nsxiv/pulls/455 +[#459]: https://codeberg.org/nsxiv/nsxiv/pulls/459 +[#460]: https://codeberg.org/nsxiv/nsxiv/pulls/460 +[#462]: https://codeberg.org/nsxiv/nsxiv/pulls/462 +[#467]: https://codeberg.org/nsxiv/nsxiv/pulls/467 +[#477]: https://codeberg.org/nsxiv/nsxiv/pulls/477 + +- - - + **[v31](https://codeberg.org/nsxiv/nsxiv/archive/v31.tar.gz)** *(January 28, 2023)* @@ -57,11 +111,11 @@ there may be more changes. Please use `git log` to view them. - - - ***SPECIAL NOTE***: Due to [this incident](https://codeberg.org/nsxiv/nsxiv-tmp/issues/1) -we have moved development over to [CodeBerg](https://codeberg.org/nsxiv/nsxiv). +we have moved development over to [Codeberg](https://codeberg.org/nsxiv/nsxiv). A lot of the references *below* may now be 404 on GitHub. Any threads which survived the wipe have been migrated over to [nsxiv-record]. All of the -references *above* can be found on the new main nsxiv repository on CodeBerg. +references *above* can be found on the new main nsxiv repository on Codeberg. [nsxiv-record]: https://codeberg.org/nsxiv/nsxiv-record/ diff --git a/etc/nsxiv.1 b/etc/nsxiv.1 index e2fb4c8..5edfece 100644 --- a/etc/nsxiv.1 +++ b/etc/nsxiv.1 @@ -219,12 +219,12 @@ steps. .B Ctrl-g Reset gamma correction. .TP -.B [ +.B Ctrl-[ Decrease brightness correction by .I count steps. .TP -.B ] +.B Ctrl-] Increase brightness correction by .I count steps. @@ -440,7 +440,9 @@ Zoom in. .B Button5 Zoom out. .SH CONFIGURATION -The following X resources are supported: +The following X resources are supported under "Nsxiv" (e.g. +.B Nsxiv.bar.font +): .TP .B window.background Color of the window background diff --git a/etc/woodpecker/analysis.yml b/etc/woodpecker/analysis.yml index 9ebcc4f..4edcd6b 100644 --- a/etc/woodpecker/analysis.yml +++ b/etc/woodpecker/analysis.yml @@ -1,11 +1,12 @@ -branches: master +when: + branch: master -pipeline: +steps: analysis: image: alpine commands: | apk add --no-cache build-base cppcheck clang-extra-tools git \ imlib2-dev xorgproto \ - libxft-dev libexif-dev giflib-dev libwebp-dev >/dev/null + libxft-dev libexif-dev >/dev/null make config.h version.h ./etc/woodpecker/analysis.sh diff --git a/etc/woodpecker/build.yml b/etc/woodpecker/build.yml index 7c79a90..431f27f 100644 --- a/etc/woodpecker/build.yml +++ b/etc/woodpecker/build.yml @@ -1,7 +1,8 @@ -branches: master +when: + branch: master # NOTE: "stable" tcc is too old and fails at linking. instead fetching a recent known working commit. -pipeline: +steps: build: image: alpine environment: @@ -9,7 +10,7 @@ pipeline: commands: | apk add --no-cache \ imlib2 imlib2-dev xorgproto \ - libxft libxft-dev libexif libexif-dev giflib giflib-dev libwebp libwebp-dev \ + libxft libxft-dev libexif libexif-dev \ gcc clang llvm llvm-dev build-base wget ca-certificates bc >/dev/null wget "https://github.com/TinyCC/tinycc/archive/$TCC_SHA.tar.gz" >/dev/null tar xzf "$TCC_SHA.tar.gz" >/dev/null @@ -26,5 +27,5 @@ pipeline: # full-build with gcc and clang # build "1" "full" # ensure minimal-build works without opt deps installed - apk del libxft libxft-dev libexif libexif-dev giflib giflib-dev libwebp libwebp-dev >/dev/null + apk del libxft libxft-dev libexif libexif-dev >/dev/null build "0" "minimal" diff --git a/etc/woodpecker/clang-tidy-checks b/etc/woodpecker/clang-tidy-checks index edf0ba6..c326eef 100644 --- a/etc/woodpecker/clang-tidy-checks +++ b/etc/woodpecker/clang-tidy-checks @@ -4,13 +4,13 @@ misc-*,android-cloexec-*,llvm-include-order -readability-*,readability-duplicate-include,readability-misleading-indentation # silence --misc-unused-parameters +-misc-unused-parameters,-misc-include-cleaner,-misc-no-recursion -bugprone-easily-swappable-parameters,-bugprone-narrowing-conversions,-bugprone-incorrect-roundings -bugprone-implicit-widening-of-multiplication-result,-bugprone-integer-division -android-cloexec-fopen,-android-cloexec-pipe,-cert-err33-c -bugprone-assignment-in-if-condition -bugprone-suspicious-realloc-usage +-bugprone-switch-missing-default-case # false positive warnings -clang-analyzer-valist.Uninitialized --misc-no-recursion diff --git a/etc/woodpecker/spell.yml b/etc/woodpecker/spell.yml index a6b71d8..78ce57f 100644 --- a/etc/woodpecker/spell.yml +++ b/etc/woodpecker/spell.yml @@ -1,10 +1,13 @@ -branches: master +when: + branch: master -# NOTE: codespell not available on stable alpine, use edge -pipeline: +# NOTE: codespell not available on stable alpine, grab it from pip +steps: spell-check: image: alpine:edge commands: | apk add --no-cache python3 py3-pip git + python3 -m venv ~/py3-venv + . ~/py3-venv/bin/activate pip install codespell git ls-files | sed '/\.png$/d' | xargs codespell diff --git a/image.c b/image.c index 426a05a..dd91994 100644 --- a/image.c +++ b/image.c @@ -33,27 +33,6 @@ #include #endif -#ifdef IMLIB2_VERSION - #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 8, 0) - #define HAVE_IMLIB2_MULTI_FRAME 1 - #endif -#endif -#ifndef HAVE_IMLIB2_MULTI_FRAME - #define HAVE_IMLIB2_MULTI_FRAME 0 -#endif - -#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME -#include -enum { DEF_GIF_DELAY = 75 }; -#endif - -#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME -#include -#include -#include -enum { DEF_WEBP_DELAY = 75 }; -#endif - #if HAVE_IMLIB2_MULTI_FRAME enum { DEF_ANIM_DELAY = 75 }; #endif @@ -96,6 +75,7 @@ void img_init(img_t *img, win_t *win) img->dirty = false; img->anti_alias = options->anti_alias; img->alpha_layer = options->alpha_layer; + img->autoreload_pending = false; img->multi.cap = img->multi.cnt = 0; img->multi.animate = options->animate; img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; @@ -152,297 +132,6 @@ void exif_auto_orientate(const fileinfo_t *file) } #endif -#if HAVE_LIBGIF || HAVE_LIBWEBP || HAVE_IMLIB2_MULTI_FRAME -static void img_multiframe_context_set(img_t *img) -{ - if (img->multi.cnt > 1) { - imlib_context_set_image(img->im); - imlib_free_image(); - img->im = img->multi.frames[0].im; - } else if (img->multi.cnt == 1) { - imlib_context_set_image(img->multi.frames[0].im); - imlib_free_image(); - img->multi.cnt = 0; - } - - imlib_context_set_image(img->im); -} -#endif - -#if (HAVE_LIBGIF || HAVE_LIBWEBP) && !HAVE_IMLIB2_MULTI_FRAME -static void img_multiframe_deprecation_notice(void) -{ - static bool warned; - if (!warned) { - error(0, 0, "\n" - "################################################################\n" - "# DEPRECATION NOTICE #\n" - "################################################################\n" - "# Internal multi-frame gif and webp loaders are deprecated and #\n" - "# will be removed soon. Please upgrade to Imlib2 v1.8.0 for #\n" - "# multi-frame/animated image support. #\n" - "################################################################"); - warned = true; - } -} -#endif - -#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME -static bool img_load_gif(img_t *img, const fileinfo_t *file) -{ - GifFileType *gif; - GifRowType *rows = NULL; - GifRecordType rec; - ColorMapObject *cmap; - uint32_t bgpixel = 0, *data, *ptr; - uint32_t *prev_frame = NULL; - Imlib_Image im; - int i, j, bg, r, g, b; - int x, y, w, h, sw, sh; - int px, py, pw, ph; - int intoffset[] = { 0, 4, 2, 1 }; - int intjump[] = { 8, 8, 4, 2 }; - int transp = -1; - unsigned int disposal = 0, prev_disposal = 0; - unsigned int delay = 0; - bool err = false; - multi_img_t *m = &img->multi; - - img_multiframe_deprecation_notice(); - -#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 - gif = DGifOpenFileName(file->path, NULL); -#else - gif = DGifOpenFileName(file->path); -#endif - if (gif == NULL) { - error(0, 0, "%s: Error opening gif image", file->name); - return false; - } - bg = gif->SBackGroundColor; - sw = gif->SWidth; - sh = gif->SHeight; - px = py = pw = ph = 0; - - m->length = m->cnt = m->sel = 0; - do { - if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { - err = true; - break; - } - if (rec == EXTENSION_RECORD_TYPE) { - int ext_code; - GifByteType *ext = NULL; - - DGifGetExtension(gif, &ext_code, &ext); - while (ext) { - if (ext_code == GRAPHICS_EXT_FUNC_CODE) { - if (ext[1] & 1) - transp = (int)ext[4]; - else - transp = -1; - - delay = 10 * ((unsigned int)ext[3] << 8 | (unsigned int)ext[2]); - disposal = (unsigned int)ext[1] >> 2 & 0x7; - } - ext = NULL; - DGifGetExtensionNext(gif, &ext); - } - } else if (rec == IMAGE_DESC_RECORD_TYPE) { - if (DGifGetImageDesc(gif) == GIF_ERROR) { - err = true; - break; - } - x = gif->Image.Left; - y = gif->Image.Top; - w = gif->Image.Width; - h = gif->Image.Height; - - rows = emalloc(h * sizeof(*rows)); - for (i = 0; i < h; i++) - rows[i] = emalloc(w * sizeof(*rows[i])); - if (gif->Image.Interlace) { - for (i = 0; i < 4; i++) { - for (j = intoffset[i]; j < h; j += intjump[i]) - DGifGetLine(gif, rows[j], w); - } - } else { - for (i = 0; i < h; i++) - DGifGetLine(gif, rows[i], w); - } - - ptr = data = emalloc(sw * sh * sizeof(*data)); - cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; - /* if bg > cmap->ColorCount, it is transparent black already */ - if (cmap && bg >= 0 && bg < cmap->ColorCount) { - r = cmap->Colors[bg].Red; - g = cmap->Colors[bg].Green; - b = cmap->Colors[bg].Blue; - bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); - } - - for (i = 0; i < sh; i++) { - for (j = 0; j < sw; j++) { - if (i < y || i >= y + h || j < x || j >= x + w || - rows[i - y][j - x] == transp) - { - if (prev_frame != NULL && - (prev_disposal != 2 || i < py || i >= py + ph || - j < px || j >= px + pw)) - { - *ptr = prev_frame[i * sw + j]; - } else { - *ptr = bgpixel; - } - } else { - assert(cmap != NULL); - r = cmap->Colors[rows[i - y][j - x]].Red; - g = cmap->Colors[rows[i - y][j - x]].Green; - b = cmap->Colors[rows[i - y][j - x]].Blue; - *ptr = 0xffu << 24 | r << 16 | g << 8 | b; - } - ptr++; - } - } - - im = imlib_create_image_using_copied_data(sw, sh, data); - - for (i = 0; i < h; i++) - free(rows[i]); - free(rows); - free(data); - - if (im == NULL) { - err = true; - break; - } - - imlib_context_set_image(im); - imlib_image_set_format("gif"); - if (transp >= 0) - imlib_image_set_has_alpha(1); - - if (disposal != 3) - prev_frame = imlib_image_get_data_for_reading_only(); - prev_disposal = disposal; - px = x, py = y, pw = w, ph = h; - - assert(m->cnt <= m->cap); - if (m->cnt == m->cap) { - m->cap = m->cap == 0 ? 16 : (m->cap * 2); - m->frames = erealloc(m->frames, m->cap * sizeof(*m->frames)); - } - m->frames[m->cnt].im = im; - delay = m->framedelay > 0 ? m->framedelay : delay; - m->frames[m->cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; - m->length += m->frames[m->cnt].delay; - m->cnt++; - } - } while (rec != TERMINATE_RECORD_TYPE); - -#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1 - DGifCloseFile(gif, NULL); -#else - DGifCloseFile(gif); -#endif - - if (err && (file->flags & FF_WARN)) - error(0, 0, "%s: Corrupted gif file", file->name); - - img_multiframe_context_set(img); - - return !err; -} -#endif /* HAVE_LIBGIF */ - -#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME -static bool img_load_webp(img_t *img, const fileinfo_t *file) -{ - FILE *webp_file; - WebPData data; - Imlib_Image im = NULL; - struct WebPAnimDecoderOptions opts; - WebPAnimDecoder *dec = NULL; - struct WebPAnimInfo info; - unsigned char *buf = NULL, *bytes = NULL; - int ts; - const WebPDemuxer *demux; - WebPIterator iter; - unsigned long flags; - unsigned int delay; - bool err = false; - multi_img_t *m = &img->multi; - - img_multiframe_deprecation_notice(); - - if ((webp_file = fopen(file->path, "rb")) == NULL) { - error(0, errno, "%s: Error opening webp image", file->name); - return false; - } - fseek(webp_file, 0L, SEEK_END); - data.size = ftell(webp_file); - rewind(webp_file); - bytes = emalloc(data.size); - if ((err = fread(bytes, 1, data.size, webp_file) != data.size)) { - error(0, 0, "%s: Error reading webp image", file->name); - goto fail; - } - data.bytes = bytes; - - /* Setup the WebP Animation Decoder */ - if ((err = !WebPAnimDecoderOptionsInit(&opts))) { - error(0, 0, "%s: WebP library version mismatch", file->name); - goto fail; - } - opts.color_mode = MODE_BGRA; - /* NOTE: Multi-threaded decoding may cause problems on some system */ - opts.use_threads = true; - dec = WebPAnimDecoderNew(&data, &opts); - if ((err = (dec == NULL) || !WebPAnimDecoderGetInfo(dec, &info))) { - error(0, 0, "%s: WebP parsing or memory error (file is corrupt?)", file->name); - goto fail; - } - demux = WebPAnimDecoderGetDemuxer(dec); - - /* Get global information for the image */ - flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); - img->w = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); - img->h = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); - - if (info.frame_count > m->cap) { - m->cap = info.frame_count; - m->frames = erealloc(m->frames, m->cap * sizeof(*m->frames)); - } - - /* Load and decode frames (also works on images with only 1 frame) */ - m->length = m->cnt = m->sel = 0; - while (WebPAnimDecoderGetNext(dec, &buf, &ts)) { - im = imlib_create_image_using_copied_data(info.canvas_width, info.canvas_height, - (uint32_t *)buf); - imlib_context_set_image(im); - imlib_image_set_format("webp"); - /* Get an iterator of this frame - used for frame info (duration, etc.) */ - WebPDemuxGetFrame(demux, m->cnt + 1, &iter); - imlib_image_set_has_alpha((flags & ALPHA_FLAG) == ALPHA_FLAG); - /* Store info for this frame */ - m->frames[m->cnt].im = im; - delay = iter.duration > 0 ? iter.duration : DEF_WEBP_DELAY; - m->frames[m->cnt].delay = delay; - m->length += m->frames[m->cnt].delay; - m->cnt++; - } - WebPDemuxReleaseIterator(&iter); - - img_multiframe_context_set(img); -fail: - if (dec != NULL) - WebPAnimDecoderDelete(dec); - free(bytes); - fclose(webp_file); - return !err; -} -#endif /* HAVE_LIBWEBP */ - #if HAVE_IMLIB2_MULTI_FRAME static void img_area_clear(int x, int y, int w, int h) { @@ -498,22 +187,24 @@ static bool img_load_multiframe(img_t *img, const fileinfo_t *file) bool has_alpha; imlib_context_set_image(m->cnt < 1 ? blank : m->frames[m->cnt - 1].im); - if ((canvas = imlib_clone_image()) == NULL || - (frame = imlib_load_image_frame(file->path, n)) == NULL) + canvas = imlib_clone_image(); + if ((frame = imlib_load_image_frame(file->path, n)) != NULL) { + imlib_context_set_image(frame); + imlib_image_set_changes_on_disk(); /* see img_load() for rationale */ + imlib_image_get_frame_info(&finfo); + } + /* NOTE: the underlying file can end up changing during load. + * so check if frame_count, w, h are all still the same or not. + */ + if (canvas == NULL || frame == NULL || finfo.frame_count != (int)fcnt || + finfo.canvas_w != img->w || finfo.canvas_h != img->h) { - if (canvas != NULL) { - imlib_context_set_image(canvas); - imlib_free_image(); - } + img_free(frame, false); + img_free(canvas, false); error(0, 0, "%s: failed to load frame %d", file->name, n); break; } - imlib_context_set_image(frame); - imlib_image_set_changes_on_disk(); /* see img_load() for rationale */ - imlib_image_get_frame_info(&finfo); - assert(finfo.frame_count == (int)fcnt); - assert(finfo.canvas_w == img->w && finfo.canvas_h == img->h); sx = finfo.frame_x; sy = finfo.frame_y; sw = finfo.frame_w; @@ -546,13 +237,19 @@ static bool img_load_multiframe(img_t *img, const fileinfo_t *file) m->frames[m->cnt].delay = finfo.frame_delay ? finfo.frame_delay : DEF_ANIM_DELAY; m->length += m->frames[m->cnt].delay; m->cnt++; - imlib_context_set_image(frame); - imlib_free_image(); + img_free(frame, false); } - imlib_context_set_image(blank); - imlib_free_image(); - img_multiframe_context_set(img); + img_free(blank, false); imlib_context_set_color_modifier(img->cmod); /* restore cmod */ + + if (m->cnt > 1) { + img_free(img->im, false); + img->im = m->frames[0].im; + } else if (m->cnt == 1) { + img_free(m->frames[0].im, false); + m->cnt = 0; + } + imlib_context_set_image(img->im); return m->cnt > 0; } #endif /* HAVE_IMLIB2_MULTI_FRAME */ @@ -572,6 +269,8 @@ Imlib_Image img_open(const fileinfo_t *file) { imlib_context_set_image(im); } + /* UPGRADE: Imlib2 v1.10.0: better error reporting with + * imlib_get_error() + imlib_strerror() */ if (im == NULL && (file->flags & FF_WARN)) error(0, 0, "%s: Error opening image", file->name); return im; @@ -590,6 +289,7 @@ bool img_load(img_t *img, const fileinfo_t *file) */ imlib_image_set_changes_on_disk(); +/* UPGRADE: Imlib2 v1.7.5: remove these exif related ifdefs */ /* since v1.7.5, Imlib2 can parse exif orientation from jpeg files. * this version also happens to be the first one which defines the * IMLIB2_VERSION macro. @@ -602,20 +302,13 @@ bool img_load(img_t *img, const fileinfo_t *file) animated = img_load_multiframe(img, file); #endif - if ((fmt = imlib_image_format()) != NULL) { /* NOLINT: fmt might be unused, not worth fixing */ -#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME - if (STREQ(fmt, "gif")) - img_load_gif(img, file); -#endif -#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME - if (STREQ(fmt, "webp")) - img_load_webp(img, file); -#endif + (void)fmt; /* maybe unused */ #if HAVE_LIBEXIF && defined(IMLIB2_VERSION) + if ((fmt = imlib_image_format()) != NULL) { if (!STREQ(fmt, "jpeg") && !STREQ(fmt, "jpg")) exif_auto_orientate(file); -#endif } +#endif /* for animated images, we want the _canvas_ width/height, which * img_load_multiframe() sets already. */ @@ -629,21 +322,41 @@ bool img_load(img_t *img, const fileinfo_t *file) return true; } +CLEANUP void img_free(Imlib_Image im, bool decache) +{ + if (im != NULL) { + imlib_context_set_image(im); + decache ? imlib_free_image_and_decache() : imlib_free_image(); + } +} + CLEANUP void img_close(img_t *img, bool decache) { unsigned int i; - void (*free_img)(void) = decache ? imlib_free_image_and_decache : imlib_free_image; if (img->multi.cnt > 0) { - for (i = 0; i < img->multi.cnt; i++) { - imlib_context_set_image(img->multi.frames[i].im); - free_img(); - } + for (i = 0; i < img->multi.cnt; i++) + img_free(img->multi.frames[i].im, decache); + /* NOTE: the above only decaches the "composed frames", + * and not the "raw frame" that's associated with the file. + * which leads to issues like: https://codeberg.org/nsxiv/nsxiv/issues/456 + */ +#if HAVE_IMLIB2_MULTI_FRAME + #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 12, 0) + if (decache) + imlib_image_decache_file(files[fileidx].path); + #else /* UPGRADE: Imlib2 v1.12.0: remove this hack */ + /* HACK: try to reload all the frames and forcefully decache them + * if imlib_image_decache_file() isn't available. + */ + for (i = 0; decache && i < img->multi.cnt; i++) + img_free(imlib_load_image_frame(files[fileidx].path, i + 1), true); + #endif +#endif img->multi.cnt = 0; img->im = NULL; } else if (img->im != NULL) { - imlib_context_set_image(img->im); - free_img(); + img_free(img->im, decache); img->im = NULL; } } @@ -704,7 +417,7 @@ static bool img_fit(img_t *img) if (ABS(img->zoom - z) > 1.0 / MAX(img->w, img->h)) { img->zoom = z; - img->dirty = title_dirty = true; + img->dirty = true; return true; } else { return false; @@ -839,7 +552,7 @@ bool img_zoom_to(img_t *img, float z) img->y = y - (y - img->y) * z / img->zoom; img->zoom = z; img->scalemode = SCALE_ZOOM; - img->dirty = img->checkpan = title_dirty = true; + img->dirty = img->checkpan = true; return true; } else { return false; @@ -963,8 +676,8 @@ void img_rotate(img_t *img, degree_t d) ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; - img->x = oy + (img->win->w - img->win->h) / 2; - img->y = ox + (img->win->h - img->win->w) / 2; + img->x = oy + (int)(img->win->w - img->win->h) / 2; + img->y = ox + (int)(img->win->h - img->win->w) / 2; tmp = img->w; img->w = img->h; diff --git a/main.c b/main.c index fed9528..d73988e 100644 --- a/main.c +++ b/main.c @@ -22,8 +22,10 @@ #include "commands.h" #include "config.h" +#include #include #include +#include #include #include #include @@ -69,9 +71,10 @@ int alternate; int markcnt; int markidx; int prefix; -bool title_dirty; const XButtonEvent *xbutton_ev; +static void autoreload(void); + static bool extprefix; static bool resized = false; @@ -91,6 +94,7 @@ static struct { struct timeval when; bool active; } timeouts[] = { + { autoreload }, { redraw }, { reset_cursor }, { slideshow }, @@ -246,10 +250,10 @@ void reset_timeout(timeout_f handler) static bool check_timeouts(int *t) { - int i = 0, tdiff, tmin = -1; + int i = 0, tdiff, tmin; struct timeval now; - while (i < (int)ARRLEN(timeouts)) { + for (i = 0; i < (int)ARRLEN(timeouts); ++i) { if (timeouts[i].active) { gettimeofday(&now, 0); tdiff = TV_DIFF(&timeouts[i].when, &now); @@ -257,16 +261,34 @@ static bool check_timeouts(int *t) timeouts[i].active = false; if (timeouts[i].handler != NULL) timeouts[i].handler(); - i = tmin = -1; - } else if (tmin < 0 || tdiff < tmin) { - tmin = tdiff; } } - i++; } - if (tmin > 0 && t != NULL) - *t = tmin; - return tmin > 0; + + tmin = INT_MAX; + gettimeofday(&now, 0); + for (i = 0; i < (int)ARRLEN(timeouts); ++i) { + if (timeouts[i].active) { + tdiff = TV_DIFF(&timeouts[i].when, &now); + tmin = MIN(tmin, tdiff); + } + } + + if (tmin != INT_MAX && t != NULL) + *t = MAX(tmin, 0); + return tmin != INT_MAX; +} + +static void autoreload(void) +{ + if (img.autoreload_pending) { + img_close(&img, true); + /* load_image() sets autoreload_pending to false */ + load_image(fileidx); + redraw(); + } else { + assert(!"unreachable"); + } } static void kill_close(pid_t pid, int *fd) @@ -300,7 +322,7 @@ static void open_title(void) char *argv[8]; char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12]; - if (wintitle.f.err || !title_dirty) + if (wintitle.f.err) return; close_title(); @@ -313,9 +335,7 @@ static void open_title(void) snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt); construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path, fidx, fcnt, w, h, z, NULL); - if ((wintitle.pid = spawn(&wintitle.fd, NULL, argv)) > 0) - fcntl(wintitle.fd, F_SETFL, O_NONBLOCK); - title_dirty = false; + wintitle.pid = spawn(&wintitle.fd, NULL, O_NONBLOCK, argv); } void close_info(void) @@ -338,8 +358,7 @@ void open_info(void) } construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h, files[fileidx].path, NULL); - if ((info.pid = spawn(&info.fd, NULL, argv)) > 0) - fcntl(info.fd, F_SETFL, O_NONBLOCK); + info.pid = spawn(&info.fd, NULL, O_NONBLOCK, argv); } static void read_info(void) @@ -367,10 +386,13 @@ void load_image(int new) if (win.xwin != None) win_set_cursor(&win, CURSOR_WATCH); + reset_timeout(autoreload); reset_timeout(slideshow); - if (new != current) + if (new != current) { alternate = current; + img.autoreload_pending = false; + } img_close(&img, false); while (!img_load(&img, &files[new])) { @@ -383,10 +405,7 @@ void load_image(int new) files[new].flags &= ~FF_WARN; fileidx = current = new; - close_info(); - open_info(); arl_add(&arl, files[fileidx].path); - title_dirty = true; if (img.multi.cnt > 0 && img.multi.animate) set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); @@ -424,9 +443,33 @@ static void update_info(void) const char *mark; win_bar_t *l = &win.bar.l, *r = &win.bar.r; + static struct { + const char *filepath; + int fileidx; + float zoom; + appmode_t mode; + } prev; + + if (prev.fileidx != fileidx || prev.mode != mode || + (prev.filepath == NULL || !STREQ(prev.filepath, files[fileidx].path))) + { + close_info(); + open_info(); + open_title(); + } else if (mode == MODE_IMAGE && prev.zoom != img.zoom) { + open_title(); + } + /* update bar contents */ if (win.bar.h == 0 || extprefix) return; + + free((char *)prev.filepath); + prev.filepath = estrdup(files[fileidx].path); + prev.fileidx = fileidx; + prev.zoom = img.zoom; + prev.mode = mode; + for (fw = 0, i = filecnt; i > 0; fw++, i /= 10) ; mark = files[fileidx].flags & FF_MARK ? "* " : ""; @@ -501,7 +544,6 @@ void redraw(void) tns_render(&tns); } update_info(); - open_title(); win_draw(&win); reset_timeout(redraw); reset_cursor(); @@ -564,12 +606,10 @@ void handle_key_handler(bool init) if (win.bar.h == 0) return; if (init) { - close_info(); - snprintf(win.bar.l.buf, win.bar.l.size, + snprintf(win.bar.r.buf, win.bar.r.size, "Getting key handler input (%s to abort)...", XKeysymToString(KEYHANDLER_ABORT)); } else { /* abort */ - open_info(); update_info(); } win_draw(&win); @@ -598,8 +638,7 @@ static bool run_key_handler(const char *key, unsigned int mask) if (key == NULL) return false; - close_info(); - strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); + strncpy(win.bar.r.buf, "Running key handler...", win.bar.r.size); win_draw(&win); win_set_cursor(&win, CURSOR_WATCH); setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1); @@ -609,7 +648,7 @@ static bool run_key_handler(const char *key, unsigned int mask) mask & Mod1Mask ? "M-" : "", mask & ShiftMask ? "S-" : "", key); construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL); - if ((pid = spawn(NULL, &writefd, argv)) < 0) + if ((pid = spawn(NULL, &writefd, 0x0, argv)) < 0) return false; if ((pfs = fdopen(writefd, "w")) == NULL) { error(0, errno, "open pipe"); @@ -651,7 +690,7 @@ static bool run_key_handler(const char *key, unsigned int mask) img_close(&img, true); load_image(fileidx); } else { - open_info(); + update_info(); } free(oldst); reset_cursor(); @@ -735,7 +774,6 @@ static void run(void) enum { FD_X, FD_INFO, FD_TITLE, FD_ARL, FD_CNT }; struct pollfd pfd[FD_CNT]; int timeout = 0; - const struct timespec ten_ms = { 0, 10000000 }; bool discard, init_thumb, load_thumb, to_set; XEvent ev, nextev; @@ -767,22 +805,19 @@ static void run(void) pfd[FD_INFO].fd = info.fd; pfd[FD_TITLE].fd = wintitle.fd; pfd[FD_ARL].fd = arl.fd; - pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_TITLE].events = pfd[FD_ARL].events = POLLIN; + + pfd[FD_X].events = pfd[FD_ARL].events = POLLIN; + pfd[FD_INFO].events = pfd[FD_TITLE].events = 0; if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0) continue; - if (pfd[FD_INFO].revents & POLLIN) + if (pfd[FD_INFO].revents & POLLHUP) read_info(); - if (pfd[FD_TITLE].revents & POLLIN) + if (pfd[FD_TITLE].revents & POLLHUP) read_title(); - if (pfd[FD_ARL].revents & POLLIN) { - if (arl_handle(&arl)) { - /* when too fast, imlib2 can't load the image */ - nanosleep(&ten_ms, NULL); - img_close(&img, true); - load_image(fileidx); - redraw(); - } + if ((pfd[FD_ARL].revents & POLLIN) && arl_handle(&arl)) { + img.autoreload_pending = true; + set_timeout(autoreload, TO_AUTORELOAD, true); } } continue; @@ -907,6 +942,23 @@ int main(int argc, char *argv[]) filecnt = fileidx; fileidx = options->startnum < filecnt ? options->startnum : 0; + if (options->background_cache && !options->private_mode) { + pid_t ppid = getpid(); /* to check if parent is still alive or not */ + switch (fork()) { + case 0: + tns_init(&tns, files, &filecnt, &fileidx, NULL); + while (filecnt > 0 && getppid() == ppid) { + tns_load(&tns, filecnt - 1, false, true); + remove_file(filecnt - 1, true); + } + exit(0); + break; + case -1: + error(0, errno, "fork failed"); + break; + } + } + win_init(&win); img_init(&img, &win); arl_init(&arl); diff --git a/nsxiv.h b/nsxiv.h index 7e373c2..fc0720c 100644 --- a/nsxiv.h +++ b/nsxiv.h @@ -107,6 +107,7 @@ typedef struct { /* timeouts in milliseconds: */ enum { + TO_AUTORELOAD = 128, TO_REDRAW_RESIZE = 75, TO_REDRAW_THUMBS = 200, TO_CURSOR_HIDE = 1200, @@ -159,6 +160,15 @@ typedef keymap_t button_t; /* image.c */ +#ifdef IMLIB2_VERSION /* UPGRADE: Imlib2 v1.8.0: remove all HAVE_IMLIB2_MULTI_FRAME ifdefs */ + #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 8, 0) + #define HAVE_IMLIB2_MULTI_FRAME 1 + #endif +#endif +#ifndef HAVE_IMLIB2_MULTI_FRAME + #define HAVE_IMLIB2_MULTI_FRAME 0 +#endif + typedef struct { Imlib_Image im; unsigned int delay; @@ -195,6 +205,7 @@ struct img { bool dirty; bool anti_alias; bool alpha_layer; + bool autoreload_pending; struct { bool on; @@ -206,6 +217,7 @@ struct img { void img_init(img_t*, win_t*); bool img_load(img_t*, const fileinfo_t*); +CLEANUP void img_free(Imlib_Image, bool); CLEANUP void img_close(img_t*, bool); void img_render(img_t*); bool img_fit_win(img_t*, scalemode_t); @@ -262,6 +274,7 @@ struct opt { bool thumb_mode; bool clean_cache; bool private_mode; + bool background_cache; }; extern const opt_t *options; @@ -343,7 +356,7 @@ int r_closedir(r_dir_t*); char* r_readdir(r_dir_t*, bool); int r_mkdir(char*); void construct_argv(char**, unsigned int, ...); -pid_t spawn(int*, int*, char *const []); +pid_t spawn(int*, int*, int, char *const []); /* window.c */ @@ -456,6 +469,5 @@ extern int alternate; extern int markcnt; extern int markidx; extern int prefix; -extern bool title_dirty; #endif /* NSXIV_H */ diff --git a/options.c b/options.c index ef874ac..4ae2ea5 100644 --- a/options.c +++ b/options.c @@ -50,6 +50,20 @@ void print_usage(void) static void print_version(void) { printf("%s %s\n", progname, VERSION); + fputs("features: " +#if HAVE_INOTIFY + "+inotify " +#endif +#if HAVE_LIBFONTS + "+statusbar " +#endif +#if HAVE_LIBEXIF + "+exif " +#endif +#if HAVE_IMLIB2_MULTI_FRAME + "+multiframe " +#endif + "\n", stdout); } void parse_options(int argc, char **argv) @@ -60,7 +74,8 @@ void parse_options(int argc, char **argv) */ OPT_START = UCHAR_MAX, OPT_AA, - OPT_AL + OPT_AL, + OPT_BG }; static const struct optparse_long longopts[] = { { "framerate", 'A', OPTPARSE_REQUIRED }, @@ -88,6 +103,8 @@ void parse_options(int argc, char **argv) { "null", '0', OPTPARSE_NONE }, { "anti-alias", OPT_AA, OPTPARSE_OPTIONAL }, { "alpha-layer", OPT_AL, OPTPARSE_OPTIONAL }, + /* TODO: document this when it's stable */ + { "bg-cache", OPT_BG, OPTPARSE_OPTIONAL }, { 0 }, /* end */ }; @@ -124,6 +141,7 @@ void parse_options(int argc, char **argv) _options.thumb_mode = false; _options.clean_cache = false; _options.private_mode = false; + _options.background_cache = false; if (argc > 0) { s = strrchr(argv[0], '/'); @@ -243,6 +261,11 @@ void parse_options(int argc, char **argv) error(EXIT_FAILURE, 0, "Invalid argument for option --alpha-layer: %s", op.optarg); _options.alpha_layer = op.optarg == NULL; break; + case OPT_BG: + if (op.optarg != NULL && !STREQ(op.optarg, "no")) + error(EXIT_FAILURE, 0, "Invalid argument for option --bg-cache: %s", op.optarg); + _options.background_cache = op.optarg == NULL; + break; } } diff --git a/thumbs.c b/thumbs.c index c1778b5..c7b9dc6 100644 --- a/thumbs.c +++ b/thumbs.c @@ -35,6 +35,8 @@ #endif static char *cache_dir; +static char *cache_tmpfile, *cache_tmpfile_base; +static const char TMP_NAME[] = "/nsxiv-XXXXXX"; static char *tns_cache_filepath(const char *filepath) { @@ -76,6 +78,7 @@ static Imlib_Image tns_cache_load(const char *filepath, bool *outdated) static void tns_cache_write(Imlib_Image im, const char *filepath, bool force) { char *cfile, *dirend; + int tmpfd; struct stat cstats, fstats; struct utimbuf times; Imlib_Load_Error err; @@ -103,12 +106,17 @@ static void tns_cache_write(Imlib_Image im, const char *filepath, bool force) imlib_image_set_format("jpg"); imlib_image_attach_data_value("quality", NULL, 90, NULL); } - imlib_save_image_with_error_return(cfile, &err); - if (err) + memcpy(cache_tmpfile_base, TMP_NAME, sizeof(TMP_NAME)); + if ((tmpfd = mkstemp(cache_tmpfile)) < 0) goto end; + close(tmpfd); + /* UPGRADE: Imlib2 v1.11.0: use imlib_save_image_fd() */ + imlib_save_image_with_error_return(cache_tmpfile, &err); times.actime = fstats.st_atime; times.modtime = fstats.st_mtime; - utime(cfile, ×); + utime(cache_tmpfile, ×); + if (err || rename(cache_tmpfile, cfile) < 0) + unlink(cache_tmpfile); } end: free(cfile); @@ -169,6 +177,9 @@ void tns_init(tns_t *tns, fileinfo_t *tns_files, const int *cnt, int *sel, win_t len = strlen(homedir) + strlen(dsuffix) + strlen(s) + 1; cache_dir = emalloc(len); snprintf(cache_dir, len, "%s%s%s", homedir, dsuffix, s); + cache_tmpfile = emalloc(len + sizeof(TMP_NAME)); + memcpy(cache_tmpfile, cache_dir, len - 1); + cache_tmpfile_base = cache_tmpfile + len - 1; } else { error(0, 0, "Cache directory not found"); } @@ -179,18 +190,16 @@ CLEANUP void tns_free(tns_t *tns) int i; if (tns->thumbs != NULL) { - for (i = 0; i < *tns->cnt; i++) { - if (tns->thumbs[i].im != NULL) { - imlib_context_set_image(tns->thumbs[i].im); - imlib_free_image(); - } - } + for (i = 0; i < *tns->cnt; i++) + img_free(tns->thumbs[i].im, false); free(tns->thumbs); tns->thumbs = NULL; } free(cache_dir); cache_dir = NULL; + free(cache_tmpfile); + cache_tmpfile = cache_tmpfile_base = NULL; } static Imlib_Image tns_scale_down(Imlib_Image im, int dim) @@ -233,12 +242,8 @@ bool tns_load(tns_t *tns, int n, bool force, bool cache_only) return false; t = &tns->thumbs[n]; - - if (t->im != NULL) { - imlib_context_set_image(t->im); - imlib_free_image(); - t->im = NULL; - } + img_free(t->im, false); + t->im = NULL; if (!force) { if ((im = tns_cache_load(file->path, &force)) != NULL) { @@ -268,6 +273,7 @@ bool tns_load(tns_t *tns, int n, bool force, bool cache_only) char tmppath[] = "/tmp/nsxiv-XXXXXX"; Imlib_Image tmpim; + /* UPGRADE: Imlib2 v1.10.0: avoid tempfile and use imlib_load_image_mem() */ if ((ed = exif_data_new_from_file(file->path)) != NULL) { if (ed->data != NULL && ed->size > 0 && (tmpfd = mkstemp(tmppath)) >= 0) @@ -362,11 +368,8 @@ void tns_unload(tns_t *tns, int n) assert(n >= 0 && n < *tns->cnt); t = &tns->thumbs[n]; - if (t->im != NULL) { - imlib_context_set_image(t->im); - imlib_free_image(); - t->im = NULL; - } + img_free(t->im, false); + t->im = NULL; } static void tns_check_view(tns_t *tns, bool scrolled) @@ -457,7 +460,6 @@ void tns_render(tns_t *tns) } tns->dirty = false; tns_highlight(tns, *tns->sel, true); - title_dirty = true; } void tns_mark(tns_t *tns, int n, bool mark) @@ -526,7 +528,6 @@ bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) tns_check_view(tns, false); if (!tns->dirty) tns_highlight(tns, *tns->sel, true); - title_dirty = true; } return *tns->sel != old; } diff --git a/util.c b/util.c index c7fb210..e2fe8af 100644 --- a/util.c +++ b/util.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -230,25 +231,27 @@ void construct_argv(char **argv, unsigned int len, ...) assert(argv[len - 1] == NULL && "argv should be NULL terminated"); } -static int mkspawn_pipe(posix_spawn_file_actions_t *fa, const char *cmd, int *pfd, int dupidx) +static int mkspawn_pipe(posix_spawn_file_actions_t *fa, const char *cmd, int *pfd, int dupidx, int pipeflags) { - int err; + int err = 0; if (pipe(pfd) < 0) { error(0, errno, "pipe: %s", cmd); return -1; } - err = posix_spawn_file_actions_adddup2(fa, pfd[dupidx], dupidx); + if (pipeflags && (fcntl(pfd[0], F_SETFL, pipeflags) < 0 || fcntl(pfd[1], F_SETFL, pipeflags) < 0)) + err = errno; + err = err ? err : posix_spawn_file_actions_adddup2(fa, pfd[dupidx], dupidx); err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[0]); err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]); if (err) { - error(0, err, "posix_spawn_file_actions: %s", cmd); + error(0, err, "mkspawn_pipe: %s", cmd); close(pfd[0]); close(pfd[1]); } return err ? -1 : 0; } -pid_t spawn(int *readfd, int *writefd, char *const argv[]) +pid_t spawn(int *readfd, int *writefd, int pipeflags, char *const argv[]) { pid_t pid = -1; const char *cmd; @@ -263,9 +266,9 @@ pid_t spawn(int *readfd, int *writefd, char *const argv[]) return pid; } - if (readfd != NULL && mkspawn_pipe(&fa, cmd, pfd_read, 1) < 0) + if (readfd != NULL && mkspawn_pipe(&fa, cmd, pfd_read, 1, pipeflags) < 0) goto err_destroy_fa; - if (writefd != NULL && mkspawn_pipe(&fa, cmd, pfd_write, 0) < 0) + if (writefd != NULL && mkspawn_pipe(&fa, cmd, pfd_write, 0, pipeflags) < 0) goto err_close_readfd; if ((err = posix_spawnp(&pid, cmd, &fa, NULL, argv, environ)) != 0) {