Krolyxon
Commit e999688

Branches

Refs →

nsxivmain.c

745 lines · 15.7 KB · C

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 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
19#include <stdlib.h>
20#include <stdio.h>
21#include <string.h>
22#include <sys/select.h>
23#include <sys/stat.h>
24#include <sys/time.h>
25#include <sys/wait.h>
26#include <unistd.h>
27
28#include <X11/Xlib.h>
29#include <X11/Xutil.h>
30#include <X11/keysym.h>
31
32#include "config.h"
33#include "image.h"
34#include "options.h"
35#include "thumbs.h"
36#include "util.h"
37#include "window.h"
38
39#ifdef EXT_COMMANDS
40#include "commands.h"
41#endif
42
43typedef enum {
44 MODE_NORMAL = 0,
45 MODE_THUMBS
46} appmode_t;
47
48void update_title();
49int check_append(const char*);
50void run();
51
52appmode_t mode;
53img_t img;
54tns_t tns;
55win_t win;
56
57#define FNAME_CNT 1024
58const char **filenames;
59int filecnt, fileidx;
60size_t filesize;
61
62#define TITLE_LEN 256
63char win_title[TITLE_LEN];
64
65void cleanup() {
66 static int in = 0;
67
68 if (!in++) {
69 img_close(&img, 0);
70 img_free(&img);
71 tns_free(&tns, &win);
72 win_close(&win);
73 }
74}
75
76int load_image(int new) {
77 int ret = 0;
78 struct stat fstats;
79
80 if (new >= 0 && new < filecnt) {
81 win_set_cursor(&win, CURSOR_WATCH);
82 img_close(&img, 0);
83 fileidx = new;
84 if (!stat(filenames[fileidx], &fstats))
85 filesize = fstats.st_size;
86 else
87 filesize = 0;
88 if (!(ret = img_load(&img, filenames[fileidx])))
89 win_set_cursor(&win, CURSOR_NONE);
90 }
91
92 return ret;
93}
94
95int main(int argc, char **argv) {
96 int i, j;
97 const char *filename;
98 char **fnames;
99 struct stat fstats;
100
101 parse_options(argc, argv);
102
103 if (!options->filecnt) {
104 print_usage();
105 exit(1);
106 }
107
108 if (options->recursive || options->from_stdin)
109 filecnt = FNAME_CNT;
110 else
111 filecnt = options->filecnt;
112
113 filenames = (const char**) s_malloc(filecnt * sizeof(const char*));
114 fileidx = 0;
115
116 if (options->from_stdin) {
117 while ((filename = readline(stdin))) {
118 if (!*filename || !check_append(filename))
119 free((void*) filename);
120 }
121 } else {
122 for (i = 0; i < options->filecnt; ++i) {
123 filename = options->filenames[i];
124 if (!stat(filename, &fstats) && S_ISDIR(fstats.st_mode)) {
125 if (options->recursive && (fnames = read_dir_rec(filename))) {
126 for (j = 0; fnames[j]; ++j) {
127 if (!check_append(fnames[j]))
128 free(fnames[j]);
129 }
130 free(fnames);
131 } else {
132 warn("ignoring directory: %s", filename);
133 }
134 } else {
135 check_append(filename);
136 }
137 }
138 }
139
140 filecnt = fileidx;
141 fileidx = 0;
142
143 if (!filecnt) {
144 fprintf(stderr, "sxiv: no valid image filename given, aborting\n");
145 exit(1);
146 }
147
148 win_open(&win);
149 img_init(&img, &win);
150
151 if (options->thumbnails) {
152 mode = MODE_THUMBS;
153 tns_init(&tns, filecnt);
154 win_clear(&win);
155 win_draw(&win);
156 } else {
157 mode = MODE_NORMAL;
158 tns.thumbs = NULL;
159 load_image(fileidx);
160 img_render(&img, &win);
161 }
162
163 update_title();
164 run();
165 cleanup();
166
167 return 0;
168}
169
170void update_title() {
171 int n;
172 float size;
173 const char *unit;
174
175 if (mode == MODE_THUMBS) {
176 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] %s",
177 tns.cnt ? tns.sel + 1 : 0, tns.cnt,
178 tns.cnt ? filenames[tns.sel] : "");
179 } else {
180 if (img.im) {
181 size = filesize;
182 size_readable(&size, &unit);
183 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] <%d%%> (%.2f%s) %s",
184 fileidx + 1, filecnt, (int) (img.zoom * 100.0), size, unit,
185 filenames[fileidx]);
186 } else {
187 n = snprintf(win_title, TITLE_LEN, "sxiv: [%d/%d] invalid: %s",
188 fileidx + 1, filecnt, filenames[fileidx]);
189 }
190 }
191
192 if (n >= TITLE_LEN) {
193 win_title[TITLE_LEN - 2] = '.';
194 win_title[TITLE_LEN - 3] = '.';
195 win_title[TITLE_LEN - 4] = '.';
196 }
197
198 win_set_title(&win, win_title);
199}
200
201int check_append(const char *filename) {
202 if (!filename)
203 return 0;
204
205 if (access(filename, R_OK)) {
206 warn("could not open file: %s", filename);
207 return 0;
208 } else if (options->all || img_check(filename)) {
209 if (fileidx == filecnt) {
210 filecnt *= 2;
211 filenames = (const char**) s_realloc(filenames,
212 filecnt * sizeof(const char*));
213 }
214 filenames[fileidx++] = filename;
215 return 1;
216 } else {
217 return 0;
218 }
219}
220
221#ifdef EXT_COMMANDS
222int run_command(const char *cline, Bool reload) {
223 int fncnt, fnlen;
224 char *cn, *cmdline;
225 const char *co, *fname;
226 pid_t pid;
227 int ret, status;
228
229 if (!cline || !*cline)
230 return 0;
231
232 fncnt = 0;
233 co = cline - 1;
234 while ((co = strchr(co + 1, '#')))
235 ++fncnt;
236
237 if (!fncnt)
238 return 0;
239
240 ret = 0;
241 fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel];
242 fnlen = strlen(fname);
243 cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) *
244 sizeof(char));
245
246 /* replace all '#' with filename */
247 for (co = cline; *co; ++co) {
248 if (*co == '#') {
249 *cn++ = '"';
250 strcpy(cn, fname);
251 cn += fnlen;
252 *cn++ = '"';
253 } else {
254 *cn++ = *co;
255 }
256 }
257 *cn = '\0';
258
259 if ((pid = fork()) == 0) {
260 execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
261 warn("could not exec: /bin/sh");
262 exit(1);
263 } else if (pid < 0) {
264 warn("could not fork. command line was: %s", cmdline);
265 } else if (reload) {
266 waitpid(pid, &status, 0);
267 if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
268 ret = 1;
269 else
270 warn("child exited with non-zero return value: %d. command line was: %s",
271 WEXITSTATUS(status), cmdline);
272 }
273
274 free(cmdline);
275 return ret;
276}
277#endif /* EXT_COMMANDS */
278
279
280/* event handling */
281
282#define TO_WIN_RESIZE 75000;
283#define TO_IMAGE_DRAG 1000;
284#define TO_CURSOR_HIDE 1500000;
285#define TO_THUMBS_LOAD 75000;
286int timo_cursor;
287int timo_redraw;
288
289unsigned char drag;
290int mox, moy;
291
292void redraw() {
293 if (mode == MODE_NORMAL) {
294 img_render(&img, &win);
295 if (timo_cursor)
296 win_set_cursor(&win, CURSOR_ARROW);
297 else if (!drag)
298 win_set_cursor(&win, CURSOR_NONE);
299 } else {
300 tns_render(&tns, &win);
301 }
302 update_title();
303 timo_redraw = 0;
304}
305
306void on_keypress(XKeyEvent *kev) {
307 int i, x, y;
308 unsigned int w, h;
309 char key;
310 KeySym ksym;
311 int changed;
312
313 if (!kev)
314 return;
315
316 XLookupString(kev, &key, 1, &ksym, NULL);
317 changed = 0;
318
319#ifdef EXT_COMMANDS
320 /* external commands from commands.h */
321 if (CLEANMASK(kev->state) & ControlMask) {
322 for (i = 0; i < LEN(commands); ++i) {
323 if (commands[i].ksym == ksym) {
324 win_set_cursor(&win, CURSOR_WATCH);
325 if (run_command(commands[i].cmdline, commands[i].reload)) {
326 if (mode == MODE_NORMAL) {
327 img_close(&img, 1);
328 load_image(fileidx);
329 tns_load(&tns, &win, fileidx, filenames[fileidx]);
330 } else {
331 tns_load(&tns, &win, tns.sel, filenames[tns.sel]);
332 }
333 redraw();
334 }
335 if (mode == MODE_THUMBS)
336 win_set_cursor(&win, CURSOR_ARROW);
337 return;
338 }
339 }
340 }
341#endif
342
343 if (mode == MODE_NORMAL) {
344 switch (ksym) {
345 /* navigate image list */
346 case XK_n:
347 case XK_space:
348 if (fileidx + 1 < filecnt)
349 changed = load_image(fileidx + 1);
350 break;
351 case XK_p:
352 case XK_BackSpace:
353 if (fileidx > 0)
354 changed = load_image(fileidx - 1);
355 break;
356 case XK_bracketleft:
357 if (fileidx != 0)
358 changed = load_image(MAX(0, fileidx - 10));
359 break;
360 case XK_bracketright:
361 if (fileidx != filecnt - 1)
362 changed = load_image(MIN(fileidx + 10, filecnt - 1));
363 break;
364 case XK_g:
365 if (fileidx != 0)
366 changed = load_image(0);
367 break;
368 case XK_G:
369 if (fileidx != filecnt - 1)
370 changed = load_image(filecnt - 1);
371 break;
372
373 /* zooming */
374 case XK_plus:
375 case XK_equal:
376 changed = img_zoom_in(&img);
377 break;
378 case XK_minus:
379 changed = img_zoom_out(&img);
380 break;
381 case XK_0:
382 changed = img_zoom(&img, 1.0);
383 break;
384 case XK_w:
385 if ((changed = img_fit_win(&img, &win)))
386 img_center(&img, &win);
387 break;
388
389 /* panning */
390 case XK_h:
391 case XK_Left:
392 changed = img_pan(&img, &win, PAN_LEFT);
393 break;
394 case XK_j:
395 case XK_Down:
396 changed = img_pan(&img, &win, PAN_DOWN);
397 break;
398 case XK_k:
399 case XK_Up:
400 changed = img_pan(&img, &win, PAN_UP);
401 break;
402 case XK_l:
403 case XK_Right:
404 changed = img_pan(&img, &win, PAN_RIGHT);
405 break;
406
407 /* rotation */
408 case XK_less:
409 img_rotate_left(&img, &win);
410 changed = 1;
411 break;
412 case XK_greater:
413 img_rotate_right(&img, &win);
414 changed = 1;
415 break;
416
417 /* control window */
418 case XK_W:
419 x = MAX(0, win.x + img.x);
420 y = MAX(0, win.y + img.y);
421 w = img.w * img.zoom;
422 h = img.h * img.zoom;
423 if ((changed = win_moveresize(&win, x, y, w, h))) {
424 img.x = x - win.x;
425 img.y = y - win.y;
426 }
427 break;
428
429 /* switch to thumbnail mode */
430 case XK_Return:
431 if (!tns.thumbs)
432 tns_init(&tns, filecnt);
433 img_close(&img, 0);
434 mode = MODE_THUMBS;
435 win_set_cursor(&win, CURSOR_ARROW);
436 timo_cursor = 0;
437 tns.sel = fileidx;
438 changed = tns.dirty = 1;
439 break;
440
441 /* miscellaneous */
442 case XK_a:
443 img_toggle_antialias(&img);
444 changed = 1;
445 break;
446 case XK_A:
447 img.alpha ^= 1;
448 changed = 1;
449 break;
450 case XK_r:
451 changed = load_image(fileidx);
452 break;
453 }
454 } else {
455 /* thumbnail mode */
456 switch (ksym) {
457 /* open selected image */
458 case XK_Return:
459 load_image(tns.sel);
460 mode = MODE_NORMAL;
461 changed = 1;
462 break;
463
464 /* move selection */
465 case XK_h:
466 case XK_Left:
467 changed = tns_move_selection(&tns, &win, TNS_LEFT);
468 break;
469 case XK_j:
470 case XK_Down:
471 changed = tns_move_selection(&tns, &win, TNS_DOWN);
472 break;
473 case XK_k:
474 case XK_Up:
475 changed = tns_move_selection(&tns, &win, TNS_UP);
476 break;
477 case XK_l:
478 case XK_Right:
479 changed = tns_move_selection(&tns, &win, TNS_RIGHT);
480 break;
481 case XK_g:
482 if (tns.sel != 0) {
483 tns.sel = 0;
484 changed = tns.dirty = 1;
485 }
486 break;
487 case XK_G:
488 if (tns.sel != tns.cnt - 1) {
489 tns.sel = tns.cnt - 1;
490 changed = tns.dirty = 1;
491 }
492 }
493 }
494
495 /* common key mappings */
496 switch (ksym) {
497 case XK_q:
498 cleanup();
499 exit(0);
500
501 case XK_f:
502 win_toggle_fullscreen(&win);
503 /* render on next configurenotify */
504 break;
505
506 case XK_D:
507 if (mode == MODE_THUMBS) {
508 if (tns.sel >= tns.cnt)
509 break;
510 i = tns.sel;
511 } else {
512 i = fileidx;
513 }
514 if (filecnt == 1) {
515 cleanup();
516 exit(0);
517 }
518 if (i + 1 < filecnt)
519 memmove(filenames + i, filenames + i + 1, (filecnt - i - 1) *
520 sizeof(const char*));
521 else if (fileidx)
522 fileidx--;
523 if (i + 1 < tns.cnt) {
524 memmove(tns.thumbs + i, tns.thumbs + i + 1, (tns.cnt - i - 1) *
525 sizeof(thumb_t));
526 memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t));
527 } else if (tns.sel) {
528 tns.sel--;
529 }
530 filecnt--;
531 if (mode == MODE_NORMAL)
532 load_image(fileidx);
533 if (i < tns.cnt)
534 tns.cnt--;
535 changed = tns.dirty = 1;
536 break;
537 }
538
539 if (changed)
540 redraw();
541}
542
543void on_buttonpress(XButtonEvent *bev) {
544 int changed, sel;
545 unsigned int mask;
546
547 if (!bev)
548 return;
549
550 mask = CLEANMASK(bev->state);
551 changed = 0;
552
553 if (mode == MODE_NORMAL) {
554 win_set_cursor(&win, CURSOR_ARROW);
555 timo_cursor = TO_CURSOR_HIDE;
556
557 switch (bev->button) {
558 case Button1:
559 if (fileidx + 1 < filecnt)
560 changed = load_image(fileidx + 1);
561 break;
562 case Button2:
563 mox = bev->x;
564 moy = bev->y;
565 win_set_cursor(&win, CURSOR_HAND);
566 timo_cursor = 0;
567 drag = 1;
568 break;
569 case Button3:
570 if (fileidx > 0)
571 changed = load_image(fileidx - 1);
572 break;
573 case Button4:
574 if (mask == ControlMask)
575 changed = img_zoom_in(&img);
576 else if (mask == ShiftMask)
577 changed = img_pan(&img, &win, PAN_LEFT);
578 else
579 changed = img_pan(&img, &win, PAN_UP);
580 break;
581 case Button5:
582 if (mask == ControlMask)
583 changed = img_zoom_out(&img);
584 else if (mask == ShiftMask)
585 changed = img_pan(&img, &win, PAN_RIGHT);
586 else
587 changed = img_pan(&img, &win, PAN_DOWN);
588 break;
589 case 6:
590 changed = img_pan(&img, &win, PAN_LEFT);
591 break;
592 case 7:
593 changed = img_pan(&img, &win, PAN_RIGHT);
594 break;
595 }
596 } else {
597 /* thumbnail mode */
598 switch (bev->button) {
599 case Button1:
600 if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
601 if (sel == tns.sel) {
602 load_image(tns.sel);
603 mode = MODE_NORMAL;
604 timo_cursor = TO_CURSOR_HIDE;
605 } else {
606 tns_highlight(&tns, &win, tns.sel, False);
607 tns_highlight(&tns, &win, sel, True);
608 tns.sel = sel;
609 }
610 changed = 1;
611 break;
612 }
613 break;
614 case Button4:
615 changed = tns_scroll(&tns, TNS_UP);
616 break;
617 case Button5:
618 changed = tns_scroll(&tns, TNS_DOWN);
619 break;
620 }
621 }
622
623 if (changed)
624 redraw();
625}
626
627void on_motionnotify(XMotionEvent *mev) {
628 if (!mev)
629 return;
630
631 if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
632 if (img_move(&img, &win, mev->x - mox, mev->y - moy))
633 timo_redraw = TO_IMAGE_DRAG;
634
635 mox = mev->x;
636 moy = mev->y;
637 }
638}
639
640void run() {
641 int xfd, timeout;
642 fd_set fds;
643 struct timeval tt, t0, t1;
644 XEvent ev;
645
646 timo_cursor = timo_redraw = 0;
647 drag = 0;
648
649 while (1) {
650 if (mode == MODE_THUMBS && tns.cnt < filecnt) {
651 win_set_cursor(&win, CURSOR_WATCH);
652 gettimeofday(&t0, 0);
653
654 while (!XPending(win.env.dpy) && tns.cnt < filecnt) {
655 /* tns.cnt is increased inside tns_load */
656 tns_load(&tns, &win, tns.cnt, filenames[tns.cnt]);
657 gettimeofday(&t1, 0);
658 if (TV_TO_DOUBLE(t1) - TV_TO_DOUBLE(t0) >= 0.25)
659 break;
660 }
661 if (tns.cnt == filecnt)
662 win_set_cursor(&win, CURSOR_ARROW);
663 if (!XPending(win.env.dpy)) {
664 redraw();
665 continue;
666 } else {
667 timo_redraw = TO_THUMBS_LOAD;
668 }
669 } else if (timo_cursor || timo_redraw) {
670 gettimeofday(&t0, 0);
671 if (timo_cursor && timo_redraw)
672 timeout = MIN(timo_cursor, timo_redraw);
673 else if (timo_cursor)
674 timeout = timo_cursor;
675 else
676 timeout = timo_redraw;
677 tt.tv_sec = timeout / 1000000;
678 tt.tv_usec = timeout % 1000000;
679 xfd = ConnectionNumber(win.env.dpy);
680 FD_ZERO(&fds);
681 FD_SET(xfd, &fds);
682
683 if (!XPending(win.env.dpy))
684 select(xfd + 1, &fds, 0, 0, &tt);
685 gettimeofday(&t1, 0);
686 timeout = MIN((TV_TO_DOUBLE(t1) - TV_TO_DOUBLE(t0)) * 1000000, timeout);
687
688 /* timeouts fired? */
689 if (timo_cursor) {
690 timo_cursor = MAX(0, timo_cursor - timeout);
691 if (!timo_cursor)
692 win_set_cursor(&win, CURSOR_NONE);
693 }
694 if (timo_redraw) {
695 timo_redraw = MAX(0, timo_redraw - timeout);
696 if (!timo_redraw)
697 redraw();
698 }
699 if (!XPending(win.env.dpy) && (timo_cursor || timo_redraw))
700 continue;
701 }
702
703 if (!XNextEvent(win.env.dpy, &ev)) {
704 switch (ev.type) {
705 case KeyPress:
706 on_keypress(&ev.xkey);
707 break;
708 case ButtonPress:
709 on_buttonpress(&ev.xbutton);
710 break;
711 case ButtonRelease:
712 if (ev.xbutton.button == Button2) {
713 drag = 0;
714 if (mode == MODE_NORMAL) {
715 win_set_cursor(&win, CURSOR_ARROW);
716 timo_cursor = TO_CURSOR_HIDE;
717 }
718 }
719 break;
720 case MotionNotify:
721 if (drag) {
722 on_motionnotify(&ev.xmotion);
723 } else if (mode == MODE_NORMAL) {
724 if (!timo_cursor)
725 win_set_cursor(&win, CURSOR_ARROW);
726 timo_cursor = TO_CURSOR_HIDE;
727 }
728 break;
729 case ConfigureNotify:
730 if (win_configure(&win, &ev.xconfigure)) {
731 timo_redraw = TO_WIN_RESIZE;
732 if (mode == MODE_NORMAL)
733 img.checkpan = 1;
734 else
735 tns.dirty = 1;
736 }
737 break;
738 case ClientMessage:
739 if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
740 return;
741 break;
742 }
743 }
744 }
745}