Krolyxon
Commit 95bc9b4

Branches

Refs →

nsxivmain.c

955 lines · 21.4 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 if (img.brightness)
448 bar_put(r, "B%+d" BAR_SEP, img.brightness);
449 if (img.contrast)
450 bar_put(r, "C%+d" BAR_SEP, img.contrast);
451 bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
452 if (img.multi.cnt > 0) {
453 for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
454 bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
455 }
456 bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
457 if (info.f.err)
458 strncpy(l->buf, files[fileidx].name, l->size);
459 }
460}
461
462int nav_button(void)
463{
464 int x, y, nw;
465
466 if (NAV_WIDTH == 0)
467 return 1;
468
469 win_cursor_pos(&win, &x, &y);
470 nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH;
471 nw = MIN(nw, ((int)win.w + 1) / 2);
472
473 if (x < nw)
474 return 0;
475 else if (x < (int)win.w - nw)
476 return 1;
477 else
478 return 2;
479}
480
481void redraw(void)
482{
483 int t;
484
485 if (mode == MODE_IMAGE) {
486 img_render(&img);
487 if (img.ss.on) {
488 t = img.ss.delay * 100;
489 if (img.multi.cnt > 0 && img.multi.animate)
490 t = MAX(t, img.multi.length);
491 set_timeout(slideshow, t, false);
492 }
493 } else {
494 tns_render(&tns);
495 }
496 update_info();
497 if (title_dirty) {
498 size_t n;
499 char buf[512];
500
501 if ((n = get_win_title(buf, sizeof(buf))) > 0)
502 win_set_title(&win, buf, n);
503 title_dirty = false;
504 }
505 win_draw(&win);
506 reset_timeout(redraw);
507 reset_cursor();
508}
509
510void reset_cursor(void)
511{
512 int c;
513 unsigned int i;
514 cursor_t cursor = CURSOR_NONE;
515
516 if (mode == MODE_IMAGE) {
517 for (i = 0; i < ARRLEN(timeouts); i++) {
518 if (timeouts[i].handler == reset_cursor) {
519 if (timeouts[i].active) {
520 c = nav_button();
521 c = MAX(fileidx > 0 ? 0 : 1, c);
522 c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
523 cursor = imgcursor[c];
524 }
525 break;
526 }
527 }
528 } else {
529 if (tns.loadnext < tns.end || tns.initnext < filecnt)
530 cursor = CURSOR_WATCH;
531 else
532 cursor = CURSOR_ARROW;
533 }
534 win_set_cursor(&win, cursor);
535}
536
537void animate(void)
538{
539 if (img_frame_animate(&img)) {
540 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
541 redraw();
542 }
543}
544
545void slideshow(void)
546{
547 load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
548 redraw();
549}
550
551void clear_resize(void)
552{
553 resized = false;
554}
555
556static Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
557{
558 return ev->type == ButtonPress || ev->type == KeyPress;
559}
560
561void handle_key_handler(bool init)
562{
563 extprefix = init;
564 if (win.bar.h == 0)
565 return;
566 if (init) {
567 close_info();
568 snprintf(win.bar.l.buf, win.bar.l.size, "Getting key handler input "
569 "(%s to abort)...", XKeysymToString(KEYHANDLER_ABORT));
570 } else { /* abort */
571 open_info();
572 update_info();
573 }
574 win_draw(&win);
575}
576
577static bool run_key_handler(const char *key, unsigned int mask)
578{
579 FILE *pfs;
580 bool marked = mode == MODE_THUMB && markcnt > 0;
581 bool changed = false;
582 int f, i;
583 int fcnt = marked ? markcnt : 1;
584 char kstr[32];
585 struct stat *oldst, st;
586 XEvent dump;
587 char *argv[3];
588 spawn_t pfd;
589
590 if (keyhandler.f.err) {
591 if (!keyhandler.warned) {
592 error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
593 keyhandler.warned = true;
594 }
595 return false;
596 }
597 if (key == NULL)
598 return false;
599
600 close_info();
601 strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
602 win_draw(&win);
603 win_set_cursor(&win, CURSOR_WATCH);
604 setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1);
605
606 snprintf(kstr, sizeof(kstr), "%s%s%s%s",
607 mask & ControlMask ? "C-" : "",
608 mask & Mod1Mask ? "M-" : "",
609 mask & ShiftMask ? "S-" : "", key);
610 construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL);
611 pfd = spawn(keyhandler.f.cmd, argv, X_WRITE);
612 if (pfd.writefd < 0)
613 return false;
614 if ((pfs = fdopen(pfd.writefd, "w")) == NULL) {
615 error(0, errno, "open pipe");
616 close(pfd.writefd);
617 return false;
618 }
619
620 oldst = emalloc(fcnt * sizeof(*oldst));
621 for (f = i = 0; f < fcnt; i++) {
622 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
623 stat(files[i].path, &oldst[f]);
624 fprintf(pfs, "%s%c", files[i].name, options->using_null ? '\0' : '\n');
625 f++;
626 }
627 }
628 fclose(pfs);
629 while (waitpid(pfd.pid, NULL, 0) == -1 && errno == EINTR);
630
631 for (f = i = 0; f < fcnt; i++) {
632 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
633 if (stat(files[i].path, &st) != 0 ||
634 memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
635 {
636 if (tns.thumbs != NULL) {
637 tns_unload(&tns, i);
638 tns.loadnext = MIN(tns.loadnext, i);
639 }
640 changed = true;
641 }
642 f++;
643 }
644 }
645 /* drop user input events that occurred while running the key handler */
646 while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
647
648 if (mode == MODE_IMAGE && changed) {
649 img_close(&img, true);
650 load_image(fileidx);
651 } else {
652 open_info();
653 }
654 free(oldst);
655 reset_cursor();
656 return true;
657}
658
659static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button,
660 unsigned int state, unsigned int implicit_mod)
661{
662 unsigned int i;
663 bool dirty = false;
664
665 for (i = 0; i < len; i++) {
666 if (bindings[i].ksym_or_button == ksym_or_button &&
667 MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) &&
668 bindings[i].cmd.func != NULL &&
669 (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode))
670 {
671 if (bindings[i].cmd.func(bindings[i].arg))
672 dirty = true;
673 }
674 }
675 return dirty;
676}
677
678static void on_keypress(XKeyEvent *kev)
679{
680 unsigned int sh = 0;
681 KeySym ksym, shksym;
682 char dummy, key;
683 bool dirty = false;
684
685 XLookupString(kev, &key, 1, &ksym, NULL);
686
687 if (kev->state & ShiftMask) {
688 kev->state &= ~ShiftMask;
689 XLookupString(kev, &dummy, 1, &shksym, NULL);
690 kev->state |= ShiftMask;
691 if (ksym != shksym)
692 sh = ShiftMask;
693 }
694 if (IsModifierKey(ksym))
695 return;
696 if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) {
697 handle_key_handler(false);
698 } else if (extprefix) {
699 if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh)))
700 extprefix = false;
701 else
702 handle_key_handler(false);
703 } else if (key >= '0' && key <= '9') {
704 /* number prefix for commands */
705 prefix = prefix * 10 + (int) (key - '0');
706 return;
707 } else {
708 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
709 }
710 if (dirty)
711 redraw();
712 prefix = 0;
713}
714
715static void on_buttonpress(const XButtonEvent *bev)
716{
717 bool dirty = false;
718
719 if (mode == MODE_IMAGE) {
720 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
721 reset_cursor();
722 dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0);
723 } else { /* thumbnail mode */
724 dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0);
725 }
726 if (dirty)
727 redraw();
728 prefix = 0;
729}
730
731static void run(void)
732{
733 enum { FD_X, FD_INFO, FD_ARL, FD_CNT };
734 struct pollfd pfd[FD_CNT];
735 int timeout = 0;
736 const struct timespec ten_ms = {0, 10000000};
737 bool discard, init_thumb, load_thumb, to_set;
738 XEvent ev, nextev;
739
740 xbutton_ev = &ev.xbutton;
741 while (true) {
742 to_set = check_timeouts(&timeout);
743 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
744 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
745
746 if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
747 arl.fd != -1) && XPending(win.env.dpy) == 0)
748 {
749 if (load_thumb) {
750 set_timeout(redraw, TO_REDRAW_THUMBS, false);
751 if (!tns_load(&tns, tns.loadnext, false, false)) {
752 remove_file(tns.loadnext, false);
753 tns.dirty = true;
754 }
755 if (tns.loadnext >= tns.end) {
756 open_info();
757 redraw();
758 }
759 } else if (init_thumb) {
760 set_timeout(redraw, TO_REDRAW_THUMBS, false);
761 if (!tns_load(&tns, tns.initnext, false, true))
762 remove_file(tns.initnext, false);
763 } else {
764 pfd[FD_X].fd = ConnectionNumber(win.env.dpy);
765 pfd[FD_INFO].fd = info.fd;
766 pfd[FD_ARL].fd = arl.fd;
767 pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_ARL].events = POLLIN;
768
769 if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0)
770 continue;
771 if (pfd[FD_INFO].revents & POLLIN)
772 read_info();
773 if (pfd[FD_ARL].revents & POLLIN) {
774 if (arl_handle(&arl)) {
775 /* when too fast, imlib2 can't load the image */
776 nanosleep(&ten_ms, NULL);
777 img_close(&img, true);
778 load_image(fileidx);
779 redraw();
780 }
781 }
782 }
783 continue;
784 }
785
786 do {
787 XNextEvent(win.env.dpy, &ev);
788 discard = false;
789 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
790 XPeekEvent(win.env.dpy, &nextev);
791 switch (ev.type) {
792 case ConfigureNotify:
793 case MotionNotify:
794 discard = ev.type == nextev.type;
795 break;
796 case KeyPress:
797 discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
798 && ev.xkey.keycode == nextev.xkey.keycode;
799 break;
800 }
801 }
802 } while (discard);
803
804 switch (ev.type) { /* handle events */
805 case ButtonPress:
806 on_buttonpress(&ev.xbutton);
807 break;
808 case ClientMessage:
809 if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
810 cg_quit(EXIT_SUCCESS);
811 break;
812 case DestroyNotify:
813 cg_quit(EXIT_FAILURE);
814 break;
815 case ConfigureNotify:
816 if (win_configure(&win, &ev.xconfigure)) {
817 if (mode == MODE_IMAGE) {
818 img.dirty = true;
819 img.checkpan = true;
820 } else {
821 tns.dirty = true;
822 }
823 if (!resized) {
824 redraw();
825 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
826 resized = true;
827 } else {
828 set_timeout(redraw, TO_REDRAW_RESIZE, false);
829 }
830 }
831 break;
832 case KeyPress:
833 on_keypress(&ev.xkey);
834 break;
835 case MotionNotify:
836 if (mode == MODE_IMAGE) {
837 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
838 reset_cursor();
839 }
840 break;
841 }
842 }
843}
844
845static void sigchld(int sig)
846{
847 while (waitpid(-1, NULL, WNOHANG) > 0);
848}
849
850static void setup_signal(int sig, void (*handler)(int sig))
851{
852 struct sigaction sa;
853
854 sa.sa_handler = handler;
855 sigemptyset(&sa.sa_mask);
856 sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
857 if (sigaction(sig, &sa, 0) == -1)
858 error(EXIT_FAILURE, errno, "signal %d", sig);
859}
860
861int main(int argc, char *argv[])
862{
863 int i;
864 size_t n;
865 const char *homedir, *dsuffix = "";
866
867 setup_signal(SIGCHLD, sigchld);
868 setup_signal(SIGPIPE, SIG_IGN);
869
870 setlocale(LC_COLLATE, "");
871
872 parse_options(argc, argv);
873
874 if (options->clean_cache) {
875 tns_init(&tns, NULL, NULL, NULL, NULL);
876 tns_clean_cache();
877 exit(EXIT_SUCCESS);
878 }
879
880 if (options->filecnt == 0 && !options->from_stdin) {
881 print_usage();
882 exit(EXIT_FAILURE);
883 }
884
885 if (options->recursive || options->from_stdin)
886 filecnt = 1024;
887 else
888 filecnt = options->filecnt;
889
890 files = ecalloc(filecnt, sizeof(*files));
891 fileidx = 0;
892
893 if (options->from_stdin) {
894 char *filename = NULL;
895 n = 0;
896 while (xgetline(&filename, &n))
897 add_entry(filename);
898 free(filename);
899 }
900
901 for (i = 0; i < options->filecnt; i++)
902 add_entry(options->filenames[i]);
903
904 if (fileidx == 0)
905 error(EXIT_FAILURE, 0, "No valid image file given, aborting");
906
907 filecnt = fileidx;
908 fileidx = options->startnum < filecnt ? options->startnum : 0;
909
910 win_init(&win);
911 img_init(&img, &win);
912 arl_init(&arl);
913
914 if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
915 homedir = getenv("HOME");
916 dsuffix = "/.config";
917 }
918 if (homedir != NULL) {
919 extcmd_t *cmd[] = { &info.f, &info.ft, &keyhandler.f, &wintitle.f };
920 const char *name[] = { "image-info", "thumb-info", "key-handler", "win-title" };
921 const char *s = "/nsxiv/exec/";
922
923 for (i = 0; i < (int)ARRLEN(cmd); i++) {
924 n = strlen(homedir) + strlen(dsuffix) + strlen(s) + strlen(name[i]) + 1;
925 cmd[i]->cmd = emalloc(n);
926 snprintf(cmd[i]->cmd, n, "%s%s%s%s", homedir, dsuffix, s, name[i]);
927 if (access(cmd[i]->cmd, X_OK) != 0)
928 cmd[i]->err = errno;
929 }
930 } else {
931 error(0, 0, "Exec directory not found");
932 }
933 info.fd = -1;
934
935 if (options->thumb_mode) {
936 mode = MODE_THUMB;
937 tns_init(&tns, files, &filecnt, &fileidx, &win);
938 while (!tns_load(&tns, fileidx, false, false))
939 remove_file(fileidx, false);
940 } else {
941 mode = MODE_IMAGE;
942 tns.thumbs = NULL;
943 load_image(fileidx);
944 }
945 win_open(&win);
946 win_set_cursor(&win, CURSOR_WATCH);
947
948 atexit(cleanup);
949
950 set_timeout(redraw, 25, false);
951
952 run();
953
954 return 0;
955}