Krolyxon
Commit 49d11f0

Branches

Refs →

nsxivmain.c

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