Krolyxon
Commit 5b5dc74

Branches

Refs →

nsxivmain.c

918 lines · 20.2 KB · C

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