Krolyxon
Commit 5bb1df4

Branches

Refs →

nsxivmain.c

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