Merge branch 'master'

This commit is contained in:
mrsu 2024-06-16 00:14:35 +01:00
commit 4e217429aa
18 changed files with 330 additions and 491 deletions

View File

@ -43,11 +43,15 @@ jobs:
run: | run: |
brew update brew update
# see: https://github.com/actions/setup-python/issues/577 # 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 - name: build
run: | run: |
# libinotify-kqueue isn't available on homebrew # 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 # force uninstallation with --ignore-dependencies
brew uninstall --ignore-dependencies libxft libexif giflib webp brew uninstall --ignore-dependencies libxft libexif
make clean && make -s CC=gcc OPT_DEP_DEFAULT=0 make clean && make -s OPT_DEP_DEFAULT=0 \
CPPFLAGS="-I/opt/homebrew/include" \
LDLIBS="-L/opt/homebrew/lib"

View File

@ -15,7 +15,7 @@ jobs:
with: with:
pr-comment: > pr-comment: >
Hi, thanks for the Pull-Request. 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). 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. 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. 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.

View File

@ -8,20 +8,14 @@ lib_fonts_0 =
lib_fonts_1 = -lXft -lfontconfig lib_fonts_1 = -lXft -lfontconfig
lib_exif_0 = lib_exif_0 =
lib_exif_1 = -lexif 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 \ nsxiv_cppflags = -D_XOPEN_SOURCE=700 \
-DHAVE_LIBGIF=$(HAVE_LIBGIF) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) -DHAVE_LIBFONTS=$(HAVE_LIBFONTS) \
-DHAVE_LIBWEBP=$(HAVE_LIBWEBP) -DHAVE_LIBFONTS=$(HAVE_LIBFONTS) \
-DHAVE_INOTIFY=$(HAVE_INOTIFY) $(inc_fonts_$(HAVE_LIBFONTS)) \ -DHAVE_INOTIFY=$(HAVE_INOTIFY) $(inc_fonts_$(HAVE_LIBFONTS)) \
$(CPPFLAGS) $(CPPFLAGS)
nsxiv_ldlibs = -lImlib2 -lX11 \ nsxiv_ldlibs = -lImlib2 -lX11 \
$(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_LIBGIF)) \ $(lib_exif_$(HAVE_LIBEXIF)) $(lib_fonts_$(HAVE_LIBFONTS)) \
$(lib_webp_$(HAVE_LIBWEBP)) $(lib_fonts_$(HAVE_LIBFONTS)) \
$(LDLIBS) $(LDLIBS)
objs = autoreload.o commands.o image.o main.o options.o \ objs = autoreload.o commands.o image.o main.o options.o \

View File

@ -1,8 +1,8 @@
[![nsxiv](https://codeberg.org/nsxiv/pages/raw/branch/master/img/logo.png)](https://codeberg.org/nsxiv/nsxiv) [![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) [![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) [![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** **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) 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 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 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 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. are welcome, see [CONTRIBUTING] to get started.
[Codeberg]: https://codeberg.org/nsxiv/nsxiv/issues/new [Codeberg]: https://codeberg.org/nsxiv/nsxiv/issues/new
@ -25,7 +25,7 @@ Features
-------- --------
* Basic image operations like zooming, panning, rotating * 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 * Thumbnail mode: grid of selectable previews of all images
* Ability to cache thumbnails for fast re-loading * Ability to cache thumbnails for fast re-loading
* Automatically refreshing modified images * Automatically refreshing modified images
@ -64,10 +64,10 @@ Dependencies
nsxiv requires the following software to be installed: nsxiv requires the following software to be installed:
* Imlib2
* X11 * X11
* Imlib2 (built with X11 support)
The following dependencies are optional. The following dependencies are optional:
* `inotify`<sup>\*</sup>: Used for auto-reloading images on change. * `inotify`<sup>\*</sup>: Used for auto-reloading images on change.
Disabled via `HAVE_INOTIFY=0`. Disabled via `HAVE_INOTIFY=0`.
@ -76,22 +76,13 @@ The following dependencies are optional.
* `libexif`: Used for auto-orientation and exif thumbnails. * `libexif`: Used for auto-orientation and exif thumbnails.
Disable via `HAVE_LIBEXIF=0`. 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 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 you want to build nsxiv on a distribution with separate runtime and development
packages (e.g. \*-dev on Debian). packages (e.g. \*-dev on Debian).
\* [inotify][] is a Linux-specific API for monitoring filesystem changes. \* [inotify][] is a Linux-specific API for monitoring filesystem changes.
It's not natively available on `*BSD` systems but can be enabed via installing It's not natively available on `*BSD` systems but can be enabled via
and linking against [libinotify-kqueue][]. installing and linking against [libinotify-kqueue][].
[inotify]: https://www.man7.org/linux/man-pages/man7/inotify.7.html [inotify]: https://www.man7.org/linux/man-pages/man7/inotify.7.html
[libinotify-kqueue]: https://github.com/libinotify-kqueue/libinotify-kqueue [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 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 $ git clone https://codeberg.org/nsxiv/nsxiv.git

View File

@ -83,9 +83,6 @@ bool cg_switch_mode(arg_t _)
load_image(fileidx); load_image(fileidx);
mode = MODE_IMAGE; mode = MODE_IMAGE;
} }
close_info();
open_info();
title_dirty = true;
return true; return true;
} }
@ -415,12 +412,7 @@ bool ci_slideshow(arg_t _)
bool ct_move_sel(arg_t dir) bool ct_move_sel(arg_t dir)
{ {
bool dirty = tns_move_selection(&tns, dir, prefix); return tns_move_selection(&tns, dir, prefix);
if (dirty) {
close_info();
open_info();
}
return dirty;
} }
bool ct_reload_all(arg_t _) bool ct_reload_all(arg_t _)

View File

@ -1,5 +1,5 @@
# nsxiv version # nsxiv version
VERSION = 31 VERSION = 32
# PREFIX for install # PREFIX for install
PREFIX = /usr/local PREFIX = /usr/local
@ -16,13 +16,6 @@ HAVE_INOTIFY = $(OPT_DEP_DEFAULT)
HAVE_LIBFONTS = $(OPT_DEP_DEFAULT) HAVE_LIBFONTS = $(OPT_DEP_DEFAULT)
HAVE_LIBEXIF = $(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, any additional compiler flags goes here
CFLAGS = -Wall -pedantic -O2 -DNDEBUG CFLAGS = -Wall -pedantic -O2 -DNDEBUG
# Uncomment for a debug build using gcc/clang # 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 # Uncomment on OpenBSD
# HAVE_INOTIFY = 0 # HAVE_INOTIFY = 0
# lib_fonts_bsd_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_0 =
# inc_fonts_bsd_1 = -I/usr/X11R6/include/freetype2 # 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/X11R6/include -I/usr/local/include $(inc_fonts_bsd_$(HAVE_LIBFONTS))
# CPPFLAGS = -I/usr/local/include -I/usr/X11R6/include $(inc_fonts_bsd_$(HAVE_LIBFONTS)) # LDLIBS = -L/usr/X11R6/lib -L/usr/local/lib $(lib_fonts_bsd_$(HAVE_LIBFONTS))

View File

@ -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)** **[v31](https://codeberg.org/nsxiv/nsxiv/archive/v31.tar.gz)**
*(January 28, 2023)* *(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) ***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 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 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/ [nsxiv-record]: https://codeberg.org/nsxiv/nsxiv-record/

View File

@ -219,12 +219,12 @@ steps.
.B Ctrl-g .B Ctrl-g
Reset gamma correction. Reset gamma correction.
.TP .TP
.B [ .B Ctrl-[
Decrease brightness correction by Decrease brightness correction by
.I count .I count
steps. steps.
.TP .TP
.B ] .B Ctrl-]
Increase brightness correction by Increase brightness correction by
.I count .I count
steps. steps.
@ -440,7 +440,9 @@ Zoom in.
.B Button5 .B Button5
Zoom out. Zoom out.
.SH CONFIGURATION .SH CONFIGURATION
The following X resources are supported: The following X resources are supported under "Nsxiv" (e.g.
.B Nsxiv.bar.font
):
.TP .TP
.B window.background .B window.background
Color of the window background Color of the window background

View File

@ -1,11 +1,12 @@
branches: master when:
branch: master
pipeline: steps:
analysis: analysis:
image: alpine image: alpine
commands: | commands: |
apk add --no-cache build-base cppcheck clang-extra-tools git \ apk add --no-cache build-base cppcheck clang-extra-tools git \
imlib2-dev xorgproto \ imlib2-dev xorgproto \
libxft-dev libexif-dev giflib-dev libwebp-dev >/dev/null libxft-dev libexif-dev >/dev/null
make config.h version.h make config.h version.h
./etc/woodpecker/analysis.sh ./etc/woodpecker/analysis.sh

View File

@ -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. # NOTE: "stable" tcc is too old and fails at linking. instead fetching a recent known working commit.
pipeline: steps:
build: build:
image: alpine image: alpine
environment: environment:
@ -9,7 +10,7 @@ pipeline:
commands: | commands: |
apk add --no-cache \ apk add --no-cache \
imlib2 imlib2-dev xorgproto \ 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 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 wget "https://github.com/TinyCC/tinycc/archive/$TCC_SHA.tar.gz" >/dev/null
tar xzf "$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 # # full-build with gcc and clang #
build "1" "full" build "1" "full"
# ensure minimal-build works without opt deps installed # 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" build "0" "minimal"

View File

@ -4,13 +4,13 @@ misc-*,android-cloexec-*,llvm-include-order
-readability-*,readability-duplicate-include,readability-misleading-indentation -readability-*,readability-duplicate-include,readability-misleading-indentation
# silence # silence
-misc-unused-parameters -misc-unused-parameters,-misc-include-cleaner,-misc-no-recursion
-bugprone-easily-swappable-parameters,-bugprone-narrowing-conversions,-bugprone-incorrect-roundings -bugprone-easily-swappable-parameters,-bugprone-narrowing-conversions,-bugprone-incorrect-roundings
-bugprone-implicit-widening-of-multiplication-result,-bugprone-integer-division -bugprone-implicit-widening-of-multiplication-result,-bugprone-integer-division
-android-cloexec-fopen,-android-cloexec-pipe,-cert-err33-c -android-cloexec-fopen,-android-cloexec-pipe,-cert-err33-c
-bugprone-assignment-in-if-condition -bugprone-assignment-in-if-condition
-bugprone-suspicious-realloc-usage -bugprone-suspicious-realloc-usage
-bugprone-switch-missing-default-case
# false positive warnings # false positive warnings
-clang-analyzer-valist.Uninitialized -clang-analyzer-valist.Uninitialized
-misc-no-recursion

View File

@ -1,10 +1,13 @@
branches: master when:
branch: master
# NOTE: codespell not available on stable alpine, use edge # NOTE: codespell not available on stable alpine, grab it from pip
pipeline: steps:
spell-check: spell-check:
image: alpine:edge image: alpine:edge
commands: | commands: |
apk add --no-cache python3 py3-pip git apk add --no-cache python3 py3-pip git
python3 -m venv ~/py3-venv
. ~/py3-venv/bin/activate
pip install codespell pip install codespell
git ls-files | sed '/\.png$/d' | xargs codespell git ls-files | sed '/\.png$/d' | xargs codespell

411
image.c
View File

@ -33,27 +33,6 @@
#include <libexif/exif-data.h> #include <libexif/exif-data.h>
#endif #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 <gif_lib.h>
enum { DEF_GIF_DELAY = 75 };
#endif
#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME
#include <stdio.h>
#include <webp/decode.h>
#include <webp/demux.h>
enum { DEF_WEBP_DELAY = 75 };
#endif
#if HAVE_IMLIB2_MULTI_FRAME #if HAVE_IMLIB2_MULTI_FRAME
enum { DEF_ANIM_DELAY = 75 }; enum { DEF_ANIM_DELAY = 75 };
#endif #endif
@ -96,6 +75,7 @@ void img_init(img_t *img, win_t *win)
img->dirty = false; img->dirty = false;
img->anti_alias = options->anti_alias; img->anti_alias = options->anti_alias;
img->alpha_layer = options->alpha_layer; img->alpha_layer = options->alpha_layer;
img->autoreload_pending = false;
img->multi.cap = img->multi.cnt = 0; img->multi.cap = img->multi.cnt = 0;
img->multi.animate = options->animate; img->multi.animate = options->animate;
img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0;
@ -152,297 +132,6 @@ void exif_auto_orientate(const fileinfo_t *file)
} }
#endif #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 #if HAVE_IMLIB2_MULTI_FRAME
static void img_area_clear(int x, int y, int w, int h) 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; bool has_alpha;
imlib_context_set_image(m->cnt < 1 ? blank : m->frames[m->cnt - 1].im); imlib_context_set_image(m->cnt < 1 ? blank : m->frames[m->cnt - 1].im);
if ((canvas = imlib_clone_image()) == NULL || canvas = imlib_clone_image();
(frame = imlib_load_image_frame(file->path, n)) == NULL) 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) { img_free(frame, false);
imlib_context_set_image(canvas); img_free(canvas, false);
imlib_free_image();
}
error(0, 0, "%s: failed to load frame %d", file->name, n); error(0, 0, "%s: failed to load frame %d", file->name, n);
break; 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; sx = finfo.frame_x;
sy = finfo.frame_y; sy = finfo.frame_y;
sw = finfo.frame_w; 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->frames[m->cnt].delay = finfo.frame_delay ? finfo.frame_delay : DEF_ANIM_DELAY;
m->length += m->frames[m->cnt].delay; m->length += m->frames[m->cnt].delay;
m->cnt++; m->cnt++;
imlib_context_set_image(frame); img_free(frame, false);
imlib_free_image();
} }
imlib_context_set_image(blank); img_free(blank, false);
imlib_free_image();
img_multiframe_context_set(img);
imlib_context_set_color_modifier(img->cmod); /* restore cmod */ 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; return m->cnt > 0;
} }
#endif /* HAVE_IMLIB2_MULTI_FRAME */ #endif /* HAVE_IMLIB2_MULTI_FRAME */
@ -572,6 +269,8 @@ Imlib_Image img_open(const fileinfo_t *file)
{ {
imlib_context_set_image(im); 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)) if (im == NULL && (file->flags & FF_WARN))
error(0, 0, "%s: Error opening image", file->name); error(0, 0, "%s: Error opening image", file->name);
return im; return im;
@ -590,6 +289,7 @@ bool img_load(img_t *img, const fileinfo_t *file)
*/ */
imlib_image_set_changes_on_disk(); 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. /* since v1.7.5, Imlib2 can parse exif orientation from jpeg files.
* this version also happens to be the first one which defines the * this version also happens to be the first one which defines the
* IMLIB2_VERSION macro. * IMLIB2_VERSION macro.
@ -602,20 +302,13 @@ bool img_load(img_t *img, const fileinfo_t *file)
animated = img_load_multiframe(img, file); animated = img_load_multiframe(img, file);
#endif #endif
if ((fmt = imlib_image_format()) != NULL) { /* NOLINT: fmt might be unused, not worth fixing */ (void)fmt; /* maybe unused */
#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
#if HAVE_LIBEXIF && defined(IMLIB2_VERSION) #if HAVE_LIBEXIF && defined(IMLIB2_VERSION)
if ((fmt = imlib_image_format()) != NULL) {
if (!STREQ(fmt, "jpeg") && !STREQ(fmt, "jpg")) if (!STREQ(fmt, "jpeg") && !STREQ(fmt, "jpg"))
exif_auto_orientate(file); exif_auto_orientate(file);
#endif
} }
#endif
/* for animated images, we want the _canvas_ width/height, which /* for animated images, we want the _canvas_ width/height, which
* img_load_multiframe() sets already. * img_load_multiframe() sets already.
*/ */
@ -629,21 +322,41 @@ bool img_load(img_t *img, const fileinfo_t *file)
return true; 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) CLEANUP void img_close(img_t *img, bool decache)
{ {
unsigned int i; unsigned int i;
void (*free_img)(void) = decache ? imlib_free_image_and_decache : imlib_free_image;
if (img->multi.cnt > 0) { if (img->multi.cnt > 0) {
for (i = 0; i < img->multi.cnt; i++) { for (i = 0; i < img->multi.cnt; i++)
imlib_context_set_image(img->multi.frames[i].im); img_free(img->multi.frames[i].im, decache);
free_img(); /* 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->multi.cnt = 0;
img->im = NULL; img->im = NULL;
} else if (img->im != NULL) { } else if (img->im != NULL) {
imlib_context_set_image(img->im); img_free(img->im, decache);
free_img();
img->im = NULL; 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)) { if (ABS(img->zoom - z) > 1.0 / MAX(img->w, img->h)) {
img->zoom = z; img->zoom = z;
img->dirty = title_dirty = true; img->dirty = true;
return true; return true;
} else { } else {
return false; 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->y = y - (y - img->y) * z / img->zoom;
img->zoom = z; img->zoom = z;
img->scalemode = SCALE_ZOOM; img->scalemode = SCALE_ZOOM;
img->dirty = img->checkpan = title_dirty = true; img->dirty = img->checkpan = true;
return true; return true;
} else { } else {
return false; 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; 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; 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->x = oy + (int)(img->win->w - img->win->h) / 2;
img->y = ox + (img->win->h - img->win->w) / 2; img->y = ox + (int)(img->win->h - img->win->w) / 2;
tmp = img->w; tmp = img->w;
img->w = img->h; img->w = img->h;

132
main.c
View File

@ -22,8 +22,10 @@
#include "commands.h" #include "commands.h"
#include "config.h" #include "config.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <locale.h> #include <locale.h>
#include <poll.h> #include <poll.h>
#include <signal.h> #include <signal.h>
@ -69,9 +71,10 @@ int alternate;
int markcnt; int markcnt;
int markidx; int markidx;
int prefix; int prefix;
bool title_dirty;
const XButtonEvent *xbutton_ev; const XButtonEvent *xbutton_ev;
static void autoreload(void);
static bool extprefix; static bool extprefix;
static bool resized = false; static bool resized = false;
@ -91,6 +94,7 @@ static struct {
struct timeval when; struct timeval when;
bool active; bool active;
} timeouts[] = { } timeouts[] = {
{ autoreload },
{ redraw }, { redraw },
{ reset_cursor }, { reset_cursor },
{ slideshow }, { slideshow },
@ -246,10 +250,10 @@ void reset_timeout(timeout_f handler)
static bool check_timeouts(int *t) static bool check_timeouts(int *t)
{ {
int i = 0, tdiff, tmin = -1; int i = 0, tdiff, tmin;
struct timeval now; struct timeval now;
while (i < (int)ARRLEN(timeouts)) { for (i = 0; i < (int)ARRLEN(timeouts); ++i) {
if (timeouts[i].active) { if (timeouts[i].active) {
gettimeofday(&now, 0); gettimeofday(&now, 0);
tdiff = TV_DIFF(&timeouts[i].when, &now); tdiff = TV_DIFF(&timeouts[i].when, &now);
@ -257,16 +261,34 @@ static bool check_timeouts(int *t)
timeouts[i].active = false; timeouts[i].active = false;
if (timeouts[i].handler != NULL) if (timeouts[i].handler != NULL)
timeouts[i].handler(); timeouts[i].handler();
i = tmin = -1;
} else if (tmin < 0 || tdiff < tmin) {
tmin = tdiff;
} }
} }
i++;
} }
if (tmin > 0 && t != NULL)
*t = tmin; tmin = INT_MAX;
return tmin > 0; 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) static void kill_close(pid_t pid, int *fd)
@ -300,7 +322,7 @@ static void open_title(void)
char *argv[8]; char *argv[8];
char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12]; char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12];
if (wintitle.f.err || !title_dirty) if (wintitle.f.err)
return; return;
close_title(); close_title();
@ -313,9 +335,7 @@ static void open_title(void)
snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt); snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt);
construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path, construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path,
fidx, fcnt, w, h, z, NULL); fidx, fcnt, w, h, z, NULL);
if ((wintitle.pid = spawn(&wintitle.fd, NULL, argv)) > 0) wintitle.pid = spawn(&wintitle.fd, NULL, O_NONBLOCK, argv);
fcntl(wintitle.fd, F_SETFL, O_NONBLOCK);
title_dirty = false;
} }
void close_info(void) void close_info(void)
@ -338,8 +358,7 @@ void open_info(void)
} }
construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h, construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h,
files[fileidx].path, NULL); files[fileidx].path, NULL);
if ((info.pid = spawn(&info.fd, NULL, argv)) > 0) info.pid = spawn(&info.fd, NULL, O_NONBLOCK, argv);
fcntl(info.fd, F_SETFL, O_NONBLOCK);
} }
static void read_info(void) static void read_info(void)
@ -367,10 +386,13 @@ void load_image(int new)
if (win.xwin != None) if (win.xwin != None)
win_set_cursor(&win, CURSOR_WATCH); win_set_cursor(&win, CURSOR_WATCH);
reset_timeout(autoreload);
reset_timeout(slideshow); reset_timeout(slideshow);
if (new != current) if (new != current) {
alternate = current; alternate = current;
img.autoreload_pending = false;
}
img_close(&img, false); img_close(&img, false);
while (!img_load(&img, &files[new])) { while (!img_load(&img, &files[new])) {
@ -383,10 +405,7 @@ void load_image(int new)
files[new].flags &= ~FF_WARN; files[new].flags &= ~FF_WARN;
fileidx = current = new; fileidx = current = new;
close_info();
open_info();
arl_add(&arl, files[fileidx].path); arl_add(&arl, files[fileidx].path);
title_dirty = true;
if (img.multi.cnt > 0 && img.multi.animate) if (img.multi.cnt > 0 && img.multi.animate)
set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
@ -424,9 +443,33 @@ static void update_info(void)
const char *mark; const char *mark;
win_bar_t *l = &win.bar.l, *r = &win.bar.r; 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 */ /* update bar contents */
if (win.bar.h == 0 || extprefix) if (win.bar.h == 0 || extprefix)
return; 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) for (fw = 0, i = filecnt; i > 0; fw++, i /= 10)
; ;
mark = files[fileidx].flags & FF_MARK ? "* " : ""; mark = files[fileidx].flags & FF_MARK ? "* " : "";
@ -501,7 +544,6 @@ void redraw(void)
tns_render(&tns); tns_render(&tns);
} }
update_info(); update_info();
open_title();
win_draw(&win); win_draw(&win);
reset_timeout(redraw); reset_timeout(redraw);
reset_cursor(); reset_cursor();
@ -564,12 +606,10 @@ void handle_key_handler(bool init)
if (win.bar.h == 0) if (win.bar.h == 0)
return; return;
if (init) { if (init) {
close_info(); snprintf(win.bar.r.buf, win.bar.r.size,
snprintf(win.bar.l.buf, win.bar.l.size,
"Getting key handler input (%s to abort)...", "Getting key handler input (%s to abort)...",
XKeysymToString(KEYHANDLER_ABORT)); XKeysymToString(KEYHANDLER_ABORT));
} else { /* abort */ } else { /* abort */
open_info();
update_info(); update_info();
} }
win_draw(&win); win_draw(&win);
@ -598,8 +638,7 @@ static bool run_key_handler(const char *key, unsigned int mask)
if (key == NULL) if (key == NULL)
return false; return false;
close_info(); strncpy(win.bar.r.buf, "Running key handler...", win.bar.r.size);
strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
win_draw(&win); win_draw(&win);
win_set_cursor(&win, CURSOR_WATCH); win_set_cursor(&win, CURSOR_WATCH);
setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1); 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 & Mod1Mask ? "M-" : "",
mask & ShiftMask ? "S-" : "", key); mask & ShiftMask ? "S-" : "", key);
construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL); 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; return false;
if ((pfs = fdopen(writefd, "w")) == NULL) { if ((pfs = fdopen(writefd, "w")) == NULL) {
error(0, errno, "open pipe"); error(0, errno, "open pipe");
@ -651,7 +690,7 @@ static bool run_key_handler(const char *key, unsigned int mask)
img_close(&img, true); img_close(&img, true);
load_image(fileidx); load_image(fileidx);
} else { } else {
open_info(); update_info();
} }
free(oldst); free(oldst);
reset_cursor(); reset_cursor();
@ -735,7 +774,6 @@ static void run(void)
enum { FD_X, FD_INFO, FD_TITLE, FD_ARL, FD_CNT }; enum { FD_X, FD_INFO, FD_TITLE, FD_ARL, FD_CNT };
struct pollfd pfd[FD_CNT]; struct pollfd pfd[FD_CNT];
int timeout = 0; int timeout = 0;
const struct timespec ten_ms = { 0, 10000000 };
bool discard, init_thumb, load_thumb, to_set; bool discard, init_thumb, load_thumb, to_set;
XEvent ev, nextev; XEvent ev, nextev;
@ -767,22 +805,19 @@ static void run(void)
pfd[FD_INFO].fd = info.fd; pfd[FD_INFO].fd = info.fd;
pfd[FD_TITLE].fd = wintitle.fd; pfd[FD_TITLE].fd = wintitle.fd;
pfd[FD_ARL].fd = arl.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) if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0)
continue; continue;
if (pfd[FD_INFO].revents & POLLIN) if (pfd[FD_INFO].revents & POLLHUP)
read_info(); read_info();
if (pfd[FD_TITLE].revents & POLLIN) if (pfd[FD_TITLE].revents & POLLHUP)
read_title(); read_title();
if (pfd[FD_ARL].revents & POLLIN) { if ((pfd[FD_ARL].revents & POLLIN) && arl_handle(&arl)) {
if (arl_handle(&arl)) { img.autoreload_pending = true;
/* when too fast, imlib2 can't load the image */ set_timeout(autoreload, TO_AUTORELOAD, true);
nanosleep(&ten_ms, NULL);
img_close(&img, true);
load_image(fileidx);
redraw();
}
} }
} }
continue; continue;
@ -907,6 +942,23 @@ int main(int argc, char *argv[])
filecnt = fileidx; filecnt = fileidx;
fileidx = options->startnum < filecnt ? options->startnum : 0; 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); win_init(&win);
img_init(&img, &win); img_init(&img, &win);
arl_init(&arl); arl_init(&arl);

16
nsxiv.h
View File

@ -107,6 +107,7 @@ typedef struct {
/* timeouts in milliseconds: */ /* timeouts in milliseconds: */
enum { enum {
TO_AUTORELOAD = 128,
TO_REDRAW_RESIZE = 75, TO_REDRAW_RESIZE = 75,
TO_REDRAW_THUMBS = 200, TO_REDRAW_THUMBS = 200,
TO_CURSOR_HIDE = 1200, TO_CURSOR_HIDE = 1200,
@ -159,6 +160,15 @@ typedef keymap_t button_t;
/* image.c */ /* 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 { typedef struct {
Imlib_Image im; Imlib_Image im;
unsigned int delay; unsigned int delay;
@ -195,6 +205,7 @@ struct img {
bool dirty; bool dirty;
bool anti_alias; bool anti_alias;
bool alpha_layer; bool alpha_layer;
bool autoreload_pending;
struct { struct {
bool on; bool on;
@ -206,6 +217,7 @@ struct img {
void img_init(img_t*, win_t*); void img_init(img_t*, win_t*);
bool img_load(img_t*, const fileinfo_t*); bool img_load(img_t*, const fileinfo_t*);
CLEANUP void img_free(Imlib_Image, bool);
CLEANUP void img_close(img_t*, bool); CLEANUP void img_close(img_t*, bool);
void img_render(img_t*); void img_render(img_t*);
bool img_fit_win(img_t*, scalemode_t); bool img_fit_win(img_t*, scalemode_t);
@ -262,6 +274,7 @@ struct opt {
bool thumb_mode; bool thumb_mode;
bool clean_cache; bool clean_cache;
bool private_mode; bool private_mode;
bool background_cache;
}; };
extern const opt_t *options; extern const opt_t *options;
@ -343,7 +356,7 @@ int r_closedir(r_dir_t*);
char* r_readdir(r_dir_t*, bool); char* r_readdir(r_dir_t*, bool);
int r_mkdir(char*); int r_mkdir(char*);
void construct_argv(char**, unsigned int, ...); void construct_argv(char**, unsigned int, ...);
pid_t spawn(int*, int*, char *const []); pid_t spawn(int*, int*, int, char *const []);
/* window.c */ /* window.c */
@ -456,6 +469,5 @@ extern int alternate;
extern int markcnt; extern int markcnt;
extern int markidx; extern int markidx;
extern int prefix; extern int prefix;
extern bool title_dirty;
#endif /* NSXIV_H */ #endif /* NSXIV_H */

View File

@ -50,6 +50,20 @@ void print_usage(void)
static void print_version(void) static void print_version(void)
{ {
printf("%s %s\n", progname, VERSION); 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) void parse_options(int argc, char **argv)
@ -60,7 +74,8 @@ void parse_options(int argc, char **argv)
*/ */
OPT_START = UCHAR_MAX, OPT_START = UCHAR_MAX,
OPT_AA, OPT_AA,
OPT_AL OPT_AL,
OPT_BG
}; };
static const struct optparse_long longopts[] = { static const struct optparse_long longopts[] = {
{ "framerate", 'A', OPTPARSE_REQUIRED }, { "framerate", 'A', OPTPARSE_REQUIRED },
@ -88,6 +103,8 @@ void parse_options(int argc, char **argv)
{ "null", '0', OPTPARSE_NONE }, { "null", '0', OPTPARSE_NONE },
{ "anti-alias", OPT_AA, OPTPARSE_OPTIONAL }, { "anti-alias", OPT_AA, OPTPARSE_OPTIONAL },
{ "alpha-layer", OPT_AL, OPTPARSE_OPTIONAL }, { "alpha-layer", OPT_AL, OPTPARSE_OPTIONAL },
/* TODO: document this when it's stable */
{ "bg-cache", OPT_BG, OPTPARSE_OPTIONAL },
{ 0 }, /* end */ { 0 }, /* end */
}; };
@ -124,6 +141,7 @@ void parse_options(int argc, char **argv)
_options.thumb_mode = false; _options.thumb_mode = false;
_options.clean_cache = false; _options.clean_cache = false;
_options.private_mode = false; _options.private_mode = false;
_options.background_cache = false;
if (argc > 0) { if (argc > 0) {
s = strrchr(argv[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); error(EXIT_FAILURE, 0, "Invalid argument for option --alpha-layer: %s", op.optarg);
_options.alpha_layer = op.optarg == NULL; _options.alpha_layer = op.optarg == NULL;
break; 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;
} }
} }

View File

@ -35,6 +35,8 @@
#endif #endif
static char *cache_dir; 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) 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) static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
{ {
char *cfile, *dirend; char *cfile, *dirend;
int tmpfd;
struct stat cstats, fstats; struct stat cstats, fstats;
struct utimbuf times; struct utimbuf times;
Imlib_Load_Error err; 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_set_format("jpg");
imlib_image_attach_data_value("quality", NULL, 90, NULL); imlib_image_attach_data_value("quality", NULL, 90, NULL);
} }
imlib_save_image_with_error_return(cfile, &err); memcpy(cache_tmpfile_base, TMP_NAME, sizeof(TMP_NAME));
if (err) if ((tmpfd = mkstemp(cache_tmpfile)) < 0)
goto end; 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.actime = fstats.st_atime;
times.modtime = fstats.st_mtime; times.modtime = fstats.st_mtime;
utime(cfile, &times); utime(cache_tmpfile, &times);
if (err || rename(cache_tmpfile, cfile) < 0)
unlink(cache_tmpfile);
} }
end: end:
free(cfile); 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; len = strlen(homedir) + strlen(dsuffix) + strlen(s) + 1;
cache_dir = emalloc(len); cache_dir = emalloc(len);
snprintf(cache_dir, len, "%s%s%s", homedir, dsuffix, s); 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 { } else {
error(0, 0, "Cache directory not found"); error(0, 0, "Cache directory not found");
} }
@ -179,18 +190,16 @@ CLEANUP void tns_free(tns_t *tns)
int i; int i;
if (tns->thumbs != NULL) { if (tns->thumbs != NULL) {
for (i = 0; i < *tns->cnt; i++) { for (i = 0; i < *tns->cnt; i++)
if (tns->thumbs[i].im != NULL) { img_free(tns->thumbs[i].im, false);
imlib_context_set_image(tns->thumbs[i].im);
imlib_free_image();
}
}
free(tns->thumbs); free(tns->thumbs);
tns->thumbs = NULL; tns->thumbs = NULL;
} }
free(cache_dir); free(cache_dir);
cache_dir = NULL; cache_dir = NULL;
free(cache_tmpfile);
cache_tmpfile = cache_tmpfile_base = NULL;
} }
static Imlib_Image tns_scale_down(Imlib_Image im, int dim) 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; return false;
t = &tns->thumbs[n]; t = &tns->thumbs[n];
img_free(t->im, false);
if (t->im != NULL) { t->im = NULL;
imlib_context_set_image(t->im);
imlib_free_image();
t->im = NULL;
}
if (!force) { if (!force) {
if ((im = tns_cache_load(file->path, &force)) != NULL) { 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"; char tmppath[] = "/tmp/nsxiv-XXXXXX";
Imlib_Image tmpim; 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 = exif_data_new_from_file(file->path)) != NULL) {
if (ed->data != NULL && ed->size > 0 && if (ed->data != NULL && ed->size > 0 &&
(tmpfd = mkstemp(tmppath)) >= 0) (tmpfd = mkstemp(tmppath)) >= 0)
@ -362,11 +368,8 @@ void tns_unload(tns_t *tns, int n)
assert(n >= 0 && n < *tns->cnt); assert(n >= 0 && n < *tns->cnt);
t = &tns->thumbs[n]; t = &tns->thumbs[n];
if (t->im != NULL) { img_free(t->im, false);
imlib_context_set_image(t->im); t->im = NULL;
imlib_free_image();
t->im = NULL;
}
} }
static void tns_check_view(tns_t *tns, bool scrolled) static void tns_check_view(tns_t *tns, bool scrolled)
@ -457,7 +460,6 @@ void tns_render(tns_t *tns)
} }
tns->dirty = false; tns->dirty = false;
tns_highlight(tns, *tns->sel, true); tns_highlight(tns, *tns->sel, true);
title_dirty = true;
} }
void tns_mark(tns_t *tns, int n, bool mark) 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); tns_check_view(tns, false);
if (!tns->dirty) if (!tns->dirty)
tns_highlight(tns, *tns->sel, true); tns_highlight(tns, *tns->sel, true);
title_dirty = true;
} }
return *tns->sel != old; return *tns->sel != old;
} }

17
util.c
View File

@ -21,6 +21,7 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <spawn.h> #include <spawn.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
@ -230,25 +231,27 @@ void construct_argv(char **argv, unsigned int len, ...)
assert(argv[len - 1] == NULL && "argv should be NULL terminated"); 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) { if (pipe(pfd) < 0) {
error(0, errno, "pipe: %s", cmd); error(0, errno, "pipe: %s", cmd);
return -1; 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[0]);
err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]); err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]);
if (err) { if (err) {
error(0, err, "posix_spawn_file_actions: %s", cmd); error(0, err, "mkspawn_pipe: %s", cmd);
close(pfd[0]); close(pfd[0]);
close(pfd[1]); close(pfd[1]);
} }
return err ? -1 : 0; 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; pid_t pid = -1;
const char *cmd; const char *cmd;
@ -263,9 +266,9 @@ pid_t spawn(int *readfd, int *writefd, char *const argv[])
return pid; 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; 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; goto err_close_readfd;
if ((err = posix_spawnp(&pid, cmd, &fa, NULL, argv, environ)) != 0) { if ((err = posix_spawnp(&pid, cmd, &fa, NULL, argv, environ)) != 0) {