Krolyxon
Commit e197429

Branches

Refs →

nsxivmain.c

947 lines · 21.2 KB · C

1/* Copyright 2011-2020 Bert Muennich
2 * Copyright 2021-2022 nsxiv contributors
3 *
4 * This file is a part of nsxiv.
5 *
6 * nsxiv is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * nsxiv is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with nsxiv. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "nsxiv.h"
21#define INCLUDE_MAPPINGS_CONFIG
22#include "commands.h"
23#include "config.h"
24
25#include <errno.h>
26#include <fcntl.h>
27#include <locale.h>
28#include <poll.h>
29#include <signal.h>
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <sys/stat.h>
35#include <sys/time.h>
36#include <sys/wait.h>
37#include <time.h>
38#include <unistd.h>
39
40#include <X11/XF86keysym.h>
41#include <X11/keysym.h>
42
43#define MODMASK(mask) ((mask) & USED_MODMASK)
44#define BAR_SEP " "
45
46#define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \
47 ((t1)->tv_usec - (t2)->tv_usec) / 1000)
48#define TV_ADD_MSEC(tv,t) { \
49 (tv)->tv_sec += (t) / 1000; \
50 (tv)->tv_usec += (t) % 1000 * 1000; \
51}
52
53typedef struct {
54 int err;
55 char *cmd;
56} extcmd_t;
57
58/* these are not declared in nsxiv.h, as it causes too many -Wshadow warnings */
59arl_t arl;
60img_t img;
61tns_t tns;
62win_t win;
63
64appmode_t mode;
65fileinfo_t *files;
66int filecnt, fileidx;
67int alternate;
68int markcnt;
69int markidx;
70int prefix;
71bool title_dirty;
72const XButtonEvent *xbutton_ev;
73
74static bool extprefix;
75static bool resized = false;
76
77static struct {
78 extcmd_t f, ft;
79 int fd;
80 pid_t pid;
81} info;
82
83static struct {
84 extcmd_t f;
85 bool warned;
86} keyhandler;
87
88static struct {
89 extcmd_t f;
90} wintitle;
91
92static struct {
93 timeout_f handler;
94 struct timeval when;
95 bool active;
96} timeouts[] = {
97 { redraw },
98 { reset_cursor },
99 { slideshow },
100 { animate },
101 { clear_resize },
102};
103
104/*
105 * function implementations
106 */
107
108static void cleanup(void)
109{
110 img_close(&img, false);
111 arl_cleanup(&arl);
112 tns_free(&tns);
113 win_close(&win);
114}
115
116static bool xgetline(char **lineptr, size_t *n)
117{
118 ssize_t len = getdelim(lineptr, n, options->using_null ? '\0' : '\n', stdin);
119 if (!options->using_null && len > 0 && (*lineptr)[len-1] == '\n')
120 (*lineptr)[len-1] = '\0';
121 return len > 0;
122}
123
124static void check_add_file(char *filename, bool given)
125{
126 char *path;
127
128 if (*filename == '\0')
129 return;
130
131 if (access(filename, R_OK) < 0 ||
132 (path = realpath(filename, NULL)) == NULL)
133 {
134 if (given)
135 error(0, errno, "%s", filename);
136 return;
137 }
138
139 if (fileidx == filecnt) {
140 filecnt *= 2;
141 files = erealloc(files, filecnt * sizeof(*files));
142 memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files));
143 }
144
145 files[fileidx].name = estrdup(filename);
146 files[fileidx].path = path;
147 if (given)
148 files[fileidx].flags |= FF_WARN;
149 fileidx++;
150}
151
152void remove_file(int n, bool manual)
153{
154 if (n < 0 || n >= filecnt)
155 return;
156
157 if (filecnt == 1) {
158 if (!manual)
159 fprintf(stderr, "%s: no more files to display, aborting\n", progname);
160 exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
161 }
162 if (files[n].flags & FF_MARK)
163 markcnt--;
164
165 if (files[n].path != files[n].name)
166 free((void*) files[n].path);
167 free((void*) files[n].name);
168
169 if (n + 1 < filecnt) {
170 if (tns.thumbs != NULL) {
171 if (tns.thumbs[n].im != NULL) {
172 imlib_context_set_image(tns.thumbs[n].im);
173 imlib_free_image_and_decache();
174 }
175 memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) *
176 sizeof(*tns.thumbs));
177 memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs));
178 }
179 memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files));
180 }
181 filecnt--;
182 if (fileidx > n || fileidx == filecnt)
183 fileidx--;
184 if (alternate > n || alternate == filecnt)
185 alternate--;
186 if (markidx > n || markidx == filecnt)
187 markidx--;
188}
189
190void set_timeout(timeout_f handler, int time, bool overwrite)
191{
192 unsigned int i;
193
194 for (i = 0; i < ARRLEN(timeouts); i++) {
195 if (timeouts[i].handler == handler) {
196 if (!timeouts[i].active || overwrite) {
197 gettimeofday(&timeouts[i].when, 0);
198 TV_ADD_MSEC(&timeouts[i].when, time);
199 timeouts[i].active = true;
200 }
201 return;
202 }
203 }
204}
205
206void reset_timeout(timeout_f handler)
207{
208 unsigned int i;
209
210 for (i = 0; i < ARRLEN(timeouts); i++) {
211 if (timeouts[i].handler == handler) {
212 timeouts[i].active = false;
213 return;
214 }
215 }
216}
217
218static bool check_timeouts(int *t)
219{
220 int i = 0, tdiff, tmin = -1;
221 struct timeval now;
222
223 while (i < (int)ARRLEN(timeouts)) {
224 if (timeouts[i].active) {
225 gettimeofday(&now, 0);
226 tdiff = TV_DIFF(&timeouts[i].when, &now);
227 if (tdiff <= 0) {
228 timeouts[i].active = false;
229 if (timeouts[i].handler != NULL)
230 timeouts[i].handler();
231 i = tmin = -1;
232 } else if (tmin < 0 || tdiff < tmin) {
233 tmin = tdiff;
234 }
235 }
236 i++;
237 }
238 if (tmin > 0 && t != NULL)
239 *t = tmin;
240 return tmin > 0;
241}
242
243static size_t get_win_title(char *buf, size_t len)
244{
245 char *argv[8];
246 spawn_t pfd;
247 char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12];
248 ssize_t n = -1;
249
250 if (wintitle.f.err || buf == NULL || len == 0)
251 return 0;
252
253 if (mode == MODE_IMAGE) {
254 snprintf(w, ARRLEN(w), "%d", img.w);
255 snprintf(h, ARRLEN(h), "%d", img.h);
256 snprintf(z, ARRLEN(z), "%d", (int)(img.zoom * 100));
257 }
258 snprintf(fidx, ARRLEN(fidx), "%d", fileidx+1);
259 snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt);
260 construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path,
261 fidx, fcnt, w, h, z, NULL);
262 pfd = spawn(wintitle.f.cmd, argv, X_READ);
263 if (pfd.readfd >= 0) {
264 if ((n = read(pfd.readfd, buf, len-1)) > 0)
265 buf[n] = '\0';
266 close(pfd.readfd);
267 }
268
269 return MAX(0, n);
270}
271
272void close_info(void)
273{
274 if (info.fd != -1) {
275 kill(info.pid, SIGTERM);
276 close(info.fd);
277 info.fd = -1;
278 }
279}
280
281void open_info(void)
282{
283 spawn_t pfd;
284 char w[12] = "", h[12] = "";
285 char *argv[6];
286 char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd;
287 bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err;
288
289 if (ferr || info.fd >= 0 || win.bar.h == 0)
290 return;
291 win.bar.l.buf[0] = '\0';
292 if (mode == MODE_IMAGE) {
293 snprintf(w, sizeof(w), "%d", img.w);
294 snprintf(h, sizeof(h), "%d", img.h);
295 }
296 construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h,
297 files[fileidx].path, NULL);
298 pfd = spawn(cmd, argv, X_READ);
299 if (pfd.readfd >= 0) {
300 fcntl(pfd.readfd, F_SETFL, O_NONBLOCK);
301 info.fd = pfd.readfd;
302 info.pid = pfd.pid;
303 }
304}
305
306static void read_info(void)
307{
308 ssize_t i, n;
309
310 if ((n = read(info.fd, win.bar.l.buf, win.bar.l.size - 1)) > 0) {
311 win.bar.l.buf[n] = '\0';
312 for (i = 0; i < n; ++i) {
313 if (win.bar.l.buf[i] == '\n')
314 win.bar.l.buf[i] = ' ';
315 }
316 win_draw(&win);
317 }
318 close_info();
319}
320
321void load_image(int new)
322{
323 bool prev = new < fileidx;
324 static int current;
325
326 if (new < 0 || new >= filecnt)
327 return;
328
329 if (win.xwin != None)
330 win_set_cursor(&win, CURSOR_WATCH);
331 reset_timeout(slideshow);
332
333 if (new != current)
334 alternate = current;
335
336 img_close(&img, false);
337 while (!img_load(&img, &files[new])) {
338 remove_file(new, false);
339 if (new >= filecnt)
340 new = filecnt - 1;
341 else if (new > 0 && prev)
342 new--;
343 }
344 files[new].flags &= ~FF_WARN;
345 fileidx = current = new;
346
347 close_info();
348 open_info();
349 arl_add(&arl, files[fileidx].path);
350 title_dirty = true;
351
352 if (img.multi.cnt > 0 && img.multi.animate)
353 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
354 else
355 reset_timeout(animate);
356}
357
358bool mark_image(int n, bool on)
359{
360 markidx = n;
361 if (!!(files[n].flags & FF_MARK) != on) {
362 files[n].flags ^= FF_MARK;
363 markcnt += on ? 1 : -1;
364 if (mode == MODE_THUMB)
365 tns_mark(&tns, n, on);
366 return true;
367 }
368 return false;
369}
370
371static void bar_put(win_bar_t *bar, const char *fmt, ...)
372{
373 size_t len = bar->size - (bar->p - bar->buf), n;
374 va_list ap;
375
376 va_start(ap, fmt);
377 n = vsnprintf(bar->p, len, fmt, ap);
378 bar->p += MIN(len, n);
379 va_end(ap);
380}
381
382static void update_info(void)
383{
384 unsigned int i, fn, fw;
385 const char *mark;
386 win_bar_t *l = &win.bar.l, *r = &win.bar.r;
387
388 /* update bar contents */
389 if (win.bar.h == 0 || extprefix)
390 return;
391 for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
392 mark = files[fileidx].flags & FF_MARK ? "* " : "";
393 l->p = l->buf;
394 r->p = r->buf;
395 if (mode == MODE_THUMB) {
396 if (tns.loadnext < tns.end)
397 bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1);
398 else if (tns.initnext < filecnt)
399 bar_put(l, "Caching... %0*d", fw, tns.initnext + 1);
400 else if (info.ft.err)
401 strncpy(l->buf, files[fileidx].name, l->size);
402 bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
403 } else {
404 bar_put(r, "%s", mark);
405 if (img.ss.on) {
406 if (img.ss.delay % 10 != 0)
407 bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
408 else
409 bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
410 }
411 if (img.gamma)
412 bar_put(r, "G%+d" BAR_SEP, img.gamma);
413 bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
414 if (img.multi.cnt > 0) {
415 for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
416 bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
417 }
418 bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
419 if (info.f.err)
420 strncpy(l->buf, files[fileidx].name, l->size);
421 }
422}
423
424int nav_button(void)
425{
426 int x, y, nw;
427
428 if (NAV_WIDTH == 0)
429 return 1;
430
431 win_cursor_pos(&win, &x, &y);
432 nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH;
433 nw = MIN(nw, ((int)win.w + 1) / 2);
434
435 if (x < nw)
436 return 0;
437 else if (x < (int)win.w - nw)
438 return 1;
439 else
440 return 2;
441}
442
443void redraw(void)
444{
445 int t;
446
447 if (mode == MODE_IMAGE) {
448 img_render(&img);
449 if (img.ss.on) {
450 t = img.ss.delay * 100;
451 if (img.multi.cnt > 0 && img.multi.animate)
452 t = MAX(t, img.multi.length);
453 set_timeout(slideshow, t, false);
454 }
455 } else {
456 tns_render(&tns);
457 }
458 update_info();
459 if (title_dirty) {
460 size_t n;
461 char buf[512];
462
463 if ((n = get_win_title(buf, sizeof(buf))) > 0)
464 win_set_title(&win, buf, n);
465 title_dirty = false;
466 }
467 win_draw(&win);
468 reset_timeout(redraw);
469 reset_cursor();
470}
471
472void reset_cursor(void)
473{
474 int c;
475 unsigned int i;
476 cursor_t cursor = CURSOR_NONE;
477
478 if (mode == MODE_IMAGE) {
479 for (i = 0; i < ARRLEN(timeouts); i++) {
480 if (timeouts[i].handler == reset_cursor) {
481 if (timeouts[i].active) {
482 c = nav_button();
483 c = MAX(fileidx > 0 ? 0 : 1, c);
484 c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
485 cursor = imgcursor[c];
486 }
487 break;
488 }
489 }
490 } else {
491 if (tns.loadnext < tns.end || tns.initnext < filecnt)
492 cursor = CURSOR_WATCH;
493 else
494 cursor = CURSOR_ARROW;
495 }
496 win_set_cursor(&win, cursor);
497}
498
499void animate(void)
500{
501 if (img_frame_animate(&img)) {
502 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
503 redraw();
504 }
505}
506
507void slideshow(void)
508{
509 load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
510 redraw();
511}
512
513void clear_resize(void)
514{
515 resized = false;
516}
517
518static Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
519{
520 return ev->type == ButtonPress || ev->type == KeyPress;
521}
522
523void handle_key_handler(bool init)
524{
525 extprefix = init;
526 if (win.bar.h == 0)
527 return;
528 if (init) {
529 close_info();
530 snprintf(win.bar.l.buf, win.bar.l.size, "Getting key handler input "
531 "(%s to abort)...", XKeysymToString(KEYHANDLER_ABORT));
532 } else { /* abort */
533 open_info();
534 update_info();
535 }
536 win_draw(&win);
537}
538
539static bool run_key_handler(const char *key, unsigned int mask)
540{
541 FILE *pfs;
542 bool marked = mode == MODE_THUMB && markcnt > 0;
543 bool changed = false;
544 int f, i;
545 int fcnt = marked ? markcnt : 1;
546 char kstr[32];
547 struct stat *oldst, st;
548 XEvent dump;
549 char *argv[3];
550 spawn_t pfd;
551
552 if (keyhandler.f.err) {
553 if (!keyhandler.warned) {
554 error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
555 keyhandler.warned = true;
556 }
557 return false;
558 }
559 if (key == NULL)
560 return false;
561
562 close_info();
563 strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
564 win_draw(&win);
565 win_set_cursor(&win, CURSOR_WATCH);
566 setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1);
567
568 snprintf(kstr, sizeof(kstr), "%s%s%s%s",
569 mask & ControlMask ? "C-" : "",
570 mask & Mod1Mask ? "M-" : "",
571 mask & ShiftMask ? "S-" : "", key);
572 construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL);
573 pfd = spawn(keyhandler.f.cmd, argv, X_WRITE);
574 if (pfd.writefd < 0)
575 return false;
576 if ((pfs = fdopen(pfd.writefd, "w")) == NULL) {
577 error(0, errno, "open pipe");
578 close(pfd.writefd);
579 return false;
580 }
581
582 oldst = emalloc(fcnt * sizeof(*oldst));
583 for (f = i = 0; f < fcnt; i++) {
584 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
585 stat(files[i].path, &oldst[f]);
586 fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n');
587 f++;
588 }
589 }
590 fclose(pfs);
591 while (waitpid(pfd.pid, NULL, 0) == -1 && errno == EINTR);
592
593 for (f = i = 0; f < fcnt; i++) {
594 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
595 if (stat(files[i].path, &st) != 0 ||
596 memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
597 {
598 if (tns.thumbs != NULL) {
599 tns_unload(&tns, i);
600 tns.loadnext = MIN(tns.loadnext, i);
601 }
602 changed = true;
603 }
604 f++;
605 }
606 }
607 /* drop user input events that occurred while running the key handler */
608 while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
609
610 if (mode == MODE_IMAGE && changed) {
611 img_close(&img, true);
612 load_image(fileidx);
613 } else {
614 open_info();
615 }
616 free(oldst);
617 reset_cursor();
618 return true;
619}
620
621static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button,
622 unsigned int state, unsigned int implicit_mod)
623{
624 unsigned int i;
625 bool dirty = false;
626
627 for (i = 0; i < len; i++) {
628 if (bindings[i].ksym_or_button == ksym_or_button &&
629 MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) &&
630 bindings[i].cmd.func != NULL &&
631 (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode))
632 {
633 if (bindings[i].cmd.func(bindings[i].arg))
634 dirty = true;
635 }
636 }
637 return dirty;
638}
639
640static void on_keypress(XKeyEvent *kev)
641{
642 unsigned int sh = 0;
643 KeySym ksym, shksym;
644 char dummy, key;
645 bool dirty = false;
646
647 XLookupString(kev, &key, 1, &ksym, NULL);
648
649 if (kev->state & ShiftMask) {
650 kev->state &= ~ShiftMask;
651 XLookupString(kev, &dummy, 1, &shksym, NULL);
652 kev->state |= ShiftMask;
653 if (ksym != shksym)
654 sh = ShiftMask;
655 }
656 if (IsModifierKey(ksym))
657 return;
658 if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) {
659 handle_key_handler(false);
660 } else if (extprefix) {
661 if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh)))
662 extprefix = false;
663 else
664 handle_key_handler(false);
665 } else if (key >= '0' && key <= '9') {
666 /* number prefix for commands */
667 prefix = prefix * 10 + (int) (key - '0');
668 return;
669 } else {
670 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
671 }
672 if (dirty)
673 redraw();
674 prefix = 0;
675}
676
677static void on_buttonpress(const XButtonEvent *bev)
678{
679 bool dirty = false;
680
681 if (mode == MODE_IMAGE) {
682 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
683 reset_cursor();
684 dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0);
685 } else { /* thumbnail mode */
686 dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0);
687 }
688 if (dirty)
689 redraw();
690 prefix = 0;
691}
692
693static void run(void)
694{
695 enum { FD_X, FD_INFO, FD_ARL, FD_CNT };
696 struct pollfd pfd[FD_CNT];
697 int timeout = 0;
698 const struct timespec ten_ms = {0, 10000000};
699 bool discard, init_thumb, load_thumb, to_set;
700 XEvent ev, nextev;
701
702 xbutton_ev = &ev.xbutton;
703 while (true) {
704 to_set = check_timeouts(&timeout);
705 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
706 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
707
708 if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
709 arl.fd != -1) && XPending(win.env.dpy) == 0)
710 {
711 if (load_thumb) {
712 set_timeout(redraw, TO_REDRAW_THUMBS, false);
713 if (!tns_load(&tns, tns.loadnext, false, false)) {
714 remove_file(tns.loadnext, false);
715 tns.dirty = true;
716 }
717 if (tns.loadnext >= tns.end) {
718 open_info();
719 redraw();
720 }
721 } else if (init_thumb) {
722 set_timeout(redraw, TO_REDRAW_THUMBS, false);
723 if (!tns_load(&tns, tns.initnext, false, true))
724 remove_file(tns.initnext, false);
725 } else {
726 pfd[FD_X].fd = ConnectionNumber(win.env.dpy);
727 pfd[FD_INFO].fd = info.fd;
728 pfd[FD_ARL].fd = arl.fd;
729 pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_ARL].events = POLLIN;
730
731 if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0)
732 continue;
733 if (pfd[FD_INFO].revents & POLLIN)
734 read_info();
735 if (pfd[FD_ARL].revents & POLLIN) {
736 if (arl_handle(&arl)) {
737 /* when too fast, imlib2 can't load the image */
738 nanosleep(&ten_ms, NULL);
739 img_close(&img, true);
740 load_image(fileidx);
741 redraw();
742 }
743 }
744 }
745 continue;
746 }
747
748 do {
749 XNextEvent(win.env.dpy, &ev);
750 discard = false;
751 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
752 XPeekEvent(win.env.dpy, &nextev);
753 switch (ev.type) {
754 case ConfigureNotify:
755 case MotionNotify:
756 discard = ev.type == nextev.type;
757 break;
758 case KeyPress:
759 discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
760 && ev.xkey.keycode == nextev.xkey.keycode;
761 break;
762 }
763 }
764 } while (discard);
765
766 switch (ev.type) { /* handle events */
767 case ButtonPress:
768 on_buttonpress(&ev.xbutton);
769 break;
770 case ClientMessage:
771 if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
772 cg_quit(EXIT_SUCCESS);
773 break;
774 case DestroyNotify:
775 cg_quit(EXIT_FAILURE);
776 break;
777 case ConfigureNotify:
778 if (win_configure(&win, &ev.xconfigure)) {
779 if (mode == MODE_IMAGE) {
780 img.dirty = true;
781 img.checkpan = true;
782 } else {
783 tns.dirty = true;
784 }
785 if (!resized) {
786 redraw();
787 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
788 resized = true;
789 } else {
790 set_timeout(redraw, TO_REDRAW_RESIZE, false);
791 }
792 }
793 break;
794 case KeyPress:
795 on_keypress(&ev.xkey);
796 break;
797 case MotionNotify:
798 if (mode == MODE_IMAGE) {
799 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
800 reset_cursor();
801 }
802 break;
803 }
804 }
805}
806
807static int fncmp(const void *a, const void *b)
808{
809 return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
810}
811
812static void sigchld(int sig)
813{
814 while (waitpid(-1, NULL, WNOHANG) > 0);
815}
816
817static void setup_signal(int sig, void (*handler)(int sig))
818{
819 struct sigaction sa;
820
821 sa.sa_handler = handler;
822 sigemptyset(&sa.sa_mask);
823 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
824 if (sigaction(sig, &sa, 0) == -1)
825 error(EXIT_FAILURE, errno, "signal %d", sig);
826}
827
828int main(int argc, char *argv[])
829{
830 int i, start;
831 size_t n;
832 char *filename;
833 const char *homedir, *dsuffix = "";
834 struct stat fstats;
835 r_dir_t dir;
836
837 setup_signal(SIGCHLD, sigchld);
838 setup_signal(SIGPIPE, SIG_IGN);
839
840 setlocale(LC_COLLATE, "");
841
842 parse_options(argc, argv);
843
844 if (options->clean_cache) {
845 tns_init(&tns, NULL, NULL, NULL, NULL);
846 tns_clean_cache();
847 exit(EXIT_SUCCESS);
848 }
849
850 if (options->filecnt == 0 && !options->from_stdin) {
851 print_usage();
852 exit(EXIT_FAILURE);
853 }
854
855 if (options->recursive || options->from_stdin)
856 filecnt = 1024;
857 else
858 filecnt = options->filecnt;
859
860 files = ecalloc(filecnt, sizeof(*files));
861 fileidx = 0;
862
863 if (options->from_stdin) {
864 n = 0;
865 filename = NULL;
866 while (xgetline(&filename, &n))
867 check_add_file(filename, true);
868 free(filename);
869 }
870
871 for (i = 0; i < options->filecnt; i++) {
872 filename = options->filenames[i];
873
874 if (stat(filename, &fstats) < 0) {
875 error(0, errno, "%s", filename);
876 continue;
877 }
878 if (!S_ISDIR(fstats.st_mode)) {
879 check_add_file(filename, true);
880 } else {
881 if (r_opendir(&dir, filename, options->recursive) < 0) {
882 error(0, errno, "%s", filename);
883 continue;
884 }
885 start = fileidx;
886 while ((filename = r_readdir(&dir, true)) != NULL) {
887 check_add_file(filename, false);
888 free((void*) filename);
889 }
890 r_closedir(&dir);
891 if (fileidx - start > 1)
892 qsort(files + start, fileidx - start, sizeof(*files), fncmp);
893 }
894 }
895
896 if (fileidx == 0)
897 error(EXIT_FAILURE, 0, "No valid image file given, aborting");
898
899 filecnt = fileidx;
900 fileidx = options->startnum < filecnt ? options->startnum : 0;
901
902 win_init(&win);
903 img_init(&img, &win);
904 arl_init(&arl);
905
906 if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
907 homedir = getenv("HOME");
908 dsuffix = "/.config";
909 }
910 if (homedir != NULL) {
911 extcmd_t *cmd[] = { &info.f, &info.ft, &keyhandler.f, &wintitle.f };
912 const char *name[] = { "image-info", "thumb-info", "key-handler", "win-title" };
913 const char *s = "/nsxiv/exec/";
914
915 for (i = 0; i < (int)ARRLEN(cmd); i++) {
916 n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
917 cmd[i]->cmd = emalloc(n);
918 snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
919 if (access(cmd[i]->cmd, X_OK) != 0)
920 cmd[i]->err = errno;
921 }
922 } else {
923 error(0, 0, "Exec directory not found");
924 }
925 info.fd = -1;
926
927 if (options->thumb_mode) {
928 mode = MODE_THUMB;
929 tns_init(&tns, files, &filecnt, &fileidx, &win);
930 while (!tns_load(&tns, fileidx, false, false))
931 remove_file(fileidx, false);
932 } else {
933 mode = MODE_IMAGE;
934 tns.thumbs = NULL;
935 load_image(fileidx);
936 }
937 win_open(&win);
938 win_set_cursor(&win, CURSOR_WATCH);
939
940 atexit(cleanup);
941
942 set_timeout(redraw, 25, false);
943
944 run();
945
946 return 0;
947}