Krolyxon
Commit eee921e

Branches

Refs →

nsxivmain.c

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