Krolyxon
Commit bb85d3d

Branches

Refs →

nsxivmain.c

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