Krolyxon
Commit 48954a1

Branches

Refs →

nsxivmain.c

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