A Simple X Image Viewer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

563 lines
12 KiB

  1. /* sxiv: events.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 modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  17. */
  18. #define _GENERAL_CONFIG
  19. #define _MAPPINGS_CONFIG
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <unistd.h>
  23. #include <sys/time.h>
  24. #include <sys/wait.h>
  25. #include <X11/keysym.h>
  26. #include <X11/Xutil.h>
  27. #include "events.h"
  28. #include "image.h"
  29. #include "thumbs.h"
  30. #include "types.h"
  31. #include "util.h"
  32. #include "window.h"
  33. #include "config.h"
  34. /* timeouts in milliseconds: */
  35. enum {
  36. TO_WIN_RESIZE = 75,
  37. TO_IMAGE_DRAG = 1,
  38. TO_CURSOR_HIDE = 1500,
  39. TO_THUMBS_LOAD = 200
  40. };
  41. void cleanup();
  42. void remove_file(int, unsigned char);
  43. void load_image(int);
  44. void update_title();
  45. extern appmode_t mode;
  46. extern img_t img;
  47. extern tns_t tns;
  48. extern win_t win;
  49. extern char **filenames;
  50. extern int filecnt, fileidx;
  51. int timo_cursor;
  52. int timo_redraw;
  53. unsigned char dragging;
  54. int mox, moy;
  55. int run_command(const char *cline, Bool reload) {
  56. int fncnt, fnlen;
  57. char *cn, *cmdline;
  58. const char *co, *fname;
  59. pid_t pid;
  60. int ret, status;
  61. if (!cline || !*cline)
  62. return 0;
  63. fncnt = 0;
  64. co = cline - 1;
  65. while ((co = strchr(co + 1, '#')))
  66. fncnt++;
  67. if (!fncnt)
  68. return 0;
  69. ret = 0;
  70. fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel];
  71. fnlen = strlen(fname);
  72. cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) *
  73. sizeof(char));
  74. /* replace all '#' with filename */
  75. for (co = cline; *co; co++) {
  76. if (*co == '#') {
  77. *cn++ = '"';
  78. strcpy(cn, fname);
  79. cn += fnlen;
  80. *cn++ = '"';
  81. } else {
  82. *cn++ = *co;
  83. }
  84. }
  85. *cn = '\0';
  86. if ((pid = fork()) == 0) {
  87. execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
  88. warn("could not exec: /bin/sh");
  89. exit(1);
  90. } else if (pid < 0) {
  91. warn("could not fork. command line was: %s", cmdline);
  92. } else if (reload) {
  93. waitpid(pid, &status, 0);
  94. if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
  95. ret = 1;
  96. else
  97. warn("child exited with non-zero return value: %d. command line was: %s",
  98. WEXITSTATUS(status), cmdline);
  99. }
  100. free(cmdline);
  101. return ret;
  102. }
  103. void redraw() {
  104. if (mode == MODE_NORMAL) {
  105. img_render(&img, &win);
  106. if (timo_cursor)
  107. win_set_cursor(&win, CURSOR_ARROW);
  108. else if (!dragging)
  109. win_set_cursor(&win, CURSOR_NONE);
  110. } else {
  111. tns_render(&tns, &win);
  112. }
  113. update_title();
  114. timo_redraw = 0;
  115. }
  116. void on_keypress(XEvent *ev) {
  117. int i;
  118. XKeyEvent *kev;
  119. KeySym ksym;
  120. char key;
  121. if (!ev || ev->type != KeyPress)
  122. return;
  123. kev = &ev->xkey;
  124. XLookupString(kev, &key, 1, &ksym, NULL);
  125. if (EXT_COMMANDS && (CLEANMASK(kev->state) & ControlMask)) {
  126. for (i = 0; i < LEN(commands); i++) {
  127. if (commands[i].ksym == ksym) {
  128. win_set_cursor(&win, CURSOR_WATCH);
  129. if (run_command(commands[i].cmdline, commands[i].reload)) {
  130. if (mode == MODE_NORMAL) {
  131. if (fileidx < tns.cnt)
  132. tns_load(&tns, fileidx, filenames[fileidx], 1);
  133. img_close(&img, 1);
  134. load_image(fileidx);
  135. } else {
  136. if (!tns_load(&tns, tns.sel, filenames[tns.sel], 0)) {
  137. remove_file(tns.sel, 0);
  138. tns.dirty = 1;
  139. if (tns.sel >= tns.cnt)
  140. tns.sel = tns.cnt - 1;
  141. }
  142. }
  143. redraw();
  144. }
  145. if (mode == MODE_THUMBS)
  146. win_set_cursor(&win, CURSOR_ARROW);
  147. else if (!timo_cursor)
  148. win_set_cursor(&win, CURSOR_NONE);
  149. return;
  150. }
  151. }
  152. }
  153. for (i = 0; i < LEN(keys); i++) {
  154. if (ksym == keys[i].ksym && keys[i].handler) {
  155. if (keys[i].handler(ev, keys[i].arg))
  156. redraw();
  157. return;
  158. }
  159. }
  160. }
  161. void on_buttonpress(XEvent *ev) {
  162. int i, sel;
  163. XButtonEvent *bev;
  164. if (!ev || ev->type != ButtonPress)
  165. return;
  166. bev = &ev->xbutton;
  167. if (mode == MODE_NORMAL) {
  168. if (!dragging) {
  169. win_set_cursor(&win, CURSOR_ARROW);
  170. timo_cursor = TO_CURSOR_HIDE;
  171. }
  172. for (i = 0; i < LEN(buttons); i++) {
  173. if (CLEANMASK(bev->state) == CLEANMASK(buttons[i].mod) &&
  174. bev->button == buttons[i].button && buttons[i].handler)
  175. {
  176. if (buttons[i].handler(ev, buttons[i].arg))
  177. redraw();
  178. return;
  179. }
  180. }
  181. } else {
  182. /* thumbnail mode */
  183. switch (bev->button) {
  184. case Button1:
  185. if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
  186. if (sel == tns.sel) {
  187. load_image(tns.sel);
  188. mode = MODE_NORMAL;
  189. timo_cursor = TO_CURSOR_HIDE;
  190. } else {
  191. tns_highlight(&tns, &win, tns.sel, False);
  192. tns_highlight(&tns, &win, sel, True);
  193. tns.sel = sel;
  194. }
  195. redraw();
  196. break;
  197. }
  198. break;
  199. case Button4:
  200. case Button5:
  201. if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN))
  202. redraw();
  203. break;
  204. }
  205. }
  206. }
  207. void on_motionnotify(XEvent *ev) {
  208. XMotionEvent *mev;
  209. if (!ev || ev->type != MotionNotify)
  210. return;
  211. mev = &ev->xmotion;
  212. if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
  213. if (img_move(&img, &win, mev->x - mox, mev->y - moy))
  214. timo_redraw = TO_IMAGE_DRAG;
  215. mox = mev->x;
  216. moy = mev->y;
  217. }
  218. }
  219. void run() {
  220. int xfd, timeout;
  221. fd_set fds;
  222. struct timeval tt, t0, t1;
  223. XEvent ev;
  224. dragging = 0;
  225. timo_cursor = mode == MODE_NORMAL ? TO_CURSOR_HIDE : 0;
  226. redraw();
  227. while (1) {
  228. if (mode == MODE_THUMBS && tns.cnt < filecnt) {
  229. /* load thumbnails */
  230. win_set_cursor(&win, CURSOR_WATCH);
  231. gettimeofday(&t0, 0);
  232. while (tns.cnt < filecnt && !XPending(win.env.dpy)) {
  233. if (tns_load(&tns, tns.cnt, filenames[tns.cnt], 0))
  234. tns.cnt++;
  235. else
  236. remove_file(tns.cnt, 0);
  237. gettimeofday(&t1, 0);
  238. if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD)
  239. break;
  240. }
  241. if (tns.cnt == filecnt)
  242. win_set_cursor(&win, CURSOR_ARROW);
  243. if (!XPending(win.env.dpy)) {
  244. redraw();
  245. continue;
  246. } else {
  247. timo_redraw = TO_THUMBS_LOAD;
  248. }
  249. } else if (timo_cursor || timo_redraw) {
  250. /* check active timeouts */
  251. gettimeofday(&t0, 0);
  252. timeout = MIN(timo_cursor + 1, timo_redraw + 1);
  253. MSEC_TO_TIMEVAL(timeout, &tt);
  254. xfd = ConnectionNumber(win.env.dpy);
  255. FD_ZERO(&fds);
  256. FD_SET(xfd, &fds);
  257. if (!XPending(win.env.dpy))
  258. select(xfd + 1, &fds, 0, 0, &tt);
  259. gettimeofday(&t1, 0);
  260. timeout = MIN(TIMEDIFF(&t1, &t0), timeout);
  261. /* timeouts fired? */
  262. if (timo_cursor) {
  263. timo_cursor = MAX(0, timo_cursor - timeout);
  264. if (!timo_cursor)
  265. win_set_cursor(&win, CURSOR_NONE);
  266. }
  267. if (timo_redraw) {
  268. timo_redraw = MAX(0, timo_redraw - timeout);
  269. if (!timo_redraw)
  270. redraw();
  271. }
  272. if ((timo_cursor || timo_redraw) && !XPending(win.env.dpy))
  273. continue;
  274. }
  275. if (!XNextEvent(win.env.dpy, &ev)) {
  276. switch (ev.type) {
  277. case ButtonPress:
  278. on_buttonpress(&ev);
  279. break;
  280. case ButtonRelease:
  281. if (dragging) {
  282. dragging = 0;
  283. if (mode == MODE_NORMAL) {
  284. win_set_cursor(&win, CURSOR_ARROW);
  285. timo_cursor = TO_CURSOR_HIDE;
  286. }
  287. }
  288. break;
  289. case ClientMessage:
  290. if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
  291. return;
  292. break;
  293. case ConfigureNotify:
  294. if (win_configure(&win, &ev.xconfigure)) {
  295. timo_redraw = TO_WIN_RESIZE;
  296. if (mode == MODE_NORMAL)
  297. img.checkpan = 1;
  298. else
  299. tns.dirty = 1;
  300. }
  301. break;
  302. case KeyPress:
  303. on_keypress(&ev);
  304. break;
  305. case MotionNotify:
  306. if (dragging) {
  307. on_motionnotify(&ev);
  308. } else if (mode == MODE_NORMAL) {
  309. if (!timo_cursor)
  310. win_set_cursor(&win, CURSOR_ARROW);
  311. timo_cursor = TO_CURSOR_HIDE;
  312. }
  313. break;
  314. }
  315. }
  316. }
  317. }
  318. /* handler functions for key and button mappings: */
  319. int quit(XEvent *e, arg_t a) {
  320. cleanup();
  321. exit(0);
  322. }
  323. int reload(XEvent *e, arg_t a) {
  324. if (mode == MODE_NORMAL) {
  325. load_image(fileidx);
  326. return 1;
  327. } else {
  328. return 0;
  329. }
  330. }
  331. int toggle_fullscreen(XEvent *e, arg_t a) {
  332. win_toggle_fullscreen(&win);
  333. if (mode == MODE_NORMAL)
  334. img.checkpan = 1;
  335. else
  336. tns.dirty = 1;
  337. timo_redraw = TO_WIN_RESIZE;
  338. return 0;
  339. }
  340. int toggle_antialias(XEvent *e, arg_t a) {
  341. if (mode == MODE_NORMAL) {
  342. img_toggle_antialias(&img);
  343. return 1;
  344. } else {
  345. return 0;
  346. }
  347. }
  348. int toggle_alpha(XEvent *e, arg_t a) {
  349. if (mode == MODE_NORMAL) {
  350. img.alpha ^= 1;
  351. return 1;
  352. } else {
  353. return 0;
  354. }
  355. }
  356. int switch_mode(XEvent *e, arg_t a) {
  357. if (mode == MODE_NORMAL) {
  358. if (!tns.thumbs)
  359. tns_init(&tns, filecnt);
  360. img_close(&img, 0);
  361. win_set_cursor(&win, CURSOR_ARROW);
  362. timo_cursor = 0;
  363. tns.sel = fileidx;
  364. tns.dirty = 1;
  365. mode = MODE_THUMBS;
  366. } else {
  367. timo_cursor = TO_CURSOR_HIDE;
  368. load_image(tns.sel);
  369. mode = MODE_NORMAL;
  370. }
  371. return 1;
  372. }
  373. int navigate(XEvent *e, arg_t n) {
  374. if (mode == MODE_NORMAL) {
  375. n += fileidx;
  376. if (n < 0)
  377. n = 0;
  378. if (n >= filecnt)
  379. n = filecnt - 1;
  380. if (n != fileidx) {
  381. load_image(n);
  382. return 1;
  383. }
  384. }
  385. return 0;
  386. }
  387. int first(XEvent *e, arg_t a) {
  388. if (mode == MODE_NORMAL && fileidx != 0) {
  389. load_image(0);
  390. return 1;
  391. } else if (mode == MODE_THUMBS && tns.sel != 0) {
  392. tns.sel = 0;
  393. tns.dirty = 1;
  394. return 1;
  395. } else {
  396. return 0;
  397. }
  398. }
  399. int last(XEvent *e, arg_t a) {
  400. if (mode == MODE_NORMAL && fileidx != filecnt - 1) {
  401. load_image(filecnt - 1);
  402. return 1;
  403. } else if (mode == MODE_THUMBS && tns.sel != tns.cnt - 1) {
  404. tns.sel = tns.cnt - 1;
  405. tns.dirty = 1;
  406. return 1;
  407. } else {
  408. return 0;
  409. }
  410. }
  411. int remove_image(XEvent *e, arg_t a) {
  412. if (mode == MODE_NORMAL) {
  413. remove_file(fileidx, 1);
  414. load_image(fileidx >= filecnt ? filecnt - 1 : fileidx);
  415. return 1;
  416. } else if (tns.sel < tns.cnt) {
  417. remove_file(tns.sel, 1);
  418. tns.dirty = 1;
  419. if (tns.sel >= tns.cnt)
  420. tns.sel = tns.cnt - 1;
  421. return 1;
  422. } else {
  423. return 0;
  424. }
  425. }
  426. int move(XEvent *e, arg_t dir) {
  427. if (mode == MODE_NORMAL)
  428. return img_pan(&img, &win, dir, 0);
  429. else
  430. return tns_move_selection(&tns, &win, dir);
  431. }
  432. int scroll(XEvent *e, arg_t dir) {
  433. if (mode == MODE_NORMAL)
  434. return img_pan(&img, &win, dir, 1);
  435. else
  436. return 0;
  437. }
  438. int pan_edge(XEvent *e, arg_t dir) {
  439. if (mode == MODE_NORMAL)
  440. return img_pan_edge(&img, &win, dir);
  441. else
  442. return 0;
  443. }
  444. int drag(XEvent *e, arg_t a) {
  445. if (mode == MODE_NORMAL) {
  446. mox = e->xbutton.x;
  447. moy = e->xbutton.y;
  448. win_set_cursor(&win, CURSOR_HAND);
  449. timo_cursor = 0;
  450. dragging = 1;
  451. }
  452. return 0;
  453. }
  454. int rotate(XEvent *e, arg_t dir) {
  455. if (mode == MODE_NORMAL) {
  456. if (dir == DIR_LEFT) {
  457. img_rotate_left(&img, &win);
  458. return 1;
  459. } else if (dir == DIR_RIGHT) {
  460. img_rotate_right(&img, &win);
  461. return 1;
  462. }
  463. }
  464. return 0;
  465. }
  466. int zoom(XEvent *e, arg_t scale) {
  467. if (mode != MODE_NORMAL)
  468. return 0;
  469. if (scale > 0)
  470. return img_zoom_in(&img, &win);
  471. else if (scale < 0)
  472. return img_zoom_out(&img, &win);
  473. else
  474. return img_zoom(&img, &win, 1.0);
  475. }
  476. int fit_to_win(XEvent *e, arg_t ret) {
  477. if (mode == MODE_NORMAL) {
  478. if ((ret = img_fit_win(&img, &win)))
  479. img_center(&img, &win);
  480. return ret;
  481. } else {
  482. return 0;
  483. }
  484. }
  485. int fit_to_img(XEvent *e, arg_t ret) {
  486. int x, y;
  487. unsigned int w, h;
  488. if (mode == MODE_NORMAL) {
  489. x = MAX(0, win.x + img.x);
  490. y = MAX(0, win.y + img.y);
  491. w = img.w * img.zoom;
  492. h = img.h * img.zoom;
  493. if ((ret = win_moveresize(&win, x, y, w, h))) {
  494. img.x = x - win.x;
  495. img.y = y - win.y;
  496. }
  497. return ret;
  498. } else {
  499. return 0;
  500. }
  501. }