Krolyxon
Commit 3804b50

Branches

Refs →

nsxivmain.c

951 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 spawn_t pfd;
281 char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12];
282 ssize_t n = -1;
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 pfd = spawn(wintitle.f.cmd, argv, X_READ);
297 if (pfd.readfd >= 0) {
298 if ((n = read(pfd.readfd, buf, len-1)) > 0)
299 buf[n] = '\0';
300 close(pfd.readfd);
301 }
302
303 return MAX(0, n);
304}
305
306void close_info(void)
307{
308 if (info.fd != -1) {
309 kill(info.pid, SIGTERM);
310 close(info.fd);
311 info.fd = -1;
312 }
313}
314
315void open_info(void)
316{
317 spawn_t pfd;
318 char w[12] = "", h[12] = "";
319 char *argv[6];
320 char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd;
321 bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err;
322
323 if (ferr || info.fd >= 0 || win.bar.h == 0)
324 return;
325 win.bar.l.buf[0] = '\0';
326 if (mode == MODE_IMAGE) {
327 snprintf(w, sizeof(w), "%d", img.w);
328 snprintf(h, sizeof(h), "%d", img.h);
329 }
330 construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h,
331 files[fileidx].path, NULL);
332 pfd = spawn(cmd, argv, X_READ);
333 if (pfd.readfd >= 0) {
334 fcntl(pfd.readfd, F_SETFL, O_NONBLOCK);
335 info.fd = pfd.readfd;
336 info.pid = pfd.pid;
337 }
338}
339
340static void read_info(void)
341{
342 ssize_t i, n;
343
344 if ((n = read(info.fd, win.bar.l.buf, win.bar.l.size - 1)) > 0) {
345 win.bar.l.buf[n] = '\0';
346 for (i = 0; i < n; ++i) {
347 if (win.bar.l.buf[i] == '\n')
348 win.bar.l.buf[i] = ' ';
349 }
350 win_draw(&win);
351 }
352 close_info();
353}
354
355void load_image(int new)
356{
357 bool prev = new < fileidx;
358 static int current;
359
360 if (new < 0 || new >= filecnt)
361 return;
362
363 if (win.xwin != None)
364 win_set_cursor(&win, CURSOR_WATCH);
365 reset_timeout(slideshow);
366
367 if (new != current)
368 alternate = current;
369
370 img_close(&img, false);
371 while (!img_load(&img, &files[new])) {
372 remove_file(new, false);
373 if (new >= filecnt)
374 new = filecnt - 1;
375 else if (new > 0 && prev)
376 new--;
377 }
378 files[new].flags &= ~FF_WARN;
379 fileidx = current = new;
380
381 close_info();
382 open_info();
383 arl_add(&arl, files[fileidx].path);
384 title_dirty = true;
385
386 if (img.multi.cnt > 0 && img.multi.animate)
387 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
388 else
389 reset_timeout(animate);
390}
391
392bool mark_image(int n, bool on)
393{
394 markidx = n;
395 if (!!(files[n].flags & FF_MARK) != on) {
396 files[n].flags ^= FF_MARK;
397 markcnt += on ? 1 : -1;
398 if (mode == MODE_THUMB)
399 tns_mark(&tns, n, on);
400 return true;
401 }
402 return false;
403}
404
405static void bar_put(win_bar_t *bar, const char *fmt, ...)
406{
407 size_t len = bar->size - (bar->p - bar->buf), n;
408 va_list ap;
409
410 va_start(ap, fmt);
411 n = vsnprintf(bar->p, len, fmt, ap);
412 bar->p += MIN(len, n);
413 va_end(ap);
414}
415
416static void update_info(void)
417{
418 unsigned int i, fn, fw;
419 const char *mark;
420 win_bar_t *l = &win.bar.l, *r = &win.bar.r;
421
422 /* update bar contents */
423 if (win.bar.h == 0 || extprefix)
424 return;
425 for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
426 mark = files[fileidx].flags & FF_MARK ? "* " : "";
427 l->p = l->buf;
428 r->p = r->buf;
429 if (mode == MODE_THUMB) {
430 if (tns.loadnext < tns.end)
431 bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1);
432 else if (tns.initnext < filecnt)
433 bar_put(l, "Caching... %0*d", fw, tns.initnext + 1);
434 else if (info.ft.err)
435 strncpy(l->buf, files[fileidx].name, l->size);
436 bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
437 } else {
438 bar_put(r, "%s", mark);
439 if (img.ss.on) {
440 if (img.ss.delay % 10 != 0)
441 bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
442 else
443 bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
444 }
445 if (img.gamma)
446 bar_put(r, "G%+d" BAR_SEP, img.gamma);
447 bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
448 if (img.multi.cnt > 0) {
449 for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
450 bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
451 }
452 bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
453 if (info.f.err)
454 strncpy(l->buf, files[fileidx].name, l->size);
455 }
456}
457
458int nav_button(void)
459{
460 int x, y, nw;
461
462 if (NAV_WIDTH == 0)
463 return 1;
464
465 win_cursor_pos(&win, &x, &y);
466 nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH;
467 nw = MIN(nw, ((int)win.w + 1) / 2);
468
469 if (x < nw)
470 return 0;
471 else if (x < (int)win.w - nw)
472 return 1;
473 else
474 return 2;
475}
476
477void redraw(void)
478{
479 int t;
480
481 if (mode == MODE_IMAGE) {
482 img_render(&img);
483 if (img.ss.on) {
484 t = img.ss.delay * 100;
485 if (img.multi.cnt > 0 && img.multi.animate)
486 t = MAX(t, img.multi.length);
487 set_timeout(slideshow, t, false);
488 }
489 } else {
490 tns_render(&tns);
491 }
492 update_info();
493 if (title_dirty) {
494 size_t n;
495 char buf[512];
496
497 if ((n = get_win_title(buf, sizeof(buf))) > 0)
498 win_set_title(&win, buf, n);
499 title_dirty = false;
500 }
501 win_draw(&win);
502 reset_timeout(redraw);
503 reset_cursor();
504}
505
506void reset_cursor(void)
507{
508 int c;
509 unsigned int i;
510 cursor_t cursor = CURSOR_NONE;
511
512 if (mode == MODE_IMAGE) {
513 for (i = 0; i < ARRLEN(timeouts); i++) {
514 if (timeouts[i].handler == reset_cursor) {
515 if (timeouts[i].active) {
516 c = nav_button();
517 c = MAX(fileidx > 0 ? 0 : 1, c);
518 c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
519 cursor = imgcursor[c];
520 }
521 break;
522 }
523 }
524 } else {
525 if (tns.loadnext < tns.end || tns.initnext < filecnt)
526 cursor = CURSOR_WATCH;
527 else
528 cursor = CURSOR_ARROW;
529 }
530 win_set_cursor(&win, cursor);
531}
532
533void animate(void)
534{
535 if (img_frame_animate(&img)) {
536 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
537 redraw();
538 }
539}
540
541void slideshow(void)
542{
543 load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
544 redraw();
545}
546
547void clear_resize(void)
548{
549 resized = false;
550}
551
552static Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
553{
554 return ev->type == ButtonPress || ev->type == KeyPress;
555}
556
557void handle_key_handler(bool init)
558{
559 extprefix = init;
560 if (win.bar.h == 0)
561 return;
562 if (init) {
563 close_info();
564 snprintf(win.bar.l.buf, win.bar.l.size, "Getting key handler input "
565 "(%s to abort)...", XKeysymToString(KEYHANDLER_ABORT));
566 } else { /* abort */
567 open_info();
568 update_info();
569 }
570 win_draw(&win);
571}
572
573static bool run_key_handler(const char *key, unsigned int mask)
574{
575 FILE *pfs;
576 bool marked = mode == MODE_THUMB && markcnt > 0;
577 bool changed = false;
578 int f, i;
579 int fcnt = marked ? markcnt : 1;
580 char kstr[32];
581 struct stat *oldst, st;
582 XEvent dump;
583 char *argv[3];
584 spawn_t pfd;
585
586 if (keyhandler.f.err) {
587 if (!keyhandler.warned) {
588 error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
589 keyhandler.warned = true;
590 }
591 return false;
592 }
593 if (key == NULL)
594 return false;
595
596 close_info();
597 strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
598 win_draw(&win);
599 win_set_cursor(&win, CURSOR_WATCH);
600 setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1);
601
602 snprintf(kstr, sizeof(kstr), "%s%s%s%s",
603 mask & ControlMask ? "C-" : "",
604 mask & Mod1Mask ? "M-" : "",
605 mask & ShiftMask ? "S-" : "", key);
606 construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL);
607 pfd = spawn(keyhandler.f.cmd, argv, X_WRITE);
608 if (pfd.writefd < 0)
609 return false;
610 if ((pfs = fdopen(pfd.writefd, "w")) == NULL) {
611 error(0, errno, "open pipe");
612 close(pfd.writefd);
613 return false;
614 }
615
616 oldst = emalloc(fcnt * sizeof(*oldst));
617 for (f = i = 0; f < fcnt; i++) {
618 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
619 stat(files[i].path, &oldst[f]);
620 fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n');
621 f++;
622 }
623 }
624 fclose(pfs);
625 while (waitpid(pfd.pid, NULL, 0) == -1 && errno == EINTR);
626
627 for (f = i = 0; f < fcnt; i++) {
628 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
629 if (stat(files[i].path, &st) != 0 ||
630 memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
631 {
632 if (tns.thumbs != NULL) {
633 tns_unload(&tns, i);
634 tns.loadnext = MIN(tns.loadnext, i);
635 }
636 changed = true;
637 }
638 f++;
639 }
640 }
641 /* drop user input events that occurred while running the key handler */
642 while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
643
644 if (mode == MODE_IMAGE && changed) {
645 img_close(&img, true);
646 load_image(fileidx);
647 } else {
648 open_info();
649 }
650 free(oldst);
651 reset_cursor();
652 return true;
653}
654
655static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button,
656 unsigned int state, unsigned int implicit_mod)
657{
658 unsigned int i;
659 bool dirty = false;
660
661 for (i = 0; i < len; i++) {
662 if (bindings[i].ksym_or_button == ksym_or_button &&
663 MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) &&
664 bindings[i].cmd.func != NULL &&
665 (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode))
666 {
667 if (bindings[i].cmd.func(bindings[i].arg))
668 dirty = true;
669 }
670 }
671 return dirty;
672}
673
674static void on_keypress(XKeyEvent *kev)
675{
676 unsigned int sh = 0;
677 KeySym ksym, shksym;
678 char dummy, key;
679 bool dirty = false;
680
681 XLookupString(kev, &key, 1, &ksym, NULL);
682
683 if (kev->state & ShiftMask) {
684 kev->state &= ~ShiftMask;
685 XLookupString(kev, &dummy, 1, &shksym, NULL);
686 kev->state |= ShiftMask;
687 if (ksym != shksym)
688 sh = ShiftMask;
689 }
690 if (IsModifierKey(ksym))
691 return;
692 if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) {
693 handle_key_handler(false);
694 } else if (extprefix) {
695 if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh)))
696 extprefix = false;
697 else
698 handle_key_handler(false);
699 } else if (key >= '0' && key <= '9') {
700 /* number prefix for commands */
701 prefix = prefix * 10 + (int) (key - '0');
702 return;
703 } else {
704 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
705 }
706 if (dirty)
707 redraw();
708 prefix = 0;
709}
710
711static void on_buttonpress(const XButtonEvent *bev)
712{
713 bool dirty = false;
714
715 if (mode == MODE_IMAGE) {
716 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
717 reset_cursor();
718 dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0);
719 } else { /* thumbnail mode */
720 dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0);
721 }
722 if (dirty)
723 redraw();
724 prefix = 0;
725}
726
727static void run(void)
728{
729 enum { FD_X, FD_INFO, FD_ARL, FD_CNT };
730 struct pollfd pfd[FD_CNT];
731 int timeout = 0;
732 const struct timespec ten_ms = {0, 10000000};
733 bool discard, init_thumb, load_thumb, to_set;
734 XEvent ev, nextev;
735
736 xbutton_ev = &ev.xbutton;
737 while (true) {
738 to_set = check_timeouts(&timeout);
739 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
740 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
741
742 if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
743 arl.fd != -1) && XPending(win.env.dpy) == 0)
744 {
745 if (load_thumb) {
746 set_timeout(redraw, TO_REDRAW_THUMBS, false);
747 if (!tns_load(&tns, tns.loadnext, false, false)) {
748 remove_file(tns.loadnext, false);
749 tns.dirty = true;
750 }
751 if (tns.loadnext >= tns.end) {
752 open_info();
753 redraw();
754 }
755 } else if (init_thumb) {
756 set_timeout(redraw, TO_REDRAW_THUMBS, false);
757 if (!tns_load(&tns, tns.initnext, false, true))
758 remove_file(tns.initnext, false);
759 } else {
760 pfd[FD_X].fd = ConnectionNumber(win.env.dpy);
761 pfd[FD_INFO].fd = info.fd;
762 pfd[FD_ARL].fd = arl.fd;
763 pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_ARL].events = POLLIN;
764
765 if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0)
766 continue;
767 if (pfd[FD_INFO].revents & POLLIN)
768 read_info();
769 if (pfd[FD_ARL].revents & POLLIN) {
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) { /* handle events */
801 case ButtonPress:
802 on_buttonpress(&ev.xbutton);
803 break;
804 case ClientMessage:
805 if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
806 cg_quit(EXIT_SUCCESS);
807 break;
808 case DestroyNotify:
809 cg_quit(EXIT_FAILURE);
810 break;
811 case ConfigureNotify:
812 if (win_configure(&win, &ev.xconfigure)) {
813 if (mode == MODE_IMAGE) {
814 img.dirty = true;
815 img.checkpan = true;
816 } else {
817 tns.dirty = true;
818 }
819 if (!resized) {
820 redraw();
821 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
822 resized = true;
823 } else {
824 set_timeout(redraw, TO_REDRAW_RESIZE, false);
825 }
826 }
827 break;
828 case KeyPress:
829 on_keypress(&ev.xkey);
830 break;
831 case MotionNotify:
832 if (mode == MODE_IMAGE) {
833 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
834 reset_cursor();
835 }
836 break;
837 }
838 }
839}
840
841static void sigchld(int sig)
842{
843 while (waitpid(-1, NULL, WNOHANG) > 0);
844}
845
846static void setup_signal(int sig, void (*handler)(int sig))
847{
848 struct sigaction sa;
849
850 sa.sa_handler = handler;
851 sigemptyset(&sa.sa_mask);
852 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
853 if (sigaction(sig, &sa, 0) == -1)
854 error(EXIT_FAILURE, errno, "signal %d", sig);
855}
856
857int main(int argc, char *argv[])
858{
859 int i;
860 size_t n;
861 const char *homedir, *dsuffix = "";
862
863 setup_signal(SIGCHLD, sigchld);
864 setup_signal(SIGPIPE, SIG_IGN);
865
866 setlocale(LC_COLLATE, "");
867
868 parse_options(argc, argv);
869
870 if (options->clean_cache) {
871 tns_init(&tns, NULL, NULL, NULL, NULL);
872 tns_clean_cache();
873 exit(EXIT_SUCCESS);
874 }
875
876 if (options->filecnt == 0 && !options->from_stdin) {
877 print_usage();
878 exit(EXIT_FAILURE);
879 }
880
881 if (options->recursive || options->from_stdin)
882 filecnt = 1024;
883 else
884 filecnt = options->filecnt;
885
886 files = ecalloc(filecnt, sizeof(*files));
887 fileidx = 0;
888
889 if (options->from_stdin) {
890 char *filename = NULL;
891 n = 0;
892 while (xgetline(&filename, &n))
893 add_entry(filename);
894 free(filename);
895 }
896
897 for (i = 0; i < options->filecnt; i++)
898 add_entry(options->filenames[i]);
899
900 if (fileidx == 0)
901 error(EXIT_FAILURE, 0, "No valid image file given, aborting");
902
903 filecnt = fileidx;
904 fileidx = options->startnum < filecnt ? options->startnum : 0;
905
906 win_init(&win);
907 img_init(&img, &win);
908 arl_init(&arl);
909
910 if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
911 homedir = getenv("HOME");
912 dsuffix = "/.config";
913 }
914 if (homedir != NULL) {
915 extcmd_t *cmd[] = { &info.f, &info.ft, &keyhandler.f, &wintitle.f };
916 const char *name[] = { "image-info", "thumb-info", "key-handler", "win-title" };
917 const char *s = "/nsxiv/exec/";
918
919 for (i = 0; i < (int)ARRLEN(cmd); i++) {
920 n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
921 cmd[i]->cmd = emalloc(n);
922 snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
923 if (access(cmd[i]->cmd, X_OK) != 0)
924 cmd[i]->err = errno;
925 }
926 } else {
927 error(0, 0, "Exec directory not found");
928 }
929 info.fd = -1;
930
931 if (options->thumb_mode) {
932 mode = MODE_THUMB;
933 tns_init(&tns, files, &filecnt, &fileidx, &win);
934 while (!tns_load(&tns, fileidx, false, false))
935 remove_file(fileidx, false);
936 } else {
937 mode = MODE_IMAGE;
938 tns.thumbs = NULL;
939 load_image(fileidx);
940 }
941 win_open(&win);
942 win_set_cursor(&win, CURSOR_WATCH);
943
944 atexit(cleanup);
945
946 set_timeout(redraw, 25, false);
947
948 run();
949
950 return 0;
951}