Krolyxon
Commit 7e3e600

Branches

Refs →

nsxivmain.c

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