Krolyxon
Commit 51854c6

Branches

Refs →

nsxivmain.c

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