A Simple X Image Viewer
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

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