Krolyxon
Commit 0e1bc3c

Branches

Refs →

nsxivmain.c

955 lines · 21.5 KB · C

1/* Copyright 2011-2020 Bert Muennich
2 * Copyright 2021-2023 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) (USED_MODMASK & (mask))
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 do { \
50 (tv)->tv_sec += (t) / 1000; \
51 (tv)->tv_usec += (t) % 1000 * 1000; \
52 } while (0)
53
54typedef struct {
55 int err;
56 char *cmd;
57} extcmd_t;
58
59/* these are not declared in nsxiv.h, as it causes too many -Wshadow warnings */
60arl_t arl;
61img_t img;
62tns_t tns;
63win_t win;
64
65appmode_t mode;
66fileinfo_t *files;
67int filecnt, fileidx;
68int alternate;
69int markcnt;
70int markidx;
71int prefix;
72bool title_dirty;
73const XButtonEvent *xbutton_ev;
74
75static bool extprefix;
76static bool resized = false;
77
78static struct {
79 extcmd_t f, ft;
80 int fd;
81 pid_t pid;
82} info, wintitle;
83
84static struct {
85 extcmd_t f;
86 bool warned;
87} keyhandler;
88
89static struct {
90 timeout_f handler;
91 struct timeval when;
92 bool active;
93} timeouts[] = {
94 { redraw },
95 { reset_cursor },
96 { slideshow },
97 { animate },
98 { clear_resize },
99};
100
101/*
102 * function implementations
103 */
104
105static void cleanup(void)
106{
107 img_close(&img, false);
108 arl_cleanup(&arl);
109 tns_free(&tns);
110 win_close(&win);
111}
112
113static bool xgetline(char **lineptr, size_t *n)
114{
115 ssize_t len = getdelim(lineptr, n, options->using_null ? '\0' : '\n', stdin);
116 if (!options->using_null && len > 0 && (*lineptr)[len - 1] == '\n')
117 (*lineptr)[len - 1] = '\0';
118 return len > 0;
119}
120
121static int fncmp(const void *a, const void *b)
122{
123 return strcoll(((fileinfo_t *)a)->name, ((fileinfo_t *)b)->name);
124}
125
126static void check_add_file(const 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
154static void add_entry(const char *entry_name)
155{
156 int start;
157 char *filename;
158 struct stat fstats;
159 r_dir_t dir;
160
161 if (stat(entry_name, &fstats) < 0) {
162 error(0, errno, "%s", entry_name);
163 return;
164 }
165 if (!S_ISDIR(fstats.st_mode)) {
166 check_add_file(entry_name, true);
167 } else {
168 if (r_opendir(&dir, entry_name, options->recursive) < 0) {
169 error(0, errno, "%s", entry_name);
170 return;
171 }
172 start = fileidx;
173 while ((filename = r_readdir(&dir, true)) != NULL) {
174 check_add_file(filename, false);
175 free(filename);
176 }
177 r_closedir(&dir);
178 if (fileidx - start > 1)
179 qsort(files + start, fileidx - start, sizeof(*files), fncmp);
180 }
181}
182
183void remove_file(int n, bool manual)
184{
185 if (n < 0 || n >= filecnt)
186 return;
187
188 if (filecnt == 1) {
189 if (!manual)
190 fprintf(stderr, "%s: no more files to display, aborting\n", progname);
191 exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
192 }
193 if (files[n].flags & FF_MARK)
194 markcnt--;
195
196 if (files[n].path != files[n].name)
197 free((void *)files[n].path);
198 free((void *)files[n].name);
199 if (tns.thumbs != NULL)
200 tns_unload(&tns, n);
201
202 if (n + 1 < filecnt) {
203 if (tns.thumbs != NULL) {
204 memmove(tns.thumbs + n, tns.thumbs + n + 1,
205 (filecnt - n - 1) * sizeof(*tns.thumbs));
206 memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs));
207 }
208 memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files));
209 }
210 filecnt--;
211 if (fileidx > n || fileidx == filecnt)
212 fileidx--;
213 if (alternate > n || alternate == filecnt)
214 alternate--;
215 if (markidx > n || markidx == filecnt)
216 markidx--;
217}
218
219void set_timeout(timeout_f handler, int time, bool overwrite)
220{
221 unsigned int i;
222
223 for (i = 0; i < ARRLEN(timeouts); i++) {
224 if (timeouts[i].handler == handler) {
225 if (!timeouts[i].active || overwrite) {
226 gettimeofday(&timeouts[i].when, 0);
227 TV_ADD_MSEC(&timeouts[i].when, time);
228 timeouts[i].active = true;
229 }
230 return;
231 }
232 }
233}
234
235void reset_timeout(timeout_f handler)
236{
237 unsigned int i;
238
239 for (i = 0; i < ARRLEN(timeouts); i++) {
240 if (timeouts[i].handler == handler) {
241 timeouts[i].active = false;
242 return;
243 }
244 }
245}
246
247static bool check_timeouts(int *t)
248{
249 int i = 0, tdiff, tmin = -1;
250 struct timeval now;
251
252 while (i < (int)ARRLEN(timeouts)) {
253 if (timeouts[i].active) {
254 gettimeofday(&now, 0);
255 tdiff = TV_DIFF(&timeouts[i].when, &now);
256 if (tdiff <= 0) {
257 timeouts[i].active = false;
258 if (timeouts[i].handler != NULL)
259 timeouts[i].handler();
260 i = tmin = -1;
261 } else if (tmin < 0 || tdiff < tmin) {
262 tmin = tdiff;
263 }
264 }
265 i++;
266 }
267 if (tmin > 0 && t != NULL)
268 *t = tmin;
269 return tmin > 0;
270}
271
272static void kill_close(pid_t pid, int *fd)
273{
274 if (fd != NULL && *fd != -1) {
275 kill(pid, SIGTERM);
276 close(*fd);
277 *fd = -1;
278 }
279}
280
281static void close_title(void)
282{
283 kill_close(wintitle.pid, &wintitle.fd);
284}
285
286static void read_title(void)
287{
288 ssize_t n;
289 char buf[512];
290
291 if ((n = read(wintitle.fd, buf, sizeof(buf) - 1)) > 0) {
292 buf[n] = '\0';
293 win_set_title(&win, buf, n);
294 }
295 close_title();
296}
297
298static void open_title(void)
299{
300 char *argv[8];
301 char w[12] = "", h[12] = "", z[12] = "", fidx[12], fcnt[12];
302
303 if (wintitle.f.err || !title_dirty)
304 return;
305
306 close_title();
307 if (mode == MODE_IMAGE) {
308 snprintf(w, ARRLEN(w), "%d", img.w);
309 snprintf(h, ARRLEN(h), "%d", img.h);
310 snprintf(z, ARRLEN(z), "%d", (int)(img.zoom * 100));
311 }
312 snprintf(fidx, ARRLEN(fidx), "%d", fileidx + 1);
313 snprintf(fcnt, ARRLEN(fcnt), "%d", filecnt);
314 construct_argv(argv, ARRLEN(argv), wintitle.f.cmd, files[fileidx].path,
315 fidx, fcnt, w, h, z, NULL);
316 if ((wintitle.pid = spawn(&wintitle.fd, NULL, argv)) > 0)
317 fcntl(wintitle.fd, F_SETFL, O_NONBLOCK);
318 title_dirty = false;
319}
320
321void close_info(void)
322{
323 kill_close(info.pid, &info.fd);
324}
325
326void open_info(void)
327{
328 char *argv[6], w[12] = "", h[12] = "";
329 char *cmd = mode == MODE_IMAGE ? info.f.cmd : info.ft.cmd;
330 bool ferr = mode == MODE_IMAGE ? info.f.err : info.ft.err;
331
332 if (ferr || info.fd >= 0 || win.bar.h == 0)
333 return;
334 win.bar.l.buf[0] = '\0';
335 if (mode == MODE_IMAGE) {
336 snprintf(w, sizeof(w), "%d", img.w);
337 snprintf(h, sizeof(h), "%d", img.h);
338 }
339 construct_argv(argv, ARRLEN(argv), cmd, files[fileidx].name, w, h,
340 files[fileidx].path, NULL);
341 if ((info.pid = spawn(&info.fd, NULL, argv)) > 0)
342 fcntl(info.fd, F_SETFL, O_NONBLOCK);
343}
344
345static void read_info(void)
346{
347 ssize_t i, n;
348
349 if ((n = read(info.fd, win.bar.l.buf, win.bar.l.size - 1)) > 0) {
350 win.bar.l.buf[n] = '\0';
351 for (i = 0; i < n; ++i) {
352 if (win.bar.l.buf[i] == '\n')
353 win.bar.l.buf[i] = ' ';
354 }
355 win_draw(&win);
356 }
357 close_info();
358}
359
360void load_image(int new)
361{
362 bool prev = new < fileidx;
363 static int current;
364
365 if (new < 0 || new >= filecnt)
366 return;
367
368 if (win.xwin != None)
369 win_set_cursor(&win, CURSOR_WATCH);
370 reset_timeout(slideshow);
371
372 if (new != current)
373 alternate = current;
374
375 img_close(&img, false);
376 while (!img_load(&img, &files[new])) {
377 remove_file(new, false);
378 if (new >= filecnt)
379 new = filecnt - 1;
380 else if (new > 0 && prev)
381 new -= 1;
382 }
383 files[new].flags &= ~FF_WARN;
384 fileidx = current = new;
385
386 close_info();
387 open_info();
388 arl_add(&arl, files[fileidx].path);
389 title_dirty = true;
390
391 if (img.multi.cnt > 0 && img.multi.animate)
392 set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
393 else
394 reset_timeout(animate);
395}
396
397bool mark_image(int n, bool on)
398{
399 markidx = n;
400 if (!!(files[n].flags & FF_MARK) != on) {
401 files[n].flags ^= FF_MARK;
402 markcnt += on ? 1 : -1;
403 if (mode == MODE_THUMB)
404 tns_mark(&tns, n, on);
405 return true;
406 }
407 return false;
408}
409
410static void bar_put(win_bar_t *bar, const char *fmt, ...)
411{
412 size_t len = bar->size - (bar->p - bar->buf), n;
413 va_list ap;
414
415 va_start(ap, fmt);
416 n = vsnprintf(bar->p, len, fmt, ap);
417 bar->p += MIN(len, n);
418 va_end(ap);
419}
420
421static void update_info(void)
422{
423 unsigned int i, fn, fw;
424 const char *mark;
425 win_bar_t *l = &win.bar.l, *r = &win.bar.r;
426
427 /* update bar contents */
428 if (win.bar.h == 0 || extprefix)
429 return;
430 for (fw = 0, i = filecnt; i > 0; fw++, i /= 10)
431 ;
432 mark = files[fileidx].flags & FF_MARK ? "* " : "";
433 l->p = l->buf;
434 r->p = r->buf;
435 if (mode == MODE_THUMB) {
436 if (tns.loadnext < tns.end)
437 bar_put(r, "Loading... %0*d | ", fw, tns.loadnext + 1);
438 else if (tns.initnext < filecnt)
439 bar_put(r, "Caching... %0*d | ", fw, tns.initnext + 1);
440 bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
441 if (info.ft.err)
442 strncpy(l->buf, files[fileidx].name, l->size);
443 } else {
444 bar_put(r, "%s", mark);
445 if (img.ss.on) {
446 if (img.ss.delay % 10 != 0)
447 bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
448 else
449 bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
450 }
451 if (img.gamma)
452 bar_put(r, "G%+d" BAR_SEP, img.gamma);
453 if (img.brightness)
454 bar_put(r, "B%+d" BAR_SEP, img.brightness);
455 if (img.contrast)
456 bar_put(r, "C%+d" BAR_SEP, img.contrast);
457 bar_put(r, "%3d%%" BAR_SEP, (int)(img.zoom * 100.0));
458 if (img.multi.cnt > 0) {
459 for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10)
460 ;
461 bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
462 }
463 bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
464 if (info.f.err)
465 strncpy(l->buf, files[fileidx].name, l->size);
466 }
467}
468
469int nav_button(void)
470{
471 int x, y, nw;
472
473 if (NAV_WIDTH == 0)
474 return 1;
475
476 win_cursor_pos(&win, &x, &y);
477 nw = NAV_IS_REL ? win.w * NAV_WIDTH / 100 : NAV_WIDTH;
478 nw = MIN(nw, ((int)win.w + 1) / 2);
479
480 if (x < nw)
481 return 0;
482 else if (x < (int)win.w - nw)
483 return 1;
484 else
485 return 2;
486}
487
488void redraw(void)
489{
490 int t;
491
492 if (mode == MODE_IMAGE) {
493 img_render(&img);
494 if (img.ss.on) {
495 t = img.ss.delay * 100;
496 if (img.multi.cnt > 0 && img.multi.animate)
497 t = MAX(t, img.multi.length);
498 set_timeout(slideshow, t, false);
499 }
500 } else {
501 tns_render(&tns);
502 }
503 update_info();
504 open_title();
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,
569 "Getting key handler input (%s to abort)...",
570 XKeysymToString(KEYHANDLER_ABORT));
571 } else { /* abort */
572 open_info();
573 update_info();
574 }
575 win_draw(&win);
576}
577
578static bool run_key_handler(const char *key, unsigned int mask)
579{
580 FILE *pfs;
581 bool marked = mode == MODE_THUMB && markcnt > 0;
582 bool changed = false;
583 pid_t pid;
584 int writefd, f, i;
585 int fcnt = marked ? markcnt : 1;
586 char kstr[32];
587 struct stat *oldst, st;
588 XEvent dump;
589 char *argv[3];
590
591 if (keyhandler.f.err) {
592 if (!keyhandler.warned) {
593 error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
594 keyhandler.warned = true;
595 }
596 return false;
597 }
598 if (key == NULL)
599 return false;
600
601 close_info();
602 strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
603 win_draw(&win);
604 win_set_cursor(&win, CURSOR_WATCH);
605 setenv("NSXIV_USING_NULL", options->using_null ? "1" : "0", 1);
606
607 snprintf(kstr, sizeof(kstr), "%s%s%s%s",
608 mask & ControlMask ? "C-" : "",
609 mask & Mod1Mask ? "M-" : "",
610 mask & ShiftMask ? "S-" : "", key);
611 construct_argv(argv, ARRLEN(argv), keyhandler.f.cmd, kstr, NULL);
612 if ((pid = spawn(NULL, &writefd, argv)) < 0)
613 return false;
614 if ((pfs = fdopen(writefd, "w")) == NULL) {
615 error(0, errno, "open pipe");
616 close(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(pid, NULL, 0) == -1 && errno == EINTR)
630 ;
631
632 for (f = i = 0; f < fcnt; i++) {
633 if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
634 if (stat(files[i].path, &st) != 0 ||
635 memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
636 {
637 if (tns.thumbs != NULL) {
638 tns_unload(&tns, i);
639 tns.loadnext = MIN(tns.loadnext, i);
640 }
641 changed = true;
642 }
643 f++;
644 }
645 }
646 /* drop user input events that occurred while running the key handler */
647 while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL))
648 ;
649
650 if (mode == MODE_IMAGE && changed) {
651 img_close(&img, true);
652 load_image(fileidx);
653 } else {
654 open_info();
655 }
656 free(oldst);
657 reset_cursor();
658 return true;
659}
660
661static bool process_bindings(const keymap_t *bindings, unsigned int len, KeySym ksym_or_button,
662 unsigned int state, unsigned int implicit_mod)
663{
664 unsigned int i;
665 bool dirty = false;
666
667 for (i = 0; i < len; i++) {
668 if (bindings[i].ksym_or_button == ksym_or_button &&
669 MODMASK(bindings[i].mask | implicit_mod) == MODMASK(state) &&
670 bindings[i].cmd.func != NULL &&
671 (bindings[i].cmd.mode == MODE_ALL || bindings[i].cmd.mode == mode))
672 {
673 if (bindings[i].cmd.func(bindings[i].arg))
674 dirty = true;
675 }
676 }
677 return dirty;
678}
679
680static void on_keypress(XKeyEvent *kev)
681{
682 unsigned int sh = 0;
683 KeySym ksym, shksym;
684 char dummy, key;
685 bool dirty = false;
686
687 XLookupString(kev, &key, 1, &ksym, NULL);
688
689 if (kev->state & ShiftMask) {
690 kev->state &= ~ShiftMask;
691 XLookupString(kev, &dummy, 1, &shksym, NULL);
692 kev->state |= ShiftMask;
693 if (ksym != shksym)
694 sh = ShiftMask;
695 }
696 if (IsModifierKey(ksym))
697 return;
698 if (extprefix && ksym == KEYHANDLER_ABORT && MODMASK(kev->state) == 0) {
699 handle_key_handler(false);
700 } else if (extprefix) {
701 if ((dirty = run_key_handler(XKeysymToString(ksym), kev->state & ~sh)))
702 extprefix = false;
703 else
704 handle_key_handler(false);
705 } else if (key >= '0' && key <= '9') {
706 /* number prefix for commands */
707 prefix = prefix * 10 + (int)(key - '0');
708 return;
709 } else {
710 dirty = process_bindings(keys, ARRLEN(keys), ksym, kev->state, sh);
711 }
712 if (dirty)
713 redraw();
714 prefix = 0;
715}
716
717static void on_buttonpress(const XButtonEvent *bev)
718{
719 bool dirty = false;
720
721 if (mode == MODE_IMAGE) {
722 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
723 reset_cursor();
724 dirty = process_bindings(buttons_img, ARRLEN(buttons_img), bev->button, bev->state, 0);
725 } else { /* thumbnail mode */
726 dirty = process_bindings(buttons_tns, ARRLEN(buttons_tns), bev->button, bev->state, 0);
727 }
728 if (dirty)
729 redraw();
730 prefix = 0;
731}
732
733static void run(void)
734{
735 enum { FD_X, FD_INFO, FD_TITLE, FD_ARL, FD_CNT };
736 struct pollfd pfd[FD_CNT];
737 int timeout = 0;
738 const struct timespec ten_ms = { 0, 10000000 };
739 bool discard, init_thumb, load_thumb, to_set;
740 XEvent ev, nextev;
741
742 xbutton_ev = &ev.xbutton;
743 while (true) {
744 to_set = check_timeouts(&timeout);
745 init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
746 load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
747
748 if ((init_thumb || load_thumb || to_set || info.fd != -1 || arl.fd != -1) &&
749 XPending(win.env.dpy) == 0)
750 {
751 if (load_thumb) {
752 set_timeout(redraw, TO_REDRAW_THUMBS, false);
753 if (!tns_load(&tns, tns.loadnext, false, false)) {
754 remove_file(tns.loadnext, false);
755 tns.dirty = true;
756 }
757 if (tns.loadnext >= tns.end) {
758 open_info();
759 redraw();
760 }
761 } else if (init_thumb) {
762 set_timeout(redraw, TO_REDRAW_THUMBS, false);
763 if (!tns_load(&tns, tns.initnext, false, true))
764 remove_file(tns.initnext, false);
765 } else {
766 pfd[FD_X].fd = ConnectionNumber(win.env.dpy);
767 pfd[FD_INFO].fd = info.fd;
768 pfd[FD_TITLE].fd = wintitle.fd;
769 pfd[FD_ARL].fd = arl.fd;
770 pfd[FD_X].events = pfd[FD_INFO].events = pfd[FD_TITLE].events = pfd[FD_ARL].events = POLLIN;
771
772 if (poll(pfd, ARRLEN(pfd), to_set ? timeout : -1) < 0)
773 continue;
774 if (pfd[FD_INFO].revents & POLLIN)
775 read_info();
776 if (pfd[FD_TITLE].revents & POLLIN)
777 read_title();
778 if (pfd[FD_ARL].revents & POLLIN) {
779 if (arl_handle(&arl)) {
780 /* when too fast, imlib2 can't load the image */
781 nanosleep(&ten_ms, NULL);
782 img_close(&img, true);
783 load_image(fileidx);
784 redraw();
785 }
786 }
787 }
788 continue;
789 }
790
791 do {
792 XNextEvent(win.env.dpy, &ev);
793 discard = false;
794 if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
795 XPeekEvent(win.env.dpy, &nextev);
796 switch (ev.type) {
797 case ConfigureNotify:
798 case MotionNotify:
799 discard = ev.type == nextev.type;
800 break;
801 case KeyPress:
802 discard = (nextev.type == KeyPress || nextev.type == KeyRelease) &&
803 ev.xkey.keycode == nextev.xkey.keycode;
804 break;
805 }
806 }
807 } while (discard);
808
809 switch (ev.type) { /* handle events */
810 case ButtonPress:
811 on_buttonpress(&ev.xbutton);
812 break;
813 case ClientMessage:
814 if ((Atom)ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
815 cg_quit(EXIT_SUCCESS);
816 break;
817 case DestroyNotify:
818 cg_quit(EXIT_FAILURE);
819 break;
820 case ConfigureNotify:
821 if (win_configure(&win, &ev.xconfigure)) {
822 if (mode == MODE_IMAGE) {
823 img.dirty = true;
824 img.checkpan = true;
825 } else {
826 tns.dirty = true;
827 }
828 if (!resized) {
829 redraw();
830 set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
831 resized = true;
832 } else {
833 set_timeout(redraw, TO_REDRAW_RESIZE, false);
834 }
835 }
836 break;
837 case KeyPress:
838 on_keypress(&ev.xkey);
839 break;
840 case MotionNotify:
841 if (mode == MODE_IMAGE) {
842 set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
843 reset_cursor();
844 }
845 break;
846 }
847 }
848}
849
850static void setup_signal(int sig, void (*handler)(int sig), int flags)
851{
852 struct sigaction sa;
853
854 sa.sa_handler = handler;
855 sigemptyset(&sa.sa_mask);
856 sa.sa_flags = flags;
857 if (sigaction(sig, &sa, NULL) < 0)
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, SIG_DFL, SA_RESTART | SA_NOCLDSTOP | SA_NOCLDWAIT);
868 setup_signal(SIGPIPE, SIG_IGN, 0);
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 wintitle.fd = 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}