Krolyxon
Commit 5cb6d12

Branches

Refs →

nsxivmain.c

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