Krolyxon
Commit b42e3ef

Branches

Refs →

nsxivmain.c

984 lines · 21.7 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 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
474 redraw();
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 bool 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 false;
528 }
529 if (key == NULL)
530 return false;
531
532 if (pipe(pfd) < 0) {
533 error(0, errno, "pipe");
534 return false;
535 }
536 if ((pfs = fdopen(pfd[1], "w")) == NULL) {
537 error(0, errno, "open pipe");
538 close(pfd[0]), close(pfd[1]);
539 return false;
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 return true;
606}
607
608static bool process_bindings(const keymap_t *bindings, 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 (bindings[i].ksym_or_button == ksym_or_button &&
616 MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) &&
617 bindings[i].cmd.func &&
618 (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode))
619 {
620 if (bindings[i].cmd.func(bindings[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 if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh)))
649 extprefix = false;
650 else
651 handle_key_handler(false);
652 } else if (key >= '0' && key <= '9') {
653 /* number prefix for commands */
654 prefix = prefix * 10 + (int) (key - '0');
655 return;
656 } else {
657 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
658 }
659 if (dirty)
660 redraw();
661 prefix = 0;
662}
663
664static void on_buttonpress(XButtonEvent *bev)
665{
666 int sel;
667 bool dirty = false;
668 static Time firstclick;
669
670 if (mode == MODE_IMAGE) {
671 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
672 reset_cursor();
673 dirty = process_bindings(buttons, ARRLEN(buttons), bev->button, bev->state, 0);
674 if (dirty)
675 redraw();
676 } else {
677 /* thumbnail mode (hard-coded) */
678 switch (bev->button) {
679 case Button1:
680 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
681 if (sel != fileidx) {
682 tns_highlight(&tns, fileidx, false);
683 tns_highlight(&tns, sel, true);
684 fileidx = sel;
685 firstclick = bev->time;
686 redraw();
687 } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) {
688 mode = MODE_IMAGE;
689 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
690 load_image(fileidx);
691 redraw();
692 } else {
693 firstclick = bev->time;
694 }
695 }
696 break;
697 case Button3:
698 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
699 bool on = !(files[sel].flags & FF_MARK);
700 XEvent e;
701
702 while (true) {
703 if (sel >= 0 && mark_image(sel, on))
704 redraw();
705 XMaskEvent(win.env.dpy,
706 ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
707 if (e.type == ButtonPress || e.type == ButtonRelease)
708 break;
709 while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
710 sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y);
711 }
712 }
713 break;
714 case Button4:
715 case Button5:
716 if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
717 (bev->state & ControlMask) != 0))
718 redraw();
719 break;
720 }
721 }
722 prefix = 0;
723}
724
725static void run(void)
726{
727 int xfd;
728 fd_set fds;
729 struct timeval timeout;
730 const struct timespec ten_ms = {0, 10000000};
731 bool discard, init_thumb, load_thumb, to_set;
732 XEvent ev, nextev;
733
734 while (true) {
735 to_set = check_timeouts(&timeout);
736 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
737 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
738
739 if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
740 arl.fd != -1) && XPending(win.env.dpy) == 0)
741 {
742 if (load_thumb) {
743 set_timeout(redraw, TO_REDRAW_THUMBS, false);
744 if (!tns_load(&tns, tns.loadnext, false, false)) {
745 remove_file(tns.loadnext, false);
746 tns.dirty = true;
747 }
748 if (tns.loadnext >= tns.end)
749 redraw();
750 } else if (init_thumb) {
751 set_timeout(redraw, TO_REDRAW_THUMBS, false);
752 if (!tns_load(&tns, tns.initnext, false, true))
753 remove_file(tns.initnext, false);
754 } else {
755 xfd = ConnectionNumber(win.env.dpy);
756 FD_ZERO(&fds);
757 FD_SET(xfd, &fds);
758 if (info.fd != -1) {
759 FD_SET(info.fd, &fds);
760 xfd = MAX(xfd, info.fd);
761 }
762 if (arl.fd != -1) {
763 FD_SET(arl.fd, &fds);
764 xfd = MAX(xfd, arl.fd);
765 }
766 select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL);
767 if (info.fd != -1 && FD_ISSET(info.fd, &fds))
768 read_info();
769 if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) {
770 if (arl_handle(&arl)) {
771 /* when too fast, imlib2 can't load the image */
772 nanosleep(&ten_ms, NULL);
773 img_close(&img, true);
774 load_image(fileidx);
775 redraw();
776 }
777 }
778 }
779 continue;
780 }
781
782 do {
783 XNextEvent(win.env.dpy, &ev);
784 discard = false;
785 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
786 XPeekEvent(win.env.dpy, &nextev);
787 switch (ev.type) {
788 case ConfigureNotify:
789 case MotionNotify:
790 discard = ev.type == nextev.type;
791 break;
792 case KeyPress:
793 discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
794 && ev.xkey.keycode == nextev.xkey.keycode;
795 break;
796 }
797 }
798 } while (discard);
799
800 switch (ev.type) {
801 /* handle events */
802 case ButtonPress:
803 on_buttonpress(&ev.xbutton);
804 break;
805 case ClientMessage:
806 if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
807 cg_quit(EXIT_SUCCESS);
808 break;
809 case DestroyNotify:
810 cg_quit(EXIT_FAILURE);
811 break;
812 case ConfigureNotify:
813 if (win_configure(&win, &ev.xconfigure)) {
814 if (mode == MODE_IMAGE) {
815 img.dirty = true;
816 img.checkpan = true;
817 } else {
818 tns.dirty = true;
819 }
820 if (!resized) {
821 redraw();
822 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
823 resized = true;
824 } else {
825 set_timeout(redraw, TO_REDRAW_RESIZE, false);
826 }
827 }
828 break;
829 case KeyPress:
830 on_keypress(&ev.xkey);
831 break;
832 case MotionNotify:
833 if (mode == MODE_IMAGE) {
834 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
835 reset_cursor();
836 }
837 break;
838 }
839 }
840}
841
842static int fncmp(const void *a, const void *b)
843{
844 return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
845}
846
847void sigchld(int sig)
848{
849 while (waitpid(-1, NULL, WNOHANG) > 0);
850}
851
852static void setup_signal(int sig, void (*handler)(int sig))
853{
854 struct sigaction sa;
855
856 sa.sa_handler = handler;
857 sigemptyset(&sa.sa_mask);
858 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
859 if (sigaction(sig, &sa, 0) == -1)
860 error(EXIT_FAILURE, errno, "signal %d", sig);
861}
862
863int main(int argc, char *argv[])
864{
865 int i, start;
866 size_t n;
867 char *filename;
868 const char *homedir, *dsuffix = "";
869 struct stat fstats;
870 r_dir_t dir;
871
872 setup_signal(SIGCHLD, sigchld);
873 setup_signal(SIGPIPE, SIG_IGN);
874
875 setlocale(LC_COLLATE, "");
876
877 parse_options(argc, argv);
878
879 if (options->clean_cache) {
880 tns_init(&tns, NULL, NULL, NULL, NULL);
881 tns_clean_cache();
882 exit(EXIT_SUCCESS);
883 }
884
885 if (options->filecnt == 0 && !options->from_stdin) {
886 print_usage();
887 exit(EXIT_FAILURE);
888 }
889
890 if (options->recursive || options->from_stdin)
891 filecnt = 1024;
892 else
893 filecnt = options->filecnt;
894
895 files = emalloc(filecnt * sizeof(*files));
896 memset(files, 0, filecnt * sizeof(*files));
897 fileidx = 0;
898
899 if (options->from_stdin) {
900 n = 0;
901 filename = NULL;
902 while (xgetline(&filename, &n))
903 check_add_file(filename, true);
904 free(filename);
905 }
906
907 for (i = 0; i < options->filecnt; i++) {
908 filename = options->filenames[i];
909
910 if (stat(filename, &fstats) < 0) {
911 error(0, errno, "%s", filename);
912 continue;
913 }
914 if (!S_ISDIR(fstats.st_mode)) {
915 check_add_file(filename, true);
916 } else {
917 if (r_opendir(&dir, filename, options->recursive) < 0) {
918 error(0, errno, "%s", filename);
919 continue;
920 }
921 start = fileidx;
922 while ((filename = r_readdir(&dir, true)) != NULL) {
923 check_add_file(filename, false);
924 free((void*) filename);
925 }
926 r_closedir(&dir);
927 if (fileidx - start > 1)
928 qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
929 }
930 }
931
932 if (fileidx == 0)
933 error(EXIT_FAILURE, 0, "No valid image file given, aborting");
934
935 filecnt = fileidx;
936 fileidx = options->startnum < filecnt ? options->startnum : 0;
937
938 win_init(&win);
939 img_init(&img, &win);
940 arl_init(&arl);
941
942 if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
943 homedir = getenv("HOME");
944 dsuffix = "/.config";
945 }
946 if (homedir != NULL) {
947 extcmd_t *cmd[] = { &info.f, &keyhandler.f };
948 const char *name[] = { "image-info", "key-handler" };
949 const char *s = "/nsxiv/exec/";
950
951 for (i = 0; i < ARRLEN(cmd); i++) {
952 n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
953 cmd[i]->cmd = emalloc(n);
954 snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
955 if (access(cmd[i]->cmd, X_OK) != 0)
956 cmd[i]->err = errno;
957 }
958 } else {
959 error(0, 0, "Exec directory not found");
960 }
961 info.fd = -1;
962
963 if (options->thumb_mode) {
964 mode = MODE_THUMB;
965 tns_init(&tns, files, &filecnt, &fileidx, &win);
966 while (!tns_load(&tns, fileidx, false, false))
967 remove_file(fileidx, false);
968 } else {
969 mode = MODE_IMAGE;
970 tns.thumbs = NULL;
971 load_image(fileidx);
972 }
973 win_open(&win);
974 win_set_title(&win, files[fileidx].path);
975 win_set_cursor(&win, CURSOR_WATCH);
976
977 atexit(cleanup);
978
979 set_timeout(redraw, 25, false);
980
981 run();
982
983 return 0;
984}