A Simple X Image Viewer
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

803 lines
17 KiB

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