aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--autoreload.c2
-rw-r--r--config.def.h8
-rw-r--r--etc/MIGRATION.md53
-rw-r--r--etc/nsxiv.126
-rw-r--r--main.c3
-rw-r--r--nsxiv.h16
-rw-r--r--options.c12
-rw-r--r--thumbs.c62
-rw-r--r--window.c23
10 files changed, 198 insertions, 13 deletions
diff --git a/README.md b/README.md
index e8ce9e9..6c97250 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,13 @@ interface and adding simple, sensible features. nsxiv is free software licensed
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
-are welcome, see [CONTRIBUTING] to get started.
+[Codeberg] after making sure you are using the latest release. If you're looking
+to migrate from `sxiv`, see [MIGRATION]. Contributions are welcome, see
+[CONTRIBUTING] to get started.
[Codeberg]: https://codeberg.org/nsxiv/nsxiv/issues/new
[CONTRIBUTING]: etc/CONTRIBUTING.md#contribution-guideline
+[MIGRATION]: etc/MIGRATION.md
Features
diff --git a/autoreload.c b/autoreload.c
index 8325210..1a889dc 100644
--- a/autoreload.c
+++ b/autoreload.c
@@ -85,7 +85,7 @@ void arl_add(arl_t *arl, const char *filepath)
add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF);
base = strrchr(filepath, '/');
- assert(base != NULL); /* filepath must be result of `realpath(3)` */
+ assert(base != NULL && "filepath must be result of realpath(3)");
dir = arl_scratch_push(filepath, MAX(base - filepath, 1));
add_watch(arl->fd, &arl->wd_dir, dir, IN_CREATE | IN_MOVED_TO);
arl->filename = arl_scratch_push(base + 1, strlen(base + 1));
diff --git a/config.def.h b/config.def.h
index 7fbfb17..ead1509 100644
--- a/config.def.h
+++ b/config.def.h
@@ -70,6 +70,14 @@ static const bool ANTI_ALIAS = true;
*/
static const bool ALPHA_LAYER = false;
+/* list of whitelisted/blacklisted directory for thumbnail cache
+ * (overwritten via --cache-{allow,deny} option).
+ * see THUMBNAIL CACHING section in nsxiv(1) manpage for more details.
+ */
+static const char TNS_FILTERS[] = "";
+/* set to true to treat `TNS_FILTERS` as a blacklist instead */
+static const bool TNS_FILTERS_IS_BLACKLIST = false;
+
#endif
#ifdef INCLUDE_THUMBS_CONFIG
diff --git a/etc/MIGRATION.md b/etc/MIGRATION.md
new file mode 100644
index 0000000..db776e0
--- /dev/null
+++ b/etc/MIGRATION.md
@@ -0,0 +1,53 @@
+# Migrating from `sxiv`
+
+`nsxiv` is *mostly* a drop-in replacement for `sxiv`, but not fully.
+This document outlines some key differences to be aware of if you're migrating
+from `sxiv`.
+
+### Configuration directory
+
+`sxiv` looks for config files under the directory
+`${XDG_CONFIG_HOME:-${HOME}/.config}/sxiv`. E.g
+`~/.config/sxiv/exec/key-handler`.
+
+`nsxiv` uses the same logic to find the config dir but uses the name "nsxiv".
+E.g `~/.config/nsxiv/...`.
+
+The "exec" scripts such as `key-handler` and `image-info` in `nsxiv` has some
+more features, but all previous argument order are preserved. And so if you have
+any exec scripts, you can simply copy them over and they should just work.
+
+### Xresources
+
+The xresources config for `nsxiv` is under the "Nsxiv" namespace whereas `sxiv`
+uses the "Sxiv" namespace. Some of the variables are also different between
+`nsxiv` and `sxiv`, below is a table that shows the old and new names:
+
+| sxiv | nsxiv |
+| :-- | :-- |
+| Sxiv.background | Nsxiv.window.background |
+| Sxiv.foreground | Nsxiv.window.foreground |
+| Sxiv.barBackground | Nsxiv.bar.background |
+| Sxiv.barForeground | Nsxiv.bar.foreground |
+| Sxiv.font | Nsxiv.bar.font |
+
+### Default window class
+
+The window class of `nsxiv` is set to "Nsxiv" by default (can be overwritten via
+`-N` flag). This usually shouldn't matter, unless you have scripts that search
+for "Sxiv" window class.
+
+### Thumbnail cache directory
+
+Similar to config dir, the thumbnail cache dir of `nsxiv` is under the "nsxiv"
+name instead of "sxiv". E.g `~/.cache/nsxiv`.
+
+The "caching structure" in `nsxiv` is the same as `sxiv`. Which means that you
+can simply rename the directory to `nsxiv`:
+
+```console
+$ mv ~/.cache/sxiv ~/.cache/nsxiv
+```
+
+If you want to have both `sxiv` and `nsxiv` installed at the same time, you can
+even use symlink to avoid duplicate cache.
diff --git a/etc/nsxiv.1 b/etc/nsxiv.1
index 5edfece..2e01302 100644
--- a/etc/nsxiv.1
+++ b/etc/nsxiv.1
@@ -124,6 +124,14 @@ as an argument, disables it instead.
Enables checkerboard background for alpha layer, when given
.I no
as an argument, disables it instead.
+.TP
+.BI "\-\-cache\-allow, \-\-cache\-deny " "CACHE\-PATHS"
+List of directories to whitelist or blacklist for thumbnail caching.
+See
+.I "THUMBNAIL CACHING"
+section for more information on the argument format.
+These options are mutually exclusive, if they are specified more than once then
+only the final one takes effect.
.SH KEYBOARD COMMANDS
.SS General
The following keyboard commands are available in both image and thumbnail modes:
@@ -559,6 +567,24 @@ afterwards inside the cache directory to remove empty subdirectories:
.RS
find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\;
.RE
+.P
+The option
+.BR "\-\-cache\-allow " or " \-\-cache\-deny"
+may be used to whitelists or blacklist certain directories from being cached.
+The argument is a
+.B :
+separated list of paths. A
+.B *
+at the beginning of the path indicates that path should be matched recursively.
+.P
+For example:
+.B \-\-cache\-allow \(dq/user/pictures:*/media/pictures\(dq
+whitelists \(dq/user/pictures\(dq directory non-recursively and \(dq/media/pictures\(dq
+directory recursively. Nothing outside these two directories will be cached.
+And
+.B \-\-cache\-deny \(dq*/secret\(dq
+will enable blacklist mode and will not cache anything inside \(dq/secret\(dq
+or it's subdirectories.
.SH ORIGINAL AUTHOR
.EX
Bert Muennich <ber.t at posteo.de>
diff --git a/main.c b/main.c
index d73988e..74776fe 100644
--- a/main.c
+++ b/main.c
@@ -460,7 +460,6 @@ static void update_info(void)
open_title();
}
- /* update bar contents */
if (win.bar.h == 0 || extprefix)
return;
@@ -841,7 +840,7 @@ static void run(void)
}
} while (discard);
- switch (ev.type) { /* handle events */
+ switch (ev.type) {
case ButtonPress:
on_buttonpress(&ev.xbutton);
break;
diff --git a/nsxiv.h b/nsxiv.h
index 978962e..5dfbed3 100644
--- a/nsxiv.h
+++ b/nsxiv.h
@@ -101,7 +101,7 @@ typedef enum {
typedef struct {
const char *name; /* as given by user */
- const char *path; /* always absolute */
+ const char *path; /* always absolute, result of realpath(3) */
fileflags_t flags;
} fileinfo_t;
@@ -265,11 +265,13 @@ struct opt {
/* window: */
bool fullscreen;
bool hide_bar;
- Window embed; /* unsigned long */
+ Window embed;
char *geometry;
char *res_name;
/* misc flags: */
+ const char *tns_filters;
+ bool tns_filters_is_blacklist;
bool quiet;
bool thumb_mode;
bool clean_cache;
@@ -293,6 +295,12 @@ typedef struct {
int y;
} thumb_t;
+typedef struct {
+ const char *path;
+ int len;
+ bool recursive;
+} thumb_filter_t;
+
struct tns {
fileinfo_t *files;
thumb_t *thumbs;
@@ -312,7 +320,11 @@ struct tns {
int bw;
int dim;
+ thumb_filter_t *filters;
+ int filters_cnt;
+
bool dirty;
+ bool filters_is_blacklist;
};
void tns_clean_cache(void);
diff --git a/options.c b/options.c
index 4ae2ea5..b813244 100644
--- a/options.c
+++ b/options.c
@@ -75,7 +75,9 @@ void parse_options(int argc, char **argv)
OPT_START = UCHAR_MAX,
OPT_AA,
OPT_AL,
- OPT_BG
+ OPT_BG,
+ OPT_CA,
+ OPT_CD
};
static const struct optparse_long longopts[] = {
{ "framerate", 'A', OPTPARSE_REQUIRED },
@@ -105,6 +107,8 @@ void parse_options(int argc, char **argv)
{ "alpha-layer", OPT_AL, OPTPARSE_OPTIONAL },
/* TODO: document this when it's stable */
{ "bg-cache", OPT_BG, OPTPARSE_OPTIONAL },
+ { "cache-allow", OPT_CA, OPTPARSE_REQUIRED },
+ { "cache-deny", OPT_CD, OPTPARSE_REQUIRED },
{ 0 }, /* end */
};
@@ -137,6 +141,8 @@ void parse_options(int argc, char **argv)
_options.geometry = NULL;
_options.res_name = NULL;
+ _options.tns_filters = TNS_FILTERS;
+ _options.tns_filters_is_blacklist = TNS_FILTERS_IS_BLACKLIST;
_options.quiet = false;
_options.thumb_mode = false;
_options.clean_cache = false;
@@ -266,6 +272,10 @@ void parse_options(int argc, char **argv)
error(EXIT_FAILURE, 0, "Invalid argument for option --bg-cache: %s", op.optarg);
_options.background_cache = op.optarg == NULL;
break;
+ case OPT_CA: case OPT_CD:
+ _options.tns_filters = op.optarg;
+ _options.tns_filters_is_blacklist = (opt == OPT_CD);
+ break;
}
}
diff --git a/thumbs.c b/thumbs.c
index d91405e..159aad7 100644
--- a/thumbs.c
+++ b/thumbs.c
@@ -43,7 +43,7 @@ static char *tns_cache_filepath(const char *filepath)
size_t len;
char *cfile = NULL;
- assert(*filepath == '/' && "filepath should be result of realpath(3)");
+ assert(*filepath == '/' && "filepath must be result of realpath(3)");
if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) {
/* don't cache images inside the cache directory! */
@@ -75,7 +75,26 @@ static Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
return im;
}
-static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
+static bool tns_cache_whitelisted(tns_t *tns, const char *filepath)
+{
+ ptrdiff_t i, dir_len = strrchr(filepath, '/') - filepath;
+ dir_len = MAX(dir_len, 1); /* account for "/file" */
+
+ if (tns->filters_cnt == 0)
+ return true; /* no cache list, cache everything */
+
+ for (i = 0; i < tns->filters_cnt; ++i) {
+ thumb_filter_t *f = tns->filters + i;
+ if ((f->recursive ? (dir_len >= f->len) : (dir_len == f->len)) &&
+ memcmp(filepath, f->path, f->len) == 0)
+ { /* in blacklist mode, finding a match means we shouldn't cache */
+ return tns->filters_is_blacklist ? false : true;
+ }
+ }
+ return tns->filters_is_blacklist; /* no match */
+}
+
+static void tns_cache_write(tns_t *tns, Imlib_Image im, const char *filepath, bool force)
{
char *cfile, *dirend;
int tmpfd;
@@ -83,7 +102,7 @@ static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
struct utimbuf times;
Imlib_Load_Error err;
- if (options->private_mode)
+ if (options->private_mode || !tns_cache_whitelisted(tns, filepath))
return;
if (stat(filepath, &fstats) < 0)
@@ -102,6 +121,7 @@ static void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
imlib_context_set_image(im);
if (imlib_image_has_alpha()) {
imlib_image_set_format("png");
+ imlib_image_attach_data_value("compression", NULL, 8, NULL);
} else {
imlib_image_set_format("jpg");
imlib_image_attach_data_value("quality", NULL, 90, NULL);
@@ -167,6 +187,34 @@ void tns_init(tns_t *tns, fileinfo_t *tns_files, const int *cnt, int *sel, win_t
tns->zl = THUMB_SIZE;
tns_zoom(tns, 0);
+ tns->filters = NULL;
+ tns->filters_cnt = 0;
+ tns->filters_is_blacklist = options->tns_filters_is_blacklist;
+ if (options->tns_filters != NULL && options->tns_filters[0] != '\0') {
+ int allocated = 0;
+ char *save, *tok, *s = estrdup(options->tns_filters);
+ for (tok = strtok_r(s, ":", &save); tok != NULL;
+ tok = strtok_r(NULL, ":", &save))
+ {
+ thumb_filter_t *f;
+ if (tns->filters_cnt == allocated) {
+ allocated = allocated > 0 ? (allocated * 2) : 4;
+ tns->filters = erealloc(tns->filters,
+ allocated * sizeof(*tns->filters));
+ }
+ f = tns->filters + tns->filters_cnt++;
+ f->recursive = *tok == '*';
+ f->path = realpath(tok + f->recursive, NULL);
+ if (f->path == NULL) {
+ error(EXIT_FAILURE, errno, "--cache-%s: `%s`",
+ tns->filters_is_blacklist ? "deny" : "allow",
+ tok + f->recursive);
+ }
+ f->len = strlen(f->path);
+ }
+ free(s);
+ }
+
if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
homedir = getenv("HOME");
dsuffix = "/.cache";
@@ -196,6 +244,12 @@ CLEANUP void tns_free(tns_t *tns)
tns->thumbs = NULL;
}
+ for (i = 0; i < tns->filters_cnt; ++i)
+ free((void *)tns->filters[i].path);
+ tns->filters_cnt = 0;
+ free(tns->filters);
+ tns->filters = NULL;
+
free(cache_dir);
cache_dir = NULL;
free(cache_tmpfile);
@@ -335,7 +389,7 @@ bool tns_load(tns_t *tns, int n, bool force, bool cache_only)
im = tns_scale_down(im, maxwh);
imlib_context_set_image(im);
if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh)
- tns_cache_write(im, file->path, true);
+ tns_cache_write(tns, im, file->path, true);
}
if (cache_only) {
diff --git a/window.c b/window.c
index 3c8b3f0..42c3f2b 100644
--- a/window.c
+++ b/window.c
@@ -67,6 +67,11 @@ static struct {
#if HAVE_LIBFONTS
static XftFont *font;
static double fontsize;
+/* utf8 ellipsis "…". the buffer must be at least 4 bytes for utf8_decode() */
+enum { ELLIPSIS_LEN = 3 };
+static const unsigned char ellipsis[4] = { 0xe2, 0x80, 0xa6, 0x0 };
+static int ellipsis_w;
+static int win_draw_text(win_t *, XftDraw *, const XftColor *, int, int, char *, int, int);
#endif
#if HAVE_LIBFONTS
@@ -156,6 +161,7 @@ void win_init(win_t *win)
f = win_res(db, BAR_FONT[0], BAR_FONT[1] ? BAR_FONT[1] : "monospace-8");
win_init_font(e, f);
+ ellipsis_w = TEXTWIDTH(win, (char *)ellipsis, ELLIPSIS_LEN);
win->bar.l.buf = lbuf;
win->bar.r.buf = rbuf;
@@ -410,7 +416,7 @@ void win_clear(win_t *win)
static int win_draw_text(win_t *win, XftDraw *d, const XftColor *color,
int x, int y, char *text, int len, int w)
{
- int err, tw = 0, warned = 0;
+ int err, tw = 0, warned = 0, danger_zone = w - ellipsis_w;
char *t, *next;
uint32_t rune;
XftFont *f;
@@ -437,6 +443,21 @@ static int win_draw_text(win_t *win, XftDraw *d, const XftColor *color,
FcCharSetDestroy(fccharset);
}
XftTextExtentsUtf8(win->env.dpy, f, (XftChar8 *)t, next - t, &ext);
+ if (d != NULL && tw + ext.xOff >= danger_zone) {
+ int remaining_width = TEXTWIDTH(win, t, (text + len) - t);
+ if (tw + remaining_width > w) { /* overflow, print ellipsis */
+ if (tw + ellipsis_w <= w) {
+ win_draw_text(win, d, color, x, y, (char *)ellipsis,
+ ELLIPSIS_LEN, ellipsis_w);
+ tw += ellipsis_w;
+ }
+ if (f != font)
+ XftFontClose(win->env.dpy, f);
+ return tw;
+ }
+ /* text will fit, no need to check again on next iterations */
+ danger_zone = INT_MAX;
+ }
tw += ext.xOff;
if (tw <= w) {
XftDrawStringUtf8(d, color, f, x, y, (XftChar8 *)t, next - t);