Krolyxon
Commit 127abbe

Branches

Refs →

nsxivmain.c

982 lines · 21.6 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) & USED_MODMASK)
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
70static bool 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 **************************/
100static void 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
116static void 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
206static bool 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
268static void 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
350static void 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
361static void 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
510static void 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 setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1);
553
554 if ((pid = fork()) == 0) {
555 close(pfd[1]);
556 dup2(pfd[0], 0);
557 execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL);
558 error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd);
559 }
560 close(pfd[0]);
561 if (pid < 0) {
562 error(0, errno, "fork");
563 fclose(pfs);
564 goto end;
565 }
566
567 for (f = i = 0; f < fcnt; i++) {
568 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
569 stat(files[i].path, &oldst[f]);
570 fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n');
571 f++;
572 }
573 }
574 fclose(pfs);
575 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
576
577 for (f = i = 0; f < fcnt; i++) {
578 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
579 if (stat(files[i].path, &st) != 0 ||
580 memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
581 {
582 if (tns.thumbs != NULL) {
583 tns_unload(&tns, i);
584 tns.loadnext = MIN(tns.loadnext, i);
585 }
586 changed = true;
587 }
588 f++;
589 }
590 }
591 /* drop user input events that occurred while running the key handler */
592 while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
593
594end:
595 if (mode == MODE_IMAGE) {
596 if (changed) {
597 img_close(&img, true);
598 load_image(fileidx);
599 } else {
600 open_info();
601 }
602 }
603 free(oldst);
604 reset_cursor();
605 redraw();
606}
607
608static bool process_bindings(const keymap_t *keys, unsigned int len, KeySym ksym_or_button,
609 unsigned int state, unsigned int implicit_mod)
610{
611 unsigned int i;
612 bool dirty = false;
613
614 for (i = 0; i < len; i++) {
615 if (keys[i].ksym_or_button == ksym_or_button &&
616 MODMASK(keys[i].mask | implicit_mod) == MODMASK(state) &&
617 keys[i].cmd.func &&
618 (keys[i].cmd.mode == MODE_ALL || keys[i].cmd.mode == mode))
619 {
620 if (keys[i].cmd.func(keys[i].arg))
621 dirty = true;
622 }
623 }
624 return dirty;
625}
626
627static void on_keypress(XKeyEvent *kev)
628{
629 unsigned int sh = 0;
630 KeySym ksym, shksym;
631 char dummy, key;
632 bool dirty = false;
633
634 XLookupString(kev, &key, 1, &ksym, NULL);
635
636 if (kev->state & ShiftMask) {
637 kev->state &= ~ShiftMask;
638 XLookupString(kev, &dummy, 1, &shksym, NULL);
639 kev->state |= ShiftMask;
640 if (ksym != shksym)
641 sh = ShiftMask;
642 }
643 if (IsModifierKey(ksym))
644 return;
645 if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) {
646 handle_key_handler(false);
647 } else if (extprefix) {
648 run_key_handler(XKeysymToString(ksym), kev->state & ~sh);
649 extprefix = false;
650 } else if (key >= '0' && key <= '9') {
651 /* number prefix for commands */
652 prefix = prefix * 10 + (int) (key - '0');
653 return;
654 } else {
655 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
656 }
657 if (dirty)
658 redraw();
659 prefix = 0;
660}
661
662static void on_buttonpress(XButtonEvent *bev)
663{
664 int sel;
665 bool dirty = false;
666 static Time firstclick;
667
668 if (mode == MODE_IMAGE) {
669 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
670 reset_cursor();
671 dirty = process_bindings(buttons, ARRLEN(buttons), bev->button, bev->state, 0);
672 if (dirty)
673 redraw();
674 } else {
675 /* thumbnail mode (hard-coded) */
676 switch (bev->button) {
677 case Button1:
678 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
679 if (sel != fileidx) {
680 tns_highlight(&tns, fileidx, false);
681 tns_highlight(&tns, sel, true);
682 fileidx = sel;
683 firstclick = bev->time;
684 redraw();
685 } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) {
686 mode = MODE_IMAGE;
687 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
688 load_image(fileidx);
689 redraw();
690 } else {
691 firstclick = bev->time;
692 }
693 }
694 break;
695 case Button3:
696 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
697 bool on = !(files[sel].flags & FF_MARK);
698 XEvent e;
699
700 while (true) {
701 if (sel >= 0 && mark_image(sel, on))
702 redraw();
703 XMaskEvent(win.env.dpy,
704 ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
705 if (e.type == ButtonPress || e.type == ButtonRelease)
706 break;
707 while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
708 sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y);
709 }
710 }
711 break;
712 case Button4:
713 case Button5:
714 if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
715 (bev->state & ControlMask) != 0))
716 redraw();
717 break;
718 }
719 }
720 prefix = 0;
721}
722
723static void run(void)
724{
725 int xfd;
726 fd_set fds;
727 struct timeval timeout;
728 const struct timespec ten_ms = {0, 10000000};
729 bool discard, init_thumb, load_thumb, to_set;
730 XEvent ev, nextev;
731
732 while (true) {
733 to_set = check_timeouts(&timeout);
734 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
735 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
736
737 if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
738 arl.fd != -1) && XPending(win.env.dpy) == 0)
739 {
740 if (load_thumb) {
741 set_timeout(redraw, TO_REDRAW_THUMBS, false);
742 if (!tns_load(&tns, tns.loadnext, false, false)) {
743 remove_file(tns.loadnext, false);
744 tns.dirty = true;
745 }
746 if (tns.loadnext >= tns.end)
747 redraw();
748 } else if (init_thumb) {
749 set_timeout(redraw, TO_REDRAW_THUMBS, false);
750 if (!tns_load(&tns, tns.initnext, false, true))
751 remove_file(tns.initnext, false);
752 } else {
753 xfd = ConnectionNumber(win.env.dpy);
754 FD_ZERO(&fds);
755 FD_SET(xfd, &fds);
756 if (info.fd != -1) {
757 FD_SET(info.fd, &fds);
758 xfd = MAX(xfd, info.fd);
759 }
760 if (arl.fd != -1) {
761 FD_SET(arl.fd, &fds);
762 xfd = MAX(xfd, arl.fd);
763 }
764 select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL);
765 if (info.fd != -1 && FD_ISSET(info.fd, &fds))
766 read_info();
767 if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) {
768 if (arl_handle(&arl)) {
769 /* when too fast, imlib2 can't load the image */
770 nanosleep(&ten_ms, NULL);
771 img_close(&img, true);
772 load_image(fileidx);
773 redraw();
774 }
775 }
776 }
777 continue;
778 }
779
780 do {
781 XNextEvent(win.env.dpy, &ev);
782 discard = false;
783 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
784 XPeekEvent(win.env.dpy, &nextev);
785 switch (ev.type) {
786 case ConfigureNotify:
787 case MotionNotify:
788 discard = ev.type == nextev.type;
789 break;
790 case KeyPress:
791 discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
792 && ev.xkey.keycode == nextev.xkey.keycode;
793 break;
794 }
795 }
796 } while (discard);
797
798 switch (ev.type) {
799 /* handle events */
800 case ButtonPress:
801 on_buttonpress(&ev.xbutton);
802 break;
803 case ClientMessage:
804 if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
805 cg_quit();
806 break;
807 case DestroyNotify:
808 exit(EXIT_FAILURE);
809 break;
810 case ConfigureNotify:
811 if (win_configure(&win, &ev.xconfigure)) {
812 if (mode == MODE_IMAGE) {
813 img.dirty = true;
814 img.checkpan = true;
815 } else {
816 tns.dirty = true;
817 }
818 if (!resized) {
819 redraw();
820 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
821 resized = true;
822 } else {
823 set_timeout(redraw, TO_REDRAW_RESIZE, false);
824 }
825 }
826 break;
827 case KeyPress:
828 on_keypress(&ev.xkey);
829 break;
830 case MotionNotify:
831 if (mode == MODE_IMAGE) {
832 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
833 reset_cursor();
834 }
835 break;
836 }
837 }
838}
839
840static int fncmp(const void *a, const void *b)
841{
842 return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
843}
844
845void sigchld(int sig)
846{
847 while (waitpid(-1, NULL, WNOHANG) > 0);
848}
849
850static void setup_signal(int sig, void (*handler)(int sig))
851{
852 struct sigaction sa;
853
854 sa.sa_handler = handler;
855 sigemptyset(&sa.sa_mask);
856 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
857 if (sigaction(sig, &sa, 0) == -1)
858 error(EXIT_FAILURE, errno, "signal %d", sig);
859}
860
861int main(int argc, char *argv[])
862{
863 int i, start;
864 size_t n;
865 char *filename;
866 const char *homedir, *dsuffix = "";
867 struct stat fstats;
868 r_dir_t dir;
869
870 setup_signal(SIGCHLD, sigchld);
871 setup_signal(SIGPIPE, SIG_IGN);
872
873 setlocale(LC_COLLATE, "");
874
875 parse_options(argc, argv);
876
877 if (options->clean_cache) {
878 tns_init(&tns, NULL, NULL, NULL, NULL);
879 tns_clean_cache();
880 exit(EXIT_SUCCESS);
881 }
882
883 if (options->filecnt == 0 && !options->from_stdin) {
884 print_usage();
885 exit(EXIT_FAILURE);
886 }
887
888 if (options->recursive || options->from_stdin)
889 filecnt = 1024;
890 else
891 filecnt = options->filecnt;
892
893 files = emalloc(filecnt * sizeof(*files));
894 memset(files, 0, filecnt * sizeof(*files));
895 fileidx = 0;
896
897 if (options->from_stdin) {
898 n = 0;
899 filename = NULL;
900 while (xgetline(&filename, &n))
901 check_add_file(filename, true);
902 free(filename);
903 }
904
905 for (i = 0; i < options->filecnt; i++) {
906 filename = options->filenames[i];
907
908 if (stat(filename, &fstats) < 0) {
909 error(0, errno, "%s", filename);
910 continue;
911 }
912 if (!S_ISDIR(fstats.st_mode)) {
913 check_add_file(filename, true);
914 } else {
915 if (r_opendir(&dir, filename, options->recursive) < 0) {
916 error(0, errno, "%s", filename);
917 continue;
918 }
919 start = fileidx;
920 while ((filename = r_readdir(&dir, true)) != NULL) {
921 check_add_file(filename, false);
922 free((void*) filename);
923 }
924 r_closedir(&dir);
925 if (fileidx - start > 1)
926 qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
927 }
928 }
929
930 if (fileidx == 0)
931 error(EXIT_FAILURE, 0, "No valid image file given, aborting");
932
933 filecnt = fileidx;
934 fileidx = options->startnum < filecnt ? options->startnum : 0;
935
936 win_init(&win);
937 img_init(&img, &win);
938 arl_init(&arl);
939
940 if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
941 homedir = getenv("HOME");
942 dsuffix = "/.config";
943 }
944 if (homedir != NULL) {
945 extcmd_t *cmd[] = { &info.f, &keyhandler.f };
946 const char *name[] = { "image-info", "key-handler" };
947 const char *s = "/nsxiv/exec/";
948
949 for (i = 0; i < ARRLEN(cmd); i++) {
950 n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
951 cmd[i]->cmd = emalloc(n);
952 snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
953 if (access(cmd[i]->cmd, X_OK) != 0)
954 cmd[i]->err = errno;
955 }
956 } else {
957 error(0, 0, "Exec directory not found");
958 }
959 info.fd = -1;
960
961 if (options->thumb_mode) {
962 mode = MODE_THUMB;
963 tns_init(&tns, files, &filecnt, &fileidx, &win);
964 while (!tns_load(&tns, fileidx, false, false))
965 remove_file(fileidx, false);
966 } else {
967 mode = MODE_IMAGE;
968 tns.thumbs = NULL;
969 load_image(fileidx);
970 }
971 win_open(&win);
972 win_set_title(&win, files[fileidx].path);
973 win_set_cursor(&win, CURSOR_WATCH);
974
975 atexit(cleanup);
976
977 set_timeout(redraw, 25, false);
978
979 run();
980
981 return 0;
982}