diff options
| author | stkhan <personal@slickd.xyz> | 2022-06-20 19:54:29 +0000 |
|---|---|---|
| committer | stkhan <personal@slickd.xyz> | 2022-06-20 19:54:29 +0000 |
| commit | 4c40a4dc245f7715f4891ed02d71475628e7a959 (patch) | |
| tree | fa52419490b5a0d89d746f6e3a0b0c1e675b7726 /utils/nsxiv/main.c | |
| parent | 513b0e238b98f72a01d740a0bd99a3739918645c (diff) | |
new directory structure
Diffstat (limited to 'utils/nsxiv/main.c')
| -rw-r--r-- | utils/nsxiv/main.c | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/utils/nsxiv/main.c b/utils/nsxiv/main.c new file mode 100644 index 0000000..976bd5f --- /dev/null +++ b/utils/nsxiv/main.c @@ -0,0 +1,955 @@ +/* Copyright 2011-2020 Bert Muennich + * Copyright 2021-2022 nsxiv contributors + * + * This file is a part of nsxiv. + * + * nsxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * nsxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nsxiv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "nsxiv.h" +#define _MAPPINGS_CONFIG +#include "commands.h" +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <locale.h> +#include <signal.h> +#include <poll.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <time.h> +#include <X11/keysym.h> +#include <X11/XF86keysym.h> + +#define MODMASK(mask) ((mask) & USED_MODMASK) +#define BAR_SEP " " + +typedef struct { + struct timeval when; + bool active; + timeout_f handler; +} timeout_t; + +typedef struct { + int err; + char *cmd; +} extcmd_t; + +/* these are not declared in nsxiv.h, as it causes too many -Wshadow warnings */ +arl_t arl; +img_t img; +tns_t tns; +win_t win; + +appmode_t mode; +const XButtonEvent *xbutton_ev; + +fileinfo_t *files; +int filecnt, fileidx; +int alternate; +int markcnt; +int markidx; + +int prefix; +static bool extprefix; + +static bool resized = false; + +static struct { + extcmd_t f, ft; + int fd; + unsigned int i, lastsep; + pid_t pid; +} info; + +static struct { + extcmd_t f; + bool warned; +} keyhandler; + +static struct { + extcmd_t f; +} wintitle; + +static timeout_t timeouts[] = { + { { 0, 0 }, false, redraw }, + { { 0, 0 }, false, reset_cursor }, + { { 0, 0 }, false, animate }, + { { 0, 0 }, false, slideshow }, + { { 0, 0 }, false, clear_resize }, +}; + +/************************** + function implementations + **************************/ +static void cleanup(void) +{ + img_close(&img, false); + arl_cleanup(&arl); + tns_free(&tns); + win_close(&win); +} + +static bool xgetline(char **lineptr, size_t *n) +{ + ssize_t len = getdelim(lineptr, n, options->using_null ? '\0' : '\n', stdin); + if (!options->using_null && len > 0 && (*lineptr)[len-1] == '\n') + (*lineptr)[len-1] = '\0'; + return len > 0; +} + +static void check_add_file(char *filename, bool given) +{ + char *path; + + if (*filename == '\0') + return; + + if (access(filename, R_OK) < 0 || + (path = realpath(filename, NULL)) == NULL) + { + if (given) + error(0, errno, "%s", filename); + return; + } + + if (fileidx == filecnt) { + filecnt *= 2; + files = erealloc(files, filecnt * sizeof(*files)); + memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files)); + } + + files[fileidx].name = estrdup(filename); + files[fileidx].path = path; + if (given) + files[fileidx].flags |= FF_WARN; + fileidx++; +} + +void remove_file(int n, bool manual) +{ + if (n < 0 || n >= filecnt) + return; + + if (filecnt == 1) { + if (!manual) + fprintf(stderr, "nsxiv: no more files to display, aborting\n"); + exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); + } + if (files[n].flags & FF_MARK) + markcnt--; + + if (files[n].path != files[n].name) + free((void*) files[n].path); + free((void*) files[n].name); + + if (n + 1 < filecnt) { + if (tns.thumbs != NULL) { + if (tns.thumbs[n].im != NULL) { + imlib_context_set_image(tns.thumbs[n].im); + imlib_free_image_and_decache(); + } + memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * + sizeof(*tns.thumbs)); + memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); + } + memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); + } + filecnt--; + if (fileidx > n || fileidx == filecnt) + fileidx--; + if (alternate > n || alternate == filecnt) + alternate--; + if (markidx > n || markidx == filecnt) + markidx--; +} + +void set_timeout(timeout_f handler, int time, bool overwrite) +{ + unsigned int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + if (!timeouts[i].active || overwrite) { + gettimeofday(&timeouts[i].when, 0); + TV_ADD_MSEC(&timeouts[i].when, time); + timeouts[i].active = true; + } + return; + } + } +} + +void reset_timeout(timeout_f handler) +{ + unsigned int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + timeouts[i].active = false; + return; + } + } +} + +static bool check_timeouts(struct timeval *t) +{ + int i = 0, tdiff, tmin = -1; + struct timeval now; + + while (i < ARRLEN(timeouts)) { + if (timeouts[i].active) { + gettimeofday(&now, 0); + tdiff = TV_DIFF(&timeouts[i].when, &now); + if (tdiff <= 0) { + 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) + TV_SET_MSEC(t, tmin); + return tmin > 0; +} + +size_t get_win_title(unsigned char *buf, int len, bool init) +{ + char *argv[8]; + spawn_t pfd; + char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12]; + ssize_t n = -1; + + if (buf == NULL || len <= 0) + return 0; + + if (init) { + n = snprintf((char *)buf, len, "%s", options->res_name != NULL ? + options->res_name : "nsxiv"); + } else if (!wintitle.f.err) { + if (mode == MODE_IMAGE) { + snprintf(w, ARRLEN(w), "%d", img.w); + snprintf(h, ARRLEN(h), "%d", img.h); + snprintf(z, ARRLEN(z), "%d", (int)(img.zoom * 100)); + } + snprintf(fidx, ARRLEN(fidx), "%d", fileidx+1); + snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt); + construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path, + fidx, fcnt, w, h, z, NULL); + pfd = spawn(wintitle.f.cmd, argv, X_READ); + if (pfd.readfd >= 0) { + if ((n = read(pfd.readfd, buf, len-1)) > 0) + buf[n] = '\0'; + close(pfd.readfd); + } + } + + return MAX(0, n); +} + +void close_info(void) +{ + if (info.fd != -1) { + kill(info.pid, SIGTERM); + close(info.fd); + info.fd = -1; + } +} + +void open_info(void) +{ + spawn_t pfd; + char w[12] = "", h[12] = ""; + char *argv[6]; + char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd; + bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err; + + if (ferr || info.fd >= 0 || win.bar.h == 0) + return; + win.bar.l.buf[0] = '\0'; + if (mode == MODE_IMAGE) { + snprintf(w, sizeof(w), "%d", img.w); + snprintf(h, sizeof(h), "%d", img.h); + } + construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h, + files[fileidx].path, NULL); + pfd = spawn(cmd, argv, X_READ); + if (pfd.readfd >= 0) { + fcntl(pfd.readfd, F_SETFL, O_NONBLOCK); + info.fd = pfd.readfd; + info.i = info.lastsep = 0; + info.pid = pfd.pid; + } +} + +static void read_info(void) +{ + ssize_t i, n; + char buf[BAR_L_LEN]; + + while (true) { + n = read(info.fd, buf, sizeof(buf)); + if (n < 0 && errno == EAGAIN) + return; + else if (n == 0) + goto end; + for (i = 0; i < n; i++) { + if (buf[i] == '\n') { + if (info.lastsep == 0) { + win.bar.l.buf[info.i++] = ' '; + info.lastsep = 1; + } + } else { + win.bar.l.buf[info.i++] = buf[i]; + info.lastsep = 0; + } + if (info.i + 1 == win.bar.l.size) + goto end; + } + } +end: + info.i -= info.lastsep; + win.bar.l.buf[info.i] = '\0'; + win_draw(&win); + close_info(); +} + +void load_image(int new) +{ + bool prev = new < fileidx; + static int current; + + if (new < 0 || new >= filecnt) + return; + + if (win.xwin != None) + win_set_cursor(&win, CURSOR_WATCH); + reset_timeout(slideshow); + + if (new != current) + alternate = current; + + img_close(&img, false); + while (!img_load(&img, &files[new])) { + remove_file(new, false); + if (new >= filecnt) + new = filecnt - 1; + else if (new > 0 && prev) + new--; + } + files[new].flags &= ~FF_WARN; + fileidx = current = new; + + close_info(); + open_info(); + arl_setup(&arl, files[fileidx].path); + + if (img.multi.cnt > 0 && img.multi.animate) + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + else + reset_timeout(animate); +} + +bool mark_image(int n, bool on) +{ + markidx = n; + if (!!(files[n].flags & FF_MARK) != on) { + files[n].flags ^= FF_MARK; + markcnt += on ? 1 : -1; + if (mode == MODE_THUMB) + tns_mark(&tns, n, on); + return true; + } + return false; +} + +static void bar_put(win_bar_t *bar, const char *fmt, ...) +{ + size_t len = bar->size - (bar->p - bar->buf), n; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(bar->p, len, fmt, ap); + bar->p += MIN(len, n); + va_end(ap); +} + +static void update_info(void) +{ + unsigned int i, fn, fw; + const char *mark; + win_bar_t *l = &win.bar.l, *r = &win.bar.r; + + /* update bar contents */ + if (win.bar.h == 0) + return; + for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); + mark = files[fileidx].flags & FF_MARK ? "* " : ""; + l->p = l->buf; + r->p = r->buf; + if (mode == MODE_THUMB) { + if (tns.loadnext < tns.end) + bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1); + else if (tns.initnext < filecnt) + bar_put(l, "Caching... %0*d", fw, tns.initnext + 1); + else if (info.ft.err) + strncpy(l->buf, files[fileidx].name, l->size); + bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); + } else { + bar_put(r, "%s", mark); + if (img.ss.on) { + if (img.ss.delay % 10 != 0) + bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); + else + bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); + } + if (img.gamma) + bar_put(r, "G%+d" BAR_SEP, img.gamma); + bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0)); + if (img.multi.cnt > 0) { + for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); + bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); + } + bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); + if (info.f.err) + strncpy(l->buf, files[fileidx].name, l->size); + } +} + +int nav_button(void) +{ + int x, y, nw; + + if (NAV_WIDTH == 0) + return 1; + + win_cursor_pos(&win, &x, &y); + nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH; + nw = MIN(nw, (win.w + 1) / 2); + + if (x < nw) + return 0; + else if (x < win.w-nw) + return 1; + else + return 2; +} + +void redraw(void) +{ + int t; + + if (mode == MODE_IMAGE) { + img_render(&img); + if (img.ss.on) { + t = img.ss.delay * 100; + if (img.multi.cnt > 0 && img.multi.animate) + t = MAX(t, img.multi.length); + set_timeout(slideshow, t, false); + } + } else { + tns_render(&tns); + } + update_info(); + win_set_title(&win, false); + win_draw(&win); + reset_timeout(redraw); + reset_cursor(); +} + +void reset_cursor(void) +{ + int c; + unsigned int i; + cursor_t cursor = CURSOR_NONE; + + if (mode == MODE_IMAGE) { + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == reset_cursor) { + if (timeouts[i].active) { + c = nav_button(); + c = MAX(fileidx > 0 ? 0 : 1, c); + c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); + cursor = imgcursor[c]; + } + break; + } + } + } else { + if (tns.loadnext < tns.end || tns.initnext < filecnt) + cursor = CURSOR_WATCH; + else + cursor = CURSOR_ARROW; + } + win_set_cursor(&win, cursor); +} + +void animate(void) +{ + if (img_frame_animate(&img)) { + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + redraw(); + } +} + +void slideshow(void) +{ + load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); + redraw(); +} + +void clear_resize(void) +{ + resized = false; +} + +static Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) +{ + return ev->type == ButtonPress || ev->type == KeyPress; +} + +void handle_key_handler(bool init) +{ + extprefix = init; + if (win.bar.h == 0) + return; + if (init) { + close_info(); + snprintf(win.bar.l.buf, win.bar.l.size, "Getting key handler input " + "(%s to abort)...", XKeysymToString(KEYHANDLER_ABORT)); + } else { /* abort */ + open_info(); + update_info(); + } + win_draw(&win); +} + +static bool run_key_handler(const char *key, unsigned int mask) +{ + FILE *pfs; + bool marked = mode == MODE_THUMB && markcnt > 0; + bool changed = false; + int f, i; + int fcnt = marked ? markcnt : 1; + char kstr[32]; + struct stat *oldst, st; + XEvent dump; + char *argv[3]; + spawn_t pfd; + + if (keyhandler.f.err) { + if (!keyhandler.warned) { + error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); + keyhandler.warned = true; + } + return false; + } + if (key == NULL) + return false; + + close_info(); + strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); + win_draw(&win); + win_set_cursor(&win, CURSOR_WATCH); + setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1); + + snprintf(kstr, sizeof(kstr), "%s%s%s%s", + mask & ControlMask ? "C-" : "", + mask & Mod1Mask ? "M-" : "", + mask & ShiftMask ? "S-" : "", key); + construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL); + pfd = spawn(keyhandler.f.cmd, argv, X_WRITE); + if (pfd.writefd < 0) + return false; + if ((pfs = fdopen(pfd.writefd, "w")) == NULL) { + close(pfd.writefd); + error(0, errno, "open pipe"); + return false; + } + + oldst = emalloc(fcnt * sizeof(*oldst)); + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + stat(files[i].path, &oldst[f]); + fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n'); + f++; + } + } + fclose(pfs); + while (waitpid(pfd.pid, NULL, 0) == -1 && errno == EINTR); + + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + if (stat(files[i].path, &st) != 0 || + memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) + { + if (tns.thumbs != NULL) { + tns_unload(&tns, i); + tns.loadnext = MIN(tns.loadnext, i); + } + changed = true; + } + f++; + } + } + /* drop user input events that occurred while running the key handler */ + while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)); + + if (mode == MODE_IMAGE) { + if (changed) { + img_close(&img, true); + load_image(fileidx); + } else { + open_info(); + } + } + free(oldst); + reset_cursor(); + return true; +} + +static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button, + unsigned int state, unsigned int implicit_mod) +{ + unsigned int i; + bool dirty = false; + + for (i = 0; i < len; i++) { + if (bindings[i].ksym_or_button == ksym_or_button && + MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) && + bindings[i].cmd.func && + (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode)) + { + if (bindings[i].cmd.func(bindings[i].arg)) + dirty = true; + } + } + return dirty; +} + +static void on_keypress(XKeyEvent *kev) +{ + unsigned int sh = 0; + KeySym ksym, shksym; + char dummy, key; + bool dirty = false; + + XLookupString(kev, &key, 1, &ksym, NULL); + + if (kev->state & ShiftMask) { + kev->state &= ~ShiftMask; + XLookupString(kev, &dummy, 1, &shksym, NULL); + kev->state |= ShiftMask; + if (ksym != shksym) + sh = ShiftMask; + } + if (IsModifierKey(ksym)) + return; + if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) { + handle_key_handler(false); + } else if (extprefix) { + if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh))) + extprefix = false; + else + handle_key_handler(false); + } else if (key >= '0' && key <= '9') { + /* number prefix for commands */ + prefix = prefix * 10 + (int) (key - '0'); + return; + } else { + dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh); + } + if (dirty) + redraw(); + prefix = 0; +} + +static void on_buttonpress(const XButtonEvent *bev) +{ + bool dirty = false; + + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0); + } else { /* thumbnail mode */ + dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0); + } + if (dirty) + redraw(); + prefix = 0; +} + +static void run(void) +{ + enum { FD_X, FD_INFO, FD_ARL, FD_CNT }; + struct pollfd pfd[FD_CNT]; + struct timeval timeout = {0}; + const struct timespec ten_ms = {0, 10000000}; + bool discard, init_thumb, load_thumb, to_set; + XEvent ev, nextev; + + xbutton_ev = &ev.xbutton; + while (true) { + to_set = check_timeouts(&timeout); + init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; + load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; + + if ((init_thumb || load_thumb || to_set || info.fd != -1 || + arl.fd != -1) && XPending(win.env.dpy) == 0) + { + if (load_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.loadnext, false, false)) { + remove_file(tns.loadnext, false); + tns.dirty = true; + } + if (tns.loadnext >= tns.end) + redraw(); + } else if (init_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.initnext, false, true)) + remove_file(tns.initnext, false); + } else { + pfd[FD_X].fd = ConnectionNumber(win.env.dpy); + pfd[FD_INFO].fd = info.fd; + pfd[FD_ARL].fd = arl.fd; + pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_ARL].events = POLLIN; + + if (poll(pfd, ARRLEN(pfd), to_set ? TV_TO_MS(&timeout) : -1) < 0) + continue; + if (pfd[FD_INFO].revents & POLLIN) + read_info(); + 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(); + } + } + } + continue; + } + + do { + XNextEvent(win.env.dpy, &ev); + discard = false; + if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { + XPeekEvent(win.env.dpy, &nextev); + switch (ev.type) { + case ConfigureNotify: + case MotionNotify: + discard = ev.type == nextev.type; + break; + case KeyPress: + discard = (nextev.type == KeyPress || nextev.type == KeyRelease) + && ev.xkey.keycode == nextev.xkey.keycode; + break; + } + } + } while (discard); + + switch (ev.type) { + /* handle events */ + case ButtonPress: + on_buttonpress(&ev.xbutton); + break; + case ClientMessage: + if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) + cg_quit(EXIT_SUCCESS); + break; + case DestroyNotify: + cg_quit(EXIT_FAILURE); + break; + case ConfigureNotify: + if (win_configure(&win, &ev.xconfigure)) { + if (mode == MODE_IMAGE) { + img.dirty = true; + img.checkpan = true; + } else { + tns.dirty = true; + } + if (!resized) { + redraw(); + set_timeout(clear_resize, TO_REDRAW_RESIZE, false); + resized = true; + } else { + set_timeout(redraw, TO_REDRAW_RESIZE, false); + } + } + break; + case KeyPress: + on_keypress(&ev.xkey); + break; + case MotionNotify: + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + } + break; + } + } +} + +static int fncmp(const void *a, const void *b) +{ + return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); +} + +static void sigchld(int sig) +{ + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +static void setup_signal(int sig, void (*handler)(int sig)) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(sig, &sa, 0) == -1) + error(EXIT_FAILURE, errno, "signal %d", sig); +} + +int main(int argc, char *argv[]) +{ + int i, start; + size_t n; + char *filename; + const char *homedir, *dsuffix = ""; + struct stat fstats; + r_dir_t dir; + + setup_signal(SIGCHLD, sigchld); + setup_signal(SIGPIPE, SIG_IGN); + + setlocale(LC_COLLATE, ""); + + parse_options(argc, argv); + + if (options->clean_cache) { + tns_init(&tns, NULL, NULL, NULL, NULL); + tns_clean_cache(); + exit(EXIT_SUCCESS); + } + + if (options->filecnt == 0 && !options->from_stdin) { + print_usage(); + exit(EXIT_FAILURE); + } + + if (options->recursive || options->from_stdin) + filecnt = 1024; + else + filecnt = options->filecnt; + + files = ecalloc(filecnt, sizeof(*files)); + fileidx = 0; + + if (options->from_stdin) { + n = 0; + filename = NULL; + while (xgetline(&filename, &n)) + check_add_file(filename, true); + free(filename); + } + + for (i = 0; i < options->filecnt; i++) { + filename = options->filenames[i]; + + if (stat(filename, &fstats) < 0) { + error(0, errno, "%s", filename); + continue; + } + if (!S_ISDIR(fstats.st_mode)) { + check_add_file(filename, true); + } else { + if (r_opendir(&dir, filename, options->recursive) < 0) { + error(0, errno, "%s", filename); + continue; + } + start = fileidx; + while ((filename = r_readdir(&dir, true)) != NULL) { + check_add_file(filename, false); + free((void*) filename); + } + r_closedir(&dir); + if (fileidx - start > 1) + qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); + } + } + + if (fileidx == 0) + error(EXIT_FAILURE, 0, "No valid image file given, aborting"); + + filecnt = fileidx; + fileidx = options->startnum < filecnt ? options->startnum : 0; + + win_init(&win); + img_init(&img, &win); + arl_init(&arl); + + if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { + homedir = getenv("HOME"); + dsuffix = "/.config"; + } + if (homedir != NULL) { + extcmd_t *cmd[] = { &info.f, &info.ft, &keyhandler.f, &wintitle.f }; + const char *name[] = { "image-info", "thumb-info", "key-handler", "win-title" }; + const char *s = "/nsxiv/exec/"; + + for (i = 0; i < ARRLEN(cmd); i++) { + n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1; + cmd[i]->cmd = emalloc(n); + snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]); + if (access(cmd[i]->cmd, X_OK) != 0) + cmd[i]->err = errno; + } + } else { + error(0, 0, "Exec directory not found"); + } + info.fd = -1; + + if (options->thumb_mode) { + mode = MODE_THUMB; + tns_init(&tns, files, &filecnt, &fileidx, &win); + while (!tns_load(&tns, fileidx, false, false)) + remove_file(fileidx, false); + } else { + mode = MODE_IMAGE; + tns.thumbs = NULL; + load_image(fileidx); + } + win_open(&win); + win_set_cursor(&win, CURSOR_WATCH); + + atexit(cleanup); + + set_timeout(redraw, 25, false); + + run(); + + return 0; +} |