diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | autoreload.c | 2 | ||||
-rw-r--r-- | config.def.h | 8 | ||||
-rw-r--r-- | etc/MIGRATION.md | 53 | ||||
-rw-r--r-- | etc/nsxiv.1 | 26 | ||||
-rw-r--r-- | main.c | 3 | ||||
-rw-r--r-- | nsxiv.h | 16 | ||||
-rw-r--r-- | options.c | 12 | ||||
-rw-r--r-- | thumbs.c | 62 | ||||
-rw-r--r-- | window.c | 23 |
10 files changed, 198 insertions, 13 deletions
@@ -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> @@ -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; @@ -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); @@ -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; } } @@ -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) { @@ -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); |