Krolyxon
Commit f52603b

Branches

Refs →

nsxivmain.c

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