Krolyxon
Commit b563a0a

Branches

Refs →

nsxivmain.c

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