Krolyxon
Commit 2df3320

Branches

Refs →

nsxivmain.c

981 lines · 21.3 KB · C

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