A Simple X Image Viewer
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 13 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
pirms 14 gadiem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. /* sxiv: main.c
  2. * Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de>
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation; either version 2
  7. * of the License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301, USA.
  17. */
  18. #include <stdlib.h>
  19. #include <stdio.h>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #include <sys/stat.h>
  23. #include <sys/time.h>
  24. #include <X11/Xutil.h>
  25. #include <X11/keysym.h>
  26. #include "commands.h"
  27. #include "image.h"
  28. #include "options.h"
  29. #include "thumbs.h"
  30. #include "types.h"
  31. #include "util.h"
  32. #include "window.h"
  33. #define _MAPPINGS_CONFIG
  34. #include "config.h"
  35. enum {
  36. TITLE_LEN = 256,
  37. FNAME_CNT = 1024
  38. };
  39. typedef struct {
  40. struct timeval when;
  41. Bool active;
  42. timeout_f handler;
  43. } timeout_t;
  44. /* timeout handler functions: */
  45. void redraw();
  46. void hide_cursor();
  47. void animate();
  48. appmode_t mode;
  49. img_t img;
  50. tns_t tns;
  51. win_t win;
  52. fileinfo_t *files;
  53. int filecnt, fileidx;
  54. size_t filesize;
  55. char win_title[TITLE_LEN];
  56. timeout_t timeouts[] = {
  57. { { 0, 0 }, False, redraw },
  58. { { 0, 0 }, False, hide_cursor },
  59. { { 0, 0 }, False, animate }
  60. };
  61. void cleanup() {
  62. static int in = 0;
  63. if (!in++) {
  64. img_close(&img, 0);
  65. tns_free(&tns);
  66. win_close(&win);
  67. }
  68. }
  69. void check_add_file(char *filename) {
  70. if (!filename || !*filename)
  71. return;
  72. if (access(filename, R_OK)) {
  73. warn("could not open file: %s", filename);
  74. return;
  75. }
  76. if (fileidx == filecnt) {
  77. filecnt *= 2;
  78. files = (fileinfo_t*) s_realloc(files, filecnt * sizeof(fileinfo_t));
  79. }
  80. if (*filename != '/') {
  81. files[fileidx].path = absolute_path(filename);
  82. if (!files[fileidx].path) {
  83. warn("could not get absolute path of file: %s\n", filename);
  84. return;
  85. }
  86. }
  87. files[fileidx].name = s_strdup(filename);
  88. if (*filename == '/')
  89. files[fileidx].path = files[fileidx].name;
  90. fileidx++;
  91. }
  92. void remove_file(int n, unsigned char silent) {
  93. if (n < 0 || n >= filecnt)
  94. return;
  95. if (filecnt == 1) {
  96. if (!silent)
  97. fprintf(stderr, "sxiv: no more files to display, aborting\n");
  98. cleanup();
  99. exit(!silent);
  100. }
  101. if (n + 1 < filecnt) {
  102. if (files[n].path != files[n].name)
  103. free((void*) files[n].path);
  104. free((void*) files[n].name);
  105. memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(fileinfo_t));
  106. }
  107. if (n + 1 < tns.cnt) {
  108. memmove(tns.thumbs + n, tns.thumbs + n + 1, (tns.cnt - n - 1) *
  109. sizeof(thumb_t));
  110. memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t));
  111. }
  112. filecnt--;
  113. if (n < tns.cnt)
  114. tns.cnt--;
  115. }
  116. void set_timeout(timeout_f handler, int time, int overwrite) {
  117. int i;
  118. for (i = 0; i < LEN(timeouts); i++) {
  119. if (timeouts[i].handler == handler) {
  120. if (!timeouts[i].active || overwrite) {
  121. gettimeofday(&timeouts[i].when, 0);
  122. MSEC_ADD_TO_TIMEVAL(time, &timeouts[i].when);
  123. timeouts[i].active = True;
  124. }
  125. return;
  126. }
  127. }
  128. }
  129. void reset_timeout(timeout_f handler) {
  130. int i;
  131. for (i = 0; i < LEN(timeouts); i++) {
  132. if (timeouts[i].handler == handler) {
  133. timeouts[i].active = False;
  134. return;
  135. }
  136. }
  137. }
  138. int check_timeouts(struct timeval *t) {
  139. int i, tdiff, tmin = -1;
  140. struct timeval now;
  141. gettimeofday(&now, 0);
  142. for (i = 0; i < LEN(timeouts); i++) {
  143. if (timeouts[i].active) {
  144. tdiff = TIMEDIFF(&timeouts[i].when, &now);
  145. if (tdiff <= 0) {
  146. timeouts[i].active = False;
  147. if (timeouts[i].handler)
  148. timeouts[i].handler();
  149. } else if (tmin < 0 || tdiff < tmin) {
  150. tmin = tdiff;
  151. }
  152. }
  153. }
  154. if (tmin > 0 && t)
  155. MSEC_TO_TIMEVAL(tmin, t);
  156. return tmin > 0;
  157. }
  158. void load_image(int new) {
  159. struct stat fstats;
  160. if (new < 0 || new >= filecnt)
  161. return;
  162. /* cursor gets reset in redraw() */
  163. win_set_cursor(&win, CURSOR_WATCH);
  164. img_close(&img, 0);
  165. while (!img_load(&img, &files[new])) {
  166. remove_file(new, 0);
  167. if (new >= filecnt)
  168. new = filecnt - 1;
  169. }
  170. fileidx = new;
  171. if (!stat(files[new].path, &fstats))
  172. filesize = fstats.st_size;
  173. else
  174. filesize = 0;
  175. if (img.multi.cnt) {
  176. if (img.multi.animate)
  177. set_timeout(animate, img.multi.frames[img.multi.sel].delay, 1);
  178. else
  179. reset_timeout(animate);
  180. }
  181. }
  182. void update_title() {
  183. int n;
  184. float size;
  185. const char *unit;
  186. if (mode == MODE_THUMB) {
  187. n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] %s",
  188. tns.cnt ? tns.sel + 1 : 0, tns.cnt,
  189. tns.cnt ? files[tns.sel].name : "");
  190. } else {
  191. size = filesize;
  192. size_readable(&size, &unit);
  193. if (img.multi.cnt)
  194. n = snprintf(win_title, TITLE_LEN,
  195. "sxiv: [%d/%d] <%d%%> <%dx%d> (%.2f%s) {%d/%d} %s",
  196. fileidx + 1, filecnt, (int) (img.zoom * 100.0), img.w,
  197. img.h, size, unit, img.multi.sel + 1, img.multi.cnt,
  198. files[fileidx].name);
  199. else
  200. n = snprintf(win_title, TITLE_LEN,
  201. "sxiv: [%d/%d] <%d%%> <%dx%d> (%.2f%s) %s",
  202. fileidx + 1, filecnt, (int) (img.zoom * 100.0), img.w,
  203. img.h, size, unit, files[fileidx].name);
  204. }
  205. if (n >= TITLE_LEN) {
  206. for (n = 0; n < 3; n++)
  207. win_title[TITLE_LEN - n - 2] = '.';
  208. }
  209. win_set_title(&win, win_title);
  210. }
  211. void redraw() {
  212. if (mode == MODE_IMAGE) {
  213. img_render(&img, &win);
  214. if (img.multi.animate) {
  215. win_set_cursor(&win, CURSOR_NONE);
  216. } else {
  217. win_set_cursor(&win, CURSOR_ARROW);
  218. set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
  219. }
  220. } else {
  221. tns_render(&tns, &win);
  222. }
  223. update_title();
  224. reset_timeout(redraw);
  225. }
  226. void hide_cursor() {
  227. win_set_cursor(&win, CURSOR_NONE);
  228. }
  229. void animate() {
  230. int delay;
  231. delay = img_frame_animate(&img, 0);
  232. if (delay) {
  233. set_timeout(animate, delay, 1);
  234. redraw();
  235. }
  236. }
  237. Bool keymask(const keymap_t *k, unsigned int state) {
  238. return (k->ctrl ? ControlMask : 0) == (state & ControlMask);
  239. }
  240. Bool buttonmask(const button_t *b, unsigned int state) {
  241. return ((b->ctrl ? ControlMask : 0) | (b->shift ? ShiftMask : 0)) ==
  242. (state & (ControlMask | ShiftMask));
  243. }
  244. void on_keypress(XKeyEvent *kev) {
  245. int i;
  246. KeySym ksym;
  247. char key;
  248. if (!kev)
  249. return;
  250. XLookupString(kev, &key, 1, &ksym, NULL);
  251. for (i = 0; i < LEN(keys); i++) {
  252. if (keys[i].ksym == ksym && keymask(&keys[i], kev->state)) {
  253. if (keys[i].cmd && keys[i].cmd(keys[i].arg))
  254. redraw();
  255. return;
  256. }
  257. }
  258. }
  259. void on_buttonpress(XButtonEvent *bev) {
  260. int i, sel;
  261. if (!bev)
  262. return;
  263. if (mode == MODE_IMAGE) {
  264. win_set_cursor(&win, CURSOR_ARROW);
  265. set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
  266. for (i = 0; i < LEN(buttons); i++) {
  267. if (buttons[i].button == bev->button &&
  268. buttonmask(&buttons[i], bev->state))
  269. {
  270. if (buttons[i].cmd && buttons[i].cmd(buttons[i].arg))
  271. redraw();
  272. return;
  273. }
  274. }
  275. } else {
  276. /* thumbnail mode (hard-coded) */
  277. switch (bev->button) {
  278. case Button1:
  279. if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
  280. if (sel == tns.sel) {
  281. load_image(tns.sel);
  282. mode = MODE_IMAGE;
  283. set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
  284. } else {
  285. tns_highlight(&tns, &win, tns.sel, False);
  286. tns_highlight(&tns, &win, sel, True);
  287. tns.sel = sel;
  288. }
  289. redraw();
  290. break;
  291. }
  292. break;
  293. case Button4:
  294. case Button5:
  295. if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN))
  296. redraw();
  297. break;
  298. }
  299. }
  300. }
  301. void run() {
  302. int xfd;
  303. fd_set fds;
  304. struct timeval timeout;
  305. XEvent ev;
  306. redraw();
  307. while (1) {
  308. while (mode == MODE_THUMB && tns.cnt < filecnt &&
  309. !XPending(win.env.dpy))
  310. {
  311. /* load thumbnails */
  312. win_set_cursor(&win, CURSOR_WATCH);
  313. set_timeout(redraw, TO_REDRAW_THUMBS, 0);
  314. if (tns_load(&tns, tns.cnt, &files[tns.cnt], False, False))
  315. tns.cnt++;
  316. else
  317. remove_file(tns.cnt, 0);
  318. if (tns.cnt == filecnt) {
  319. redraw();
  320. win_set_cursor(&win, CURSOR_ARROW);
  321. } else {
  322. check_timeouts(NULL);
  323. }
  324. }
  325. if (!XPending(win.env.dpy) && check_timeouts(&timeout)) {
  326. /* handle timeouts */
  327. xfd = ConnectionNumber(win.env.dpy);
  328. FD_ZERO(&fds);
  329. FD_SET(xfd, &fds);
  330. if (!select(xfd + 1, &fds, 0, 0, &timeout))
  331. check_timeouts(NULL);
  332. }
  333. if (!XNextEvent(win.env.dpy, &ev)) {
  334. /* handle events */
  335. switch (ev.type) {
  336. case ButtonPress:
  337. on_buttonpress(&ev.xbutton);
  338. break;
  339. case ClientMessage:
  340. if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
  341. return;
  342. break;
  343. case ConfigureNotify:
  344. if (win_configure(&win, &ev.xconfigure)) {
  345. set_timeout(redraw, TO_REDRAW_RESIZE, 0);
  346. if (mode == MODE_IMAGE)
  347. img.checkpan = 1;
  348. else
  349. tns.dirty = 1;
  350. }
  351. break;
  352. case KeyPress:
  353. on_keypress(&ev.xkey);
  354. break;
  355. case MotionNotify:
  356. if (mode == MODE_IMAGE) {
  357. win_set_cursor(&win, CURSOR_ARROW);
  358. set_timeout(hide_cursor, TO_CURSOR_HIDE, 1);
  359. }
  360. break;
  361. }
  362. }
  363. }
  364. }
  365. int fncmp(const void *a, const void *b) {
  366. return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
  367. }
  368. int main(int argc, char **argv) {
  369. int i, len, start;
  370. size_t n;
  371. char *filename;
  372. struct stat fstats;
  373. r_dir_t dir;
  374. parse_options(argc, argv);
  375. if (options->clean_cache) {
  376. tns_init(&tns, 0);
  377. tns_clean_cache(&tns);
  378. exit(0);
  379. }
  380. if (!options->filecnt) {
  381. print_usage();
  382. exit(1);
  383. }
  384. if (options->recursive || options->from_stdin)
  385. filecnt = FNAME_CNT;
  386. else
  387. filecnt = options->filecnt;
  388. files = (fileinfo_t*) s_malloc(filecnt * sizeof(fileinfo_t));
  389. fileidx = 0;
  390. /* build file list: */
  391. if (options->from_stdin) {
  392. filename = NULL;
  393. while ((len = getline(&filename, &n, stdin)) > 0) {
  394. if (filename[len-1] == '\n')
  395. filename[len-1] = '\0';
  396. check_add_file(filename);
  397. }
  398. } else {
  399. for (i = 0; i < options->filecnt; i++) {
  400. filename = options->filenames[i];
  401. if (stat(filename, &fstats) || !S_ISDIR(fstats.st_mode)) {
  402. check_add_file(filename);
  403. } else {
  404. if (!options->recursive) {
  405. warn("ignoring directory: %s", filename);
  406. continue;
  407. }
  408. if (r_opendir(&dir, filename)) {
  409. warn("could not open directory: %s", filename);
  410. continue;
  411. }
  412. start = fileidx;
  413. printf("reading dir: %s\n", filename);
  414. while ((filename = r_readdir(&dir))) {
  415. check_add_file(filename);
  416. free((void*) filename);
  417. }
  418. r_closedir(&dir);
  419. if (fileidx - start > 1)
  420. qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
  421. }
  422. }
  423. }
  424. if (!fileidx) {
  425. fprintf(stderr, "sxiv: no valid image file given, aborting\n");
  426. exit(1);
  427. }
  428. filecnt = fileidx;
  429. fileidx = options->startnum < filecnt ? options->startnum : 0;
  430. win_init(&win);
  431. img_init(&img, &win);
  432. if (options->thumbnails) {
  433. mode = MODE_THUMB;
  434. tns_init(&tns, filecnt);
  435. while (!tns_load(&tns, 0, &files[0], False, False))
  436. remove_file(0, 0);
  437. tns.cnt = 1;
  438. } else {
  439. mode = MODE_IMAGE;
  440. tns.thumbs = NULL;
  441. load_image(fileidx);
  442. }
  443. win_open(&win);
  444. run();
  445. cleanup();
  446. return 0;
  447. }