├── .gitignore
├── icon
├── 16x16.png
├── 32x32.png
├── 48x48.png
├── 64x64.png
├── 128x128.png
├── Makefile
├── dat2h.awk
└── data.h
├── TODO
├── sxiv.desktop
├── exec
├── image-info
└── key-handler
├── commands.lst
├── autoreload_nop.c
├── Makefile
├── utf8.h
├── autoreload_inotify.c
├── options.c
├── util.c
├── config.def.h
├── README.md
├── sxiv.h
├── commands.c
├── sxiv.1
├── window.c
├── thumbs.c
├── LICENSE
├── image.c
└── main.c
/.gitignore:
--------------------------------------------------------------------------------
1 | config.h
2 | version.h
3 | *.d
4 | *.o
5 | sxiv
6 |
--------------------------------------------------------------------------------
/icon/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyb3rt/sxiv/HEAD/icon/16x16.png
--------------------------------------------------------------------------------
/icon/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyb3rt/sxiv/HEAD/icon/32x32.png
--------------------------------------------------------------------------------
/icon/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyb3rt/sxiv/HEAD/icon/48x48.png
--------------------------------------------------------------------------------
/icon/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyb3rt/sxiv/HEAD/icon/64x64.png
--------------------------------------------------------------------------------
/icon/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xyb3rt/sxiv/HEAD/icon/128x128.png
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | - Load all frames from TIFF files. We have to write our own loader for this to
2 | happen--just like we did for GIF images--because Imlib2 does not support
3 | multiple frames. Issue #241.
4 | - Add support for more embedded thumbnail formats. Right now, sxiv seems to use
5 | the smallest one. Issue #238.
6 |
--------------------------------------------------------------------------------
/sxiv.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Type=Application
3 | Name=sxiv
4 | GenericName=Image Viewer
5 | Exec=sxiv %F
6 | MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap;
7 | NoDisplay=true
8 | Icon=sxiv
9 |
--------------------------------------------------------------------------------
/icon/Makefile:
--------------------------------------------------------------------------------
1 | PREFIX = /usr/local
2 | ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png
3 |
4 | all:
5 |
6 | install:
7 | for f in $(ICONS); do \
8 | dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \
9 | mkdir -p "$$dir"; \
10 | cp "$$f" "$$dir/sxiv.png"; \
11 | chmod 644 "$$dir/sxiv.png"; \
12 | done
13 |
--------------------------------------------------------------------------------
/exec/image-info:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Example for $XDG_CONFIG_HOME/sxiv/exec/image-info
4 | # Called by sxiv(1) whenever an image gets loaded.
5 | # The output is displayed in sxiv's status bar.
6 | # Arguments:
7 | # $1: path to image file
8 | # $2: image width
9 | # $3: image height
10 |
11 | s=" " # field separator
12 |
13 | exec 2>/dev/null
14 |
15 | filename=$(basename -- "$1")
16 | filesize=$(du -Hh -- "$1" | cut -f 1)
17 | geometry="${2}x${3}"
18 |
19 | echo "${filesize}${s}${geometry}${s}${filename}"
20 |
21 |
--------------------------------------------------------------------------------
/icon/dat2h.awk:
--------------------------------------------------------------------------------
1 | #!/usr/bin/awk -f
2 |
3 | function printchars() {
4 | while (n > 0) {
5 | x = n / 16 >= 1 ? 16 : n;
6 | printf("0x%x%x,%s", x - 1, ref[c] - 1, ++i % 12 == 0 ? "\n" : " ");
7 | n -= x;
8 | }
9 | }
10 |
11 | /^$/ {
12 | printchars();
13 | printf("\n\n");
14 | c = "";
15 | i = 0;
16 | }
17 |
18 | /./ {
19 | if (!ref[$0]) {
20 | col[cnt++] = $0;
21 | ref[$0] = cnt;
22 | }
23 | if ($0 != c) {
24 | if (c != "")
25 | printchars();
26 | c = $0;
27 | n = 0;
28 | }
29 | n++;
30 | }
31 |
32 | END {
33 | for (i = 0; i < cnt; i++)
34 | printf("%s,%s", col[i], ++j % 4 == 0 || i + 1 == cnt ? "\n" : " ");
35 | }
36 |
--------------------------------------------------------------------------------
/commands.lst:
--------------------------------------------------------------------------------
1 | G_CMD(quit)
2 | G_CMD(switch_mode)
3 | G_CMD(toggle_fullscreen)
4 | G_CMD(toggle_bar)
5 | G_CMD(prefix_external)
6 | G_CMD(reload_image)
7 | G_CMD(remove_image)
8 | G_CMD(first)
9 | G_CMD(n_or_last)
10 | G_CMD(scroll_screen)
11 | G_CMD(zoom)
12 | G_CMD(toggle_image_mark)
13 | G_CMD(reverse_marks)
14 | G_CMD(mark_range)
15 | G_CMD(unmark_all)
16 | G_CMD(navigate_marked)
17 | G_CMD(change_gamma)
18 |
19 | I_CMD(navigate)
20 | I_CMD(cursor_navigate)
21 | I_CMD(alternate)
22 | I_CMD(navigate_frame)
23 | I_CMD(toggle_animation)
24 | I_CMD(scroll)
25 | I_CMD(scroll_to_edge)
26 | I_CMD(drag)
27 | I_CMD(set_zoom)
28 | I_CMD(fit_to_win)
29 | I_CMD(rotate)
30 | I_CMD(flip)
31 | I_CMD(toggle_antialias)
32 | I_CMD(toggle_alpha)
33 | I_CMD(slideshow)
34 |
35 | T_CMD(move_sel)
36 | T_CMD(reload_all)
37 |
38 |
--------------------------------------------------------------------------------
/autoreload_nop.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Max Voit
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 |
21 | void arl_init(arl_t *arl)
22 | {
23 | arl->fd = -1;
24 | }
25 |
26 | void arl_cleanup(arl_t *arl)
27 | {
28 | (void) arl;
29 | }
30 |
31 | void arl_setup(arl_t *arl, const char *filepath)
32 | {
33 | (void) arl;
34 | (void) filepath;
35 | }
36 |
37 | bool arl_handle(arl_t *arl)
38 | {
39 | (void) arl;
40 | return false;
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/exec/key-handler:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler
4 | # Called by sxiv(1) after the external prefix key (C-x by default) is pressed.
5 | # The next key combo is passed as its first argument. Passed via stdin are the
6 | # images to act upon, one path per line: all marked images, if in thumbnail
7 | # mode and at least one image has been marked, otherwise the current image.
8 | # sxiv(1) blocks until this script terminates. It then checks which images
9 | # have been modified and reloads them.
10 |
11 | # The key combo argument has the following form: "[C-][M-][S-]KEY",
12 | # where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X
13 | # keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix.
14 |
15 | rotate() {
16 | degree="$1"
17 | tr '\n' '\0' | xargs -0 realpath | sort | uniq | while read file; do
18 | case "$(file -b -i "$file")" in
19 | image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;;
20 | *) mogrify -rotate "$degree" "$file" ;;
21 | esac
22 | done
23 | }
24 |
25 | case "$1" in
26 | "C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;;
27 | "C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;;
28 | "C-e") while read file; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 pr -q -pa '$file' | less" & done ;;
29 | "C-g") tr '\n' '\0' | xargs -0 gimp & ;;
30 | "C-r") while read file; do rawtherapee "$file" & done ;;
31 | "C-comma") rotate 270 ;;
32 | "C-period") rotate 90 ;;
33 | "C-slash") rotate 180 ;;
34 | esac
35 |
36 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | version = 26
2 |
3 | srcdir = .
4 | VPATH = $(srcdir)
5 |
6 | PREFIX = /usr/local
7 | MANPREFIX = $(PREFIX)/share/man
8 |
9 | # autoreload backend: inotify/nop
10 | AUTORELOAD = inotify
11 |
12 | # enable features requiring giflib (-lgif)
13 | HAVE_GIFLIB = 1
14 |
15 | # enable features requiring libexif (-lexif)
16 | HAVE_LIBEXIF = 1
17 |
18 | cflags = -std=c99 -Wall -pedantic $(CFLAGS)
19 | cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \
20 | -DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \
21 | -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2
22 |
23 | lib_exif_0 =
24 | lib_exif_1 = -lexif
25 | lib_gif_0 =
26 | lib_gif_1 = -lgif
27 | ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \
28 | $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB))
29 |
30 | objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \
31 | thumbs.o util.o window.o
32 |
33 | all: sxiv
34 |
35 | .PHONY: all clean install uninstall
36 | .SUFFIXES:
37 | .SUFFIXES: .c .o
38 | $(V).SILENT:
39 |
40 | sxiv: $(objs)
41 | @echo "LINK $@"
42 | $(CC) $(LDFLAGS) -o $@ $(objs) $(ldlibs)
43 |
44 | $(objs): Makefile sxiv.h commands.lst config.h
45 | options.o: version.h
46 | window.o: icon/data.h
47 |
48 | .c.o:
49 | @echo "CC $@"
50 | $(CC) $(cflags) $(cppflags) -c -o $@ $<
51 |
52 | config.h:
53 | @echo "GEN $@"
54 | cp $(srcdir)/config.def.h $@
55 |
56 | version.h: Makefile .git/index
57 | @echo "GEN $@"
58 | v="$$(cd $(srcdir); git describe 2>/dev/null)"; \
59 | echo "#define VERSION \"$${v:-$(version)}\"" >$@
60 |
61 | .git/index:
62 |
63 | clean:
64 | rm -f *.o sxiv
65 |
66 | install: all
67 | @echo "INSTALL bin/sxiv"
68 | mkdir -p $(DESTDIR)$(PREFIX)/bin
69 | cp sxiv $(DESTDIR)$(PREFIX)/bin/
70 | chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv
71 | @echo "INSTALL sxiv.1"
72 | mkdir -p $(DESTDIR)$(MANPREFIX)/man1
73 | sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(version)!g" sxiv.1 \
74 | >$(DESTDIR)$(MANPREFIX)/man1/sxiv.1
75 | chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1
76 | @echo "INSTALL share/sxiv/"
77 | mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec
78 | cp exec/* $(DESTDIR)$(PREFIX)/share/sxiv/exec/
79 | chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/*
80 |
81 | uninstall:
82 | @echo "REMOVE bin/sxiv"
83 | rm -f $(DESTDIR)$(PREFIX)/bin/sxiv
84 | @echo "REMOVE sxiv.1"
85 | rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1
86 | @echo "REMOVE share/sxiv/"
87 | rm -rf $(DESTDIR)$(PREFIX)/share/sxiv
88 |
89 |
--------------------------------------------------------------------------------
/utf8.h:
--------------------------------------------------------------------------------
1 | /* Branchless UTF-8 decoder
2 | *
3 | * This is free and unencumbered software released into the public domain.
4 | */
5 | #ifndef UTF8_H
6 | #define UTF8_H
7 |
8 | #include
9 |
10 | /* Decode the next character, C, from BUF, reporting errors in E.
11 | *
12 | * Since this is a branchless decoder, four bytes will be read from the
13 | * buffer regardless of the actual length of the next character. This
14 | * means the buffer _must_ have at least three bytes of zero padding
15 | * following the end of the data stream.
16 | *
17 | * Errors are reported in E, which will be non-zero if the parsed
18 | * character was somehow invalid: invalid byte sequence, non-canonical
19 | * encoding, or a surrogate half.
20 | *
21 | * The function returns a pointer to the next character. When an error
22 | * occurs, this pointer will be a guess that depends on the particular
23 | * error, but it will always advance at least one byte.
24 | */
25 | static void *
26 | utf8_decode(void *buf, uint32_t *c, int *e)
27 | {
28 | static const char lengths[] = {
29 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
30 | 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
31 | };
32 | static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
33 | static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
34 | static const int shiftc[] = {0, 18, 12, 6, 0};
35 | static const int shifte[] = {0, 6, 4, 2, 0};
36 |
37 | unsigned char *s = buf;
38 | int len = lengths[s[0] >> 3];
39 |
40 | /* Compute the pointer to the next character early so that the next
41 | * iteration can start working on the next character. Neither Clang
42 | * nor GCC figure out this reordering on their own.
43 | */
44 | unsigned char *next = s + len + !len;
45 |
46 | /* Assume a four-byte character and load four bytes. Unused bits are
47 | * shifted out.
48 | */
49 | *c = (uint32_t)(s[0] & masks[len]) << 18;
50 | *c |= (uint32_t)(s[1] & 0x3f) << 12;
51 | *c |= (uint32_t)(s[2] & 0x3f) << 6;
52 | *c |= (uint32_t)(s[3] & 0x3f) << 0;
53 | *c >>= shiftc[len];
54 |
55 | /* Accumulate the various error conditions. */
56 | *e = (*c < mins[len]) << 6; // non-canonical encoding
57 | *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half?
58 | *e |= (*c > 0x10FFFF) << 8; // out of range?
59 | *e |= (s[1] & 0xc0) >> 2;
60 | *e |= (s[2] & 0xc0) >> 4;
61 | *e |= (s[3] ) >> 6;
62 | *e ^= 0x2a; // top two bits of each tail byte correct?
63 | *e >>= shifte[len];
64 |
65 | return next;
66 | }
67 |
68 | #endif
69 |
--------------------------------------------------------------------------------
/autoreload_inotify.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2017 Max Voit, Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | void arl_init(arl_t *arl)
28 | {
29 | arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
30 | arl->wd_dir = arl->wd_file = -1;
31 | if (arl->fd == -1)
32 | error(0, 0, "Could not initialize inotify, no automatic image reloading");
33 | }
34 |
35 | CLEANUP void arl_cleanup(arl_t *arl)
36 | {
37 | if (arl->fd != -1)
38 | close(arl->fd);
39 | free(arl->filename);
40 | }
41 |
42 | static void rm_watch(int fd, int *wd)
43 | {
44 | if (*wd != -1) {
45 | inotify_rm_watch(fd, *wd);
46 | *wd = -1;
47 | }
48 | }
49 |
50 | static void add_watch(int fd, int *wd, const char *path, uint32_t mask)
51 | {
52 | *wd = inotify_add_watch(fd, path, mask);
53 | if (*wd == -1)
54 | error(0, errno, "inotify: %s", path);
55 | }
56 |
57 | void arl_setup(arl_t *arl, const char *filepath)
58 | {
59 | char *base = strrchr(filepath, '/');
60 |
61 | if (arl->fd == -1)
62 | return;
63 |
64 | rm_watch(arl->fd, &arl->wd_dir);
65 | rm_watch(arl->fd, &arl->wd_file);
66 |
67 | add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF);
68 |
69 | free(arl->filename);
70 | arl->filename = estrdup(filepath);
71 |
72 | if (base != NULL) {
73 | arl->filename[++base - filepath] = '\0';
74 | add_watch(arl->fd, &arl->wd_dir, arl->filename, IN_CREATE | IN_MOVED_TO);
75 | strcpy(arl->filename, base);
76 | }
77 | }
78 |
79 | union {
80 | char d[4096]; /* aligned buffer */
81 | struct inotify_event e;
82 | } buf;
83 |
84 | bool arl_handle(arl_t *arl)
85 | {
86 | bool reload = false;
87 | char *ptr;
88 | const struct inotify_event *e;
89 |
90 | for (;;) {
91 | ssize_t len = read(arl->fd, buf.d, sizeof(buf.d));
92 |
93 | if (len == -1) {
94 | if (errno == EINTR)
95 | continue;
96 | break;
97 | }
98 | for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) {
99 | e = (const struct inotify_event*) ptr;
100 | if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) {
101 | reload = true;
102 | } else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) {
103 | rm_watch(arl->fd, &arl->wd_file);
104 | } else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) {
105 | if (STREQ(e->name, arl->filename))
106 | reload = true;
107 | }
108 | }
109 | }
110 | return reload;
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/options.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _IMAGE_CONFIG
21 | #include "config.h"
22 | #include "version.h"
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | opt_t _options;
29 | const opt_t *options = (const opt_t*) &_options;
30 |
31 | void print_usage(void)
32 | {
33 | printf("usage: sxiv [-abcfhiopqrtvZ] [-A FRAMERATE] [-e WID] [-G GAMMA] "
34 | "[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] [-z ZOOM] "
35 | "FILES...\n");
36 | }
37 |
38 | void print_version(void)
39 | {
40 | puts("sxiv " VERSION);
41 | }
42 |
43 | void parse_options(int argc, char **argv)
44 | {
45 | int n, opt;
46 | char *end, *s;
47 | const char *scalemodes = "dfwh";
48 |
49 | progname = strrchr(argv[0], '/');
50 | progname = progname ? progname + 1 : argv[0];
51 |
52 | _options.from_stdin = false;
53 | _options.to_stdout = false;
54 | _options.recursive = false;
55 | _options.startnum = 0;
56 |
57 | _options.scalemode = SCALE_DOWN;
58 | _options.zoom = 1.0;
59 | _options.animate = false;
60 | _options.gamma = 0;
61 | _options.slideshow = 0;
62 | _options.framerate = 0;
63 |
64 | _options.fullscreen = false;
65 | _options.embed = 0;
66 | _options.hide_bar = false;
67 | _options.geometry = NULL;
68 | _options.res_name = NULL;
69 |
70 | _options.quiet = false;
71 | _options.thumb_mode = false;
72 | _options.clean_cache = false;
73 | _options.private_mode = false;
74 |
75 | while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:tvZz:")) != -1) {
76 | switch (opt) {
77 | case '?':
78 | print_usage();
79 | exit(EXIT_FAILURE);
80 | case 'A':
81 | n = strtol(optarg, &end, 0);
82 | if (*end != '\0' || n <= 0)
83 | error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg);
84 | _options.framerate = n;
85 | /* fall through */
86 | case 'a':
87 | _options.animate = true;
88 | break;
89 | case 'b':
90 | _options.hide_bar = true;
91 | break;
92 | case 'c':
93 | _options.clean_cache = true;
94 | break;
95 | case 'e':
96 | n = strtol(optarg, &end, 0);
97 | if (*end != '\0')
98 | error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg);
99 | _options.embed = n;
100 | break;
101 | case 'f':
102 | _options.fullscreen = true;
103 | break;
104 | case 'G':
105 | n = strtol(optarg, &end, 0);
106 | if (*end != '\0')
107 | error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg);
108 | _options.gamma = n;
109 | break;
110 | case 'g':
111 | _options.geometry = optarg;
112 | break;
113 | case 'h':
114 | print_usage();
115 | exit(EXIT_SUCCESS);
116 | case 'i':
117 | _options.from_stdin = true;
118 | break;
119 | case 'n':
120 | n = strtol(optarg, &end, 0);
121 | if (*end != '\0' || n <= 0)
122 | error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg);
123 | _options.startnum = n - 1;
124 | break;
125 | case 'N':
126 | _options.res_name = optarg;
127 | break;
128 | case 'o':
129 | _options.to_stdout = true;
130 | break;
131 | case 'p':
132 | _options.private_mode = true;
133 | break;
134 | case 'q':
135 | _options.quiet = true;
136 | break;
137 | case 'r':
138 | _options.recursive = true;
139 | break;
140 | case 'S':
141 | n = strtof(optarg, &end) * 10;
142 | if (*end != '\0' || n <= 0)
143 | error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg);
144 | _options.slideshow = n;
145 | break;
146 | case 's':
147 | s = strchr(scalemodes, optarg[0]);
148 | if (s == NULL || *s == '\0' || strlen(optarg) != 1)
149 | error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg);
150 | _options.scalemode = s - scalemodes;
151 | break;
152 | case 't':
153 | _options.thumb_mode = true;
154 | break;
155 | case 'v':
156 | print_version();
157 | exit(EXIT_SUCCESS);
158 | case 'Z':
159 | _options.scalemode = SCALE_ZOOM;
160 | _options.zoom = 1.0;
161 | break;
162 | case 'z':
163 | n = strtol(optarg, &end, 0);
164 | if (*end != '\0' || n <= 0)
165 | error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg);
166 | _options.scalemode = SCALE_ZOOM;
167 | _options.zoom = (float) n / 100.0;
168 | break;
169 | }
170 | }
171 |
172 | _options.filenames = argv + optind;
173 | _options.filecnt = argc - optind;
174 |
175 | if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) {
176 | _options.filenames++;
177 | _options.filecnt--;
178 | _options.from_stdin = true;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/util.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | const char *progname;
29 |
30 | void* emalloc(size_t size)
31 | {
32 | void *ptr;
33 |
34 | ptr = malloc(size);
35 | if (ptr == NULL)
36 | error(EXIT_FAILURE, errno, NULL);
37 | return ptr;
38 | }
39 |
40 | void* erealloc(void *ptr, size_t size)
41 | {
42 | ptr = realloc(ptr, size);
43 | if (ptr == NULL)
44 | error(EXIT_FAILURE, errno, NULL);
45 | return ptr;
46 | }
47 |
48 | char* estrdup(const char *s)
49 | {
50 | char *d;
51 | size_t n = strlen(s) + 1;
52 |
53 | d = malloc(n);
54 | if (d == NULL)
55 | error(EXIT_FAILURE, errno, NULL);
56 | memcpy(d, s, n);
57 | return d;
58 | }
59 |
60 | void error(int eval, int err, const char* fmt, ...)
61 | {
62 | va_list ap;
63 |
64 | if (eval == 0 && options->quiet)
65 | return;
66 |
67 | fflush(stdout);
68 | fprintf(stderr, "%s: ", progname);
69 | va_start(ap, fmt);
70 | if (fmt != NULL)
71 | vfprintf(stderr, fmt, ap);
72 | va_end(ap);
73 | if (err != 0)
74 | fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err));
75 | fputc('\n', stderr);
76 |
77 | if (eval != 0)
78 | exit(eval);
79 | }
80 |
81 | void size_readable(float *size, const char **unit)
82 | {
83 | const char *units[] = { "", "K", "M", "G" };
84 | int i;
85 |
86 | for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++)
87 | *size /= 1024.0;
88 | *unit = units[MIN(i, ARRLEN(units) - 1)];
89 | }
90 |
91 | int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive)
92 | {
93 | if (*dirname == '\0')
94 | return -1;
95 |
96 | if ((rdir->dir = opendir(dirname)) == NULL) {
97 | rdir->name = NULL;
98 | rdir->stack = NULL;
99 | return -1;
100 | }
101 |
102 | rdir->stcap = 512;
103 | rdir->stack = (char**) emalloc(rdir->stcap * sizeof(char*));
104 | rdir->stlen = 0;
105 |
106 | rdir->name = (char*) dirname;
107 | rdir->d = 0;
108 | rdir->recursive = recursive;
109 |
110 | return 0;
111 | }
112 |
113 | int r_closedir(r_dir_t *rdir)
114 | {
115 | int ret = 0;
116 |
117 | if (rdir->stack != NULL) {
118 | while (rdir->stlen > 0)
119 | free(rdir->stack[--rdir->stlen]);
120 | free(rdir->stack);
121 | rdir->stack = NULL;
122 | }
123 |
124 | if (rdir->dir != NULL) {
125 | if ((ret = closedir(rdir->dir)) == 0)
126 | rdir->dir = NULL;
127 | }
128 |
129 | if (rdir->d != 0) {
130 | free(rdir->name);
131 | rdir->name = NULL;
132 | }
133 |
134 | return ret;
135 | }
136 |
137 | char* r_readdir(r_dir_t *rdir, bool skip_dotfiles)
138 | {
139 | size_t len;
140 | char *filename;
141 | struct dirent *dentry;
142 | struct stat fstats;
143 |
144 | while (true) {
145 | if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) {
146 | if (dentry->d_name[0] == '.') {
147 | if (skip_dotfiles)
148 | continue;
149 | if (dentry->d_name[1] == '\0')
150 | continue;
151 | if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0')
152 | continue;
153 | }
154 |
155 | len = strlen(rdir->name) + strlen(dentry->d_name) + 2;
156 | filename = (char*) emalloc(len);
157 | snprintf(filename, len, "%s%s%s", rdir->name,
158 | rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/",
159 | dentry->d_name);
160 |
161 | if (stat(filename, &fstats) < 0)
162 | continue;
163 | if (S_ISDIR(fstats.st_mode)) {
164 | /* put subdirectory on the stack */
165 | if (rdir->stlen == rdir->stcap) {
166 | rdir->stcap *= 2;
167 | rdir->stack = (char**) erealloc(rdir->stack,
168 | rdir->stcap * sizeof(char*));
169 | }
170 | rdir->stack[rdir->stlen++] = filename;
171 | continue;
172 | }
173 | return filename;
174 | }
175 |
176 | if (rdir->recursive && rdir->stlen > 0) {
177 | /* open next subdirectory */
178 | closedir(rdir->dir);
179 | if (rdir->d != 0)
180 | free(rdir->name);
181 | rdir->name = rdir->stack[--rdir->stlen];
182 | rdir->d = 1;
183 | if ((rdir->dir = opendir(rdir->name)) == NULL)
184 | error(0, errno, "%s", rdir->name);
185 | continue;
186 | }
187 | /* no more entries */
188 | break;
189 | }
190 | return NULL;
191 | }
192 |
193 | int r_mkdir(char *path)
194 | {
195 | char c, *s = path;
196 | struct stat st;
197 |
198 | while (*s != '\0') {
199 | if (*s == '/') {
200 | s++;
201 | continue;
202 | }
203 | for (; *s != '\0' && *s != '/'; s++);
204 | c = *s;
205 | *s = '\0';
206 | if (mkdir(path, 0755) == -1)
207 | if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode))
208 | return -1;
209 | *s = c;
210 | }
211 | return 0;
212 | }
213 |
214 |
--------------------------------------------------------------------------------
/config.def.h:
--------------------------------------------------------------------------------
1 | #ifdef _WINDOW_CONFIG
2 |
3 | /* default window dimensions (overwritten via -g option): */
4 | enum {
5 | WIN_WIDTH = 800,
6 | WIN_HEIGHT = 600
7 | };
8 |
9 | /* colors and font are configured with 'background', 'foreground' and
10 | * 'font' X resource properties.
11 | * See X(7) section Resources and xrdb(1) for more information.
12 | */
13 |
14 | #endif
15 | #ifdef _IMAGE_CONFIG
16 |
17 | /* levels (in percent) to use when zooming via '-' and '+':
18 | * (first/last value is used as min/max zoom level)
19 | */
20 | static const float zoom_levels[] = {
21 | 12.5, 25.0, 50.0, 75.0,
22 | 100.0, 150.0, 200.0, 400.0, 800.0
23 | };
24 |
25 | /* default slideshow delay (in sec, overwritten via -S option): */
26 | enum { SLIDESHOW_DELAY = 5 };
27 |
28 | /* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and
29 | * (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX].
30 | * */
31 | static const double GAMMA_MAX = 10.0;
32 | static const int GAMMA_RANGE = 32;
33 |
34 | /* command i_scroll pans image 1/PAN_FRACTION of screen width/height */
35 | static const int PAN_FRACTION = 5;
36 |
37 | /* if false, pixelate images at zoom level != 100%,
38 | * toggled with 'a' key binding
39 | */
40 | static const bool ANTI_ALIAS = true;
41 |
42 | /* if true, use a checkerboard background for alpha layer,
43 | * toggled with 'A' key binding
44 | */
45 | static const bool ALPHA_LAYER = false;
46 |
47 | #endif
48 | #ifdef _THUMBS_CONFIG
49 |
50 | /* thumbnail sizes in pixels (width == height): */
51 | static const int thumb_sizes[] = { 32, 64, 96, 128, 160 };
52 |
53 | /* thumbnail size at startup, index into thumb_sizes[]: */
54 | static const int THUMB_SIZE = 3;
55 |
56 | #endif
57 | #ifdef _MAPPINGS_CONFIG
58 |
59 | /* keyboard mappings for image and thumbnail mode: */
60 | static const keymap_t keys[] = {
61 | /* modifiers key function argument */
62 | { 0, XK_q, g_quit, None },
63 | { 0, XK_Return, g_switch_mode, None },
64 | { 0, XK_f, g_toggle_fullscreen, None },
65 | { 0, XK_b, g_toggle_bar, None },
66 | { ControlMask, XK_x, g_prefix_external, None },
67 | { 0, XK_g, g_first, None },
68 | { 0, XK_G, g_n_or_last, None },
69 | { 0, XK_r, g_reload_image, None },
70 | { 0, XK_D, g_remove_image, None },
71 | { ControlMask, XK_h, g_scroll_screen, DIR_LEFT },
72 | { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT },
73 | { ControlMask, XK_j, g_scroll_screen, DIR_DOWN },
74 | { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN },
75 | { ControlMask, XK_k, g_scroll_screen, DIR_UP },
76 | { ControlMask, XK_Up, g_scroll_screen, DIR_UP },
77 | { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT },
78 | { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT },
79 | { 0, XK_plus, g_zoom, +1 },
80 | { 0, XK_KP_Add, g_zoom, +1 },
81 | { 0, XK_minus, g_zoom, -1 },
82 | { 0, XK_KP_Subtract, g_zoom, -1 },
83 | { 0, XK_m, g_toggle_image_mark, None },
84 | { 0, XK_M, g_mark_range, None },
85 | { ControlMask, XK_m, g_reverse_marks, None },
86 | { ControlMask, XK_u, g_unmark_all, None },
87 | { 0, XK_N, g_navigate_marked, +1 },
88 | { 0, XK_P, g_navigate_marked, -1 },
89 | { 0, XK_braceleft, g_change_gamma, -1 },
90 | { 0, XK_braceright, g_change_gamma, +1 },
91 | { ControlMask, XK_g, g_change_gamma, 0 },
92 |
93 | { 0, XK_h, t_move_sel, DIR_LEFT },
94 | { 0, XK_Left, t_move_sel, DIR_LEFT },
95 | { 0, XK_j, t_move_sel, DIR_DOWN },
96 | { 0, XK_Down, t_move_sel, DIR_DOWN },
97 | { 0, XK_k, t_move_sel, DIR_UP },
98 | { 0, XK_Up, t_move_sel, DIR_UP },
99 | { 0, XK_l, t_move_sel, DIR_RIGHT },
100 | { 0, XK_Right, t_move_sel, DIR_RIGHT },
101 | { 0, XK_R, t_reload_all, None },
102 |
103 | { 0, XK_n, i_navigate, +1 },
104 | { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP },
105 | { 0, XK_space, i_navigate, +1 },
106 | { 0, XK_p, i_navigate, -1 },
107 | { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP },
108 | { 0, XK_BackSpace, i_navigate, -1 },
109 | { 0, XK_bracketright, i_navigate, +10 },
110 | { 0, XK_bracketleft, i_navigate, -10 },
111 | { ControlMask, XK_6, i_alternate, None },
112 | { ControlMask, XK_n, i_navigate_frame, +1 },
113 | { ControlMask, XK_p, i_navigate_frame, -1 },
114 | { ControlMask, XK_space, i_toggle_animation, None },
115 | { 0, XK_h, i_scroll, DIR_LEFT },
116 | { 0, XK_Left, i_scroll, DIR_LEFT },
117 | { 0, XK_j, i_scroll, DIR_DOWN },
118 | { 0, XK_Down, i_scroll, DIR_DOWN },
119 | { 0, XK_k, i_scroll, DIR_UP },
120 | { 0, XK_Up, i_scroll, DIR_UP },
121 | { 0, XK_l, i_scroll, DIR_RIGHT },
122 | { 0, XK_Right, i_scroll, DIR_RIGHT },
123 | { 0, XK_H, i_scroll_to_edge, DIR_LEFT },
124 | { 0, XK_J, i_scroll_to_edge, DIR_DOWN },
125 | { 0, XK_K, i_scroll_to_edge, DIR_UP },
126 | { 0, XK_L, i_scroll_to_edge, DIR_RIGHT },
127 | { 0, XK_equal, i_set_zoom, 100 },
128 | { 0, XK_w, i_fit_to_win, SCALE_DOWN },
129 | { 0, XK_W, i_fit_to_win, SCALE_FIT },
130 | { 0, XK_e, i_fit_to_win, SCALE_WIDTH },
131 | { 0, XK_E, i_fit_to_win, SCALE_HEIGHT },
132 | { 0, XK_less, i_rotate, DEGREE_270 },
133 | { 0, XK_greater, i_rotate, DEGREE_90 },
134 | { 0, XK_question, i_rotate, DEGREE_180 },
135 | { 0, XK_bar, i_flip, FLIP_HORIZONTAL },
136 | { 0, XK_underscore, i_flip, FLIP_VERTICAL },
137 | { 0, XK_a, i_toggle_antialias, None },
138 | { 0, XK_A, i_toggle_alpha, None },
139 | { 0, XK_s, i_slideshow, None },
140 | };
141 |
142 | /* mouse button mappings for image mode: */
143 | static const button_t buttons[] = {
144 | /* modifiers button function argument */
145 | { 0, 1, i_cursor_navigate, None },
146 | { 0, 2, i_drag, DRAG_ABSOLUTE },
147 | { 0, 3, g_switch_mode, None },
148 | { 0, 4, g_zoom, +1 },
149 | { 0, 5, g_zoom, -1 },
150 | };
151 |
152 | #endif
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | **Simple X Image Viewer**
4 |
5 | The sole purpose of sxiv is to be the perfect image viewer for me. It is free
6 | software so that you can use it and modify it for your needs. Please file a bug
7 | report if something does not work as documented or expected. Contributions are
8 | welcome but there is no guarantee that they will be incorporated.
9 |
10 |
11 | Features
12 | --------
13 |
14 | * Basic image operations, e.g. zooming, panning, rotating
15 | * Customizable key and mouse button mappings (in *config.h*)
16 | * Thumbnail mode: grid of selectable previews of all images
17 | * Ability to cache thumbnails for fast re-loading
18 | * Basic support for multi-frame images
19 | * Load all frames from GIF files and play GIF animations
20 | * Display image information in status bar
21 |
22 |
23 | Screenshots
24 | -----------
25 |
26 | **Image mode:**
27 |
28 | 
29 |
30 | **Thumbnail mode:**
31 |
32 | 
33 |
34 |
35 | Dependencies
36 | ------------
37 |
38 | sxiv requires the following software to be installed:
39 |
40 | * Imlib2
41 | * X11
42 | * Xft
43 | * freetype2
44 | * fontconfig
45 | * giflib (optional, disabled with `HAVE_GIFLIB=0`)
46 | * libexif (optional, disabled with `HAVE_LIBEXIF=0`)
47 |
48 | Please make sure to install the corresponding development packages in case that
49 | you want to build sxiv on a distribution with separate runtime and development
50 | packages (e.g. *-dev on Debian).
51 |
52 |
53 | Building
54 | --------
55 |
56 | sxiv is built using the commands:
57 |
58 | $ make
59 | # make install
60 |
61 | Please note, that the latter one requires root privileges.
62 | By default, sxiv is installed using the prefix "/usr/local", so the full path
63 | of the executable will be "/usr/local/bin/sxiv".
64 |
65 | You can install sxiv into a directory of your choice by changing the second
66 | command to:
67 |
68 | # make PREFIX="/your/dir" install
69 |
70 | The build-time specific settings of sxiv can be found in the file *config.h*.
71 | Please check and change them, so that they fit your needs.
72 | If the file *config.h* does not already exist, then you have to create it with
73 | the following command:
74 |
75 | $ make config.h
76 |
77 |
78 | Usage
79 | -----
80 |
81 | Please see the [man page](http://xyb3rt.github.io/sxiv/sxiv.1.html) for
82 | information on how to use sxiv.
83 |
84 |
85 | Download & Changelog
86 | --------------------
87 |
88 | You can [browse](https://github.com/xyb3rt/sxiv) the source code repository
89 | on GitHub or get a copy using git with the following command:
90 |
91 | git clone https://github.com/xyb3rt/sxiv.git
92 |
93 | **Stable releases**
94 |
95 | **[v26](https://github.com/xyb3rt/sxiv/archive/v26.tar.gz)**
96 | *(January 16, 2020)*
97 |
98 | * Maintenance release
99 |
100 | **[v25](https://github.com/xyb3rt/sxiv/archive/v25.tar.gz)**
101 | *(January 26, 2019)*
102 |
103 | * Support font fallback for missing glyphs
104 | * Fix busy loop when built without inotify
105 | * Use background/foreground colors from X resource database
106 |
107 | **[v24](https://github.com/xyb3rt/sxiv/archive/v24.tar.gz)**
108 | *(October 27, 2017)*
109 |
110 | * Automatically reload the current image whenever it changes
111 | * Support embedding into other X windows with -e (e.g. tabbed)
112 | * New option -p prevents sxiv from creating cache and temporary files
113 | * Simpler mouse mappings, the most basic features are accessible with the
114 | mouse only (navigate, zoom, pan)
115 |
116 | **[v1.3.2](https://github.com/xyb3rt/sxiv/archive/v1.3.2.tar.gz)**
117 | *(December 20, 2015)*
118 |
119 | * external key handler gets file paths on stdin, not as arguments
120 | * Cache out-of-view thumbnails in the background
121 | * Apply gamma correction to thumbnails
122 |
123 | **[v1.3.1](https://github.com/xyb3rt/sxiv/archive/v1.3.1.tar.gz)**
124 | *(November 16, 2014)*
125 |
126 | * Fixed build error, caused by delayed config.h creation
127 | * Fixed segfault when run with -c
128 |
129 | **[v1.3](https://github.com/xyb3rt/sxiv/archive/v1.3.tar.gz)**
130 | *(October 24, 2014)*
131 |
132 | * Extract thumbnails from EXIF tags (requires libexif)
133 | * Zoomable thumbnails, supported sizes defined in config.h
134 | * Fixed build error with giflib version >= 5.1.0
135 |
136 | **[v1.2](https://github.com/xyb3rt/sxiv/archive/v1.2.tar.gz)**
137 | *(April 24, 2014)*
138 |
139 | * Added external key handler, called on keys prefixed with `Ctrl-x`
140 | * New keybinding `{`/`}` to change gamma (by András Mohari)
141 | * Support for slideshows, enabled with `-S` option & toggled with `s`
142 | * Added application icon (created by 0ion9)
143 | * Checkerboard background for alpha layer
144 | * Option `-o` only prints files marked with `m` key
145 | * Fixed rotation/flipping of multi-frame images (gifs)
146 |
147 | **[v1.1.1](https://github.com/xyb3rt/sxiv/archive/v1.1.1.tar.gz)**
148 | *(June 2, 2013)*
149 |
150 | * Various bug fixes
151 |
152 | **[v1.1](https://github.com/xyb3rt/sxiv/archive/v1.1.tar.gz)**
153 | *(March 30, 2013)*
154 |
155 | * Added status bar on bottom of window with customizable content
156 | * New keyboard shortcuts `\`/`|`: flip image vertically/horizontally
157 | * New keyboard shortcut `Ctrl-6`: go to last/alternate image
158 | * Added own EXIF orientation handling, removed dependency on libexif
159 | * Fixed various bugs
160 |
161 | **[v1.0](https://github.com/xyb3rt/sxiv/archive/v1.0.tar.gz)**
162 | *(October 31, 2011)*
163 |
164 | * Support for multi-frame images & GIF animations
165 | * POSIX compliant (IEEE Std 1003.1-2001)
166 |
167 | **[v0.9](https://github.com/xyb3rt/sxiv/archive/v0.9.tar.gz)**
168 | *(August 17, 2011)*
169 |
170 | * Made key and mouse mappings fully configurable in config.h
171 | * Complete code refactoring
172 |
173 | **[v0.8.2](https://github.com/xyb3rt/sxiv/archive/v0.8.2.tar.gz)**
174 | *(June 29, 2011)*
175 |
176 | * POSIX-compliant Makefile; compiles under NetBSD
177 |
178 | **[v0.8.1](https://github.com/xyb3rt/sxiv/archive/v0.8.1.tar.gz)**
179 | *(May 8, 2011)*
180 |
181 | * Fixed fullscreen under window managers, which are not fully EWMH-compliant
182 |
183 | **[v0.8](https://github.com/xyb3rt/sxiv/archive/v0.8.tar.gz)**
184 | *(April 18, 2011)*
185 |
186 | * Support for thumbnail caching
187 | * Ability to run external commands (e.g. jpegtran, convert) on current image
188 |
189 | **[v0.7](https://github.com/xyb3rt/sxiv/archive/v0.7.tar.gz)**
190 | *(February 26, 2011)*
191 |
192 | * Sort directory entries when using `-r` command line option
193 | * Hide cursor in image mode
194 | * Full functional thumbnail mode, use Return key to switch between image and
195 | thumbnail mode
196 |
197 | **[v0.6](https://github.com/xyb3rt/sxiv/archive/v0.6.tar.gz)**
198 | *(February 16, 2011)*
199 |
200 | * Bug fix: Correctly display filenames with umlauts in window title
201 | * Basic support of thumbnails
202 |
203 | **[v0.5](https://github.com/xyb3rt/sxiv/archive/v0.5.tar.gz)**
204 | *(February 6, 2011)*
205 |
206 | * New command line option: `-r`: open all images in given directories
207 | * New key shortcuts: `w`: resize image to fit into window; `W`: resize window
208 | to fit to image
209 |
210 | **[v0.4](https://github.com/xyb3rt/sxiv/archive/v0.4.tar.gz)**
211 | *(February 1, 2011)*
212 |
213 | * New command line option: `-F`, `-g`: use fixed window dimensions and apply
214 | a given window geometry
215 | * New key shortcut: `r`: reload current image
216 |
217 | **[v0.3.1](https://github.com/xyb3rt/sxiv/archive/v0.3.1.tar.gz)**
218 | *(January 30, 2011)*
219 |
220 | * Bug fix: Do not set setuid bit on executable when using `make install`
221 | * Pan image with mouse while pressing middle mouse button
222 |
223 | **[v0.3](https://github.com/xyb3rt/sxiv/archive/v0.3.tar.gz)**
224 | *(January 29, 2011)*
225 |
226 | * New command line options: `-d`, `-f`, `-p`, `-s`, `-v`, `-w`, `-Z`, `-z`
227 | * More mouse mappings: Go to next/previous image with left/right click,
228 | scroll image with mouse wheel (horizontally if Shift key is pressed),
229 | zoom image with mouse wheel if Ctrl key is pressed
230 |
231 | **[v0.2](https://github.com/xyb3rt/sxiv/archive/v0.2.tar.gz)**
232 | *(January 23, 2011)*
233 |
234 | * Bug fix: Handle window resizes correctly
235 | * New keyboard shortcuts: `g`/`G`: go to first/last image; `[`/`]`: go 10
236 | images back/forward
237 | * Support for mouse wheel zooming (by Dave Reisner)
238 | * Added fullscreen mode
239 |
240 | **[v0.1](https://github.com/xyb3rt/sxiv/archive/v0.1.tar.gz)**
241 | *(January 21, 2011)*
242 |
243 | * Initial release
244 |
245 |
--------------------------------------------------------------------------------
/sxiv.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2011 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #ifndef SXIV_H
20 | #define SXIV_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | /*
31 | * Annotation for functions called in cleanup().
32 | * These functions are not allowed to call error(!0, ...) or exit().
33 | */
34 | #define CLEANUP
35 |
36 | #ifndef MIN
37 | #define MIN(a,b) ((a) < (b) ? (a) : (b))
38 | #endif
39 | #ifndef MAX
40 | #define MAX(a,b) ((a) > (b) ? (a) : (b))
41 | #endif
42 |
43 | #define ARRLEN(a) (sizeof(a) / sizeof((a)[0]))
44 |
45 | #define STREQ(s1,s2) (strcmp((s1), (s2)) == 0)
46 |
47 | #define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \
48 | ((t1)->tv_usec - (t2)->tv_usec) / 1000)
49 |
50 | #define TV_SET_MSEC(tv,t) { \
51 | (tv)->tv_sec = (t) / 1000; \
52 | (tv)->tv_usec = (t) % 1000 * 1000; \
53 | }
54 |
55 | #define TV_ADD_MSEC(tv,t) { \
56 | (tv)->tv_sec += (t) / 1000; \
57 | (tv)->tv_usec += (t) % 1000 * 1000; \
58 | }
59 |
60 | typedef enum {
61 | BO_BIG_ENDIAN,
62 | BO_LITTLE_ENDIAN
63 | } byteorder_t;
64 |
65 | typedef enum {
66 | MODE_IMAGE,
67 | MODE_THUMB
68 | } appmode_t;
69 |
70 | typedef enum {
71 | DIR_LEFT = 1,
72 | DIR_RIGHT = 2,
73 | DIR_UP = 4,
74 | DIR_DOWN = 8
75 | } direction_t;
76 |
77 | typedef enum {
78 | DEGREE_90 = 1,
79 | DEGREE_180 = 2,
80 | DEGREE_270 = 3
81 | } degree_t;
82 |
83 | typedef enum {
84 | FLIP_HORIZONTAL = 1,
85 | FLIP_VERTICAL = 2
86 | } flipdir_t;
87 |
88 | typedef enum {
89 | SCALE_DOWN,
90 | SCALE_FIT,
91 | SCALE_WIDTH,
92 | SCALE_HEIGHT,
93 | SCALE_ZOOM
94 | } scalemode_t;
95 |
96 | typedef enum {
97 | DRAG_RELATIVE,
98 | DRAG_ABSOLUTE
99 | } dragmode_t;
100 |
101 | typedef enum {
102 | CURSOR_ARROW,
103 | CURSOR_DRAG,
104 | CURSOR_WATCH,
105 | CURSOR_LEFT,
106 | CURSOR_RIGHT,
107 | CURSOR_NONE,
108 |
109 | CURSOR_COUNT
110 | } cursor_t;
111 |
112 | typedef enum {
113 | FF_WARN = 1,
114 | FF_MARK = 2,
115 | FF_TN_INIT = 4
116 | } fileflags_t;
117 |
118 | typedef struct {
119 | const char *name; /* as given by user */
120 | const char *path; /* always absolute */
121 | fileflags_t flags;
122 | } fileinfo_t;
123 |
124 | /* timeouts in milliseconds: */
125 | enum {
126 | TO_REDRAW_RESIZE = 75,
127 | TO_REDRAW_THUMBS = 200,
128 | TO_CURSOR_HIDE = 1200,
129 | TO_DOUBLE_CLICK = 300
130 | };
131 |
132 | typedef void (*timeout_f)(void);
133 |
134 | typedef struct arl arl_t;
135 | typedef struct img img_t;
136 | typedef struct opt opt_t;
137 | typedef struct tns tns_t;
138 | typedef struct win win_t;
139 |
140 |
141 | /* autoreload.c */
142 |
143 | struct arl {
144 | int fd;
145 | int wd_dir;
146 | int wd_file;
147 | char *filename;
148 | };
149 |
150 | void arl_init(arl_t*);
151 | void arl_cleanup(arl_t*);
152 | void arl_setup(arl_t*, const char* /* result of realpath(3) */);
153 | bool arl_handle(arl_t*);
154 |
155 |
156 | /* commands.c */
157 |
158 | typedef int arg_t;
159 | typedef bool (*cmd_f)(arg_t);
160 |
161 | #define G_CMD(c) g_##c,
162 | #define I_CMD(c) i_##c,
163 | #define T_CMD(c) t_##c,
164 |
165 | typedef enum {
166 | #include "commands.lst"
167 | CMD_COUNT
168 | } cmd_id_t;
169 |
170 | typedef struct {
171 | int mode;
172 | cmd_f func;
173 | } cmd_t;
174 |
175 | typedef struct {
176 | unsigned int mask;
177 | KeySym ksym;
178 | cmd_id_t cmd;
179 | arg_t arg;
180 | } keymap_t;
181 |
182 | typedef struct {
183 | unsigned int mask;
184 | unsigned int button;
185 | cmd_id_t cmd;
186 | arg_t arg;
187 | } button_t;
188 |
189 | extern const cmd_t cmds[CMD_COUNT];
190 |
191 |
192 | /* image.c */
193 |
194 | typedef struct {
195 | Imlib_Image im;
196 | unsigned int delay;
197 | } img_frame_t;
198 |
199 | typedef struct {
200 | img_frame_t *frames;
201 | int cap;
202 | int cnt;
203 | int sel;
204 | bool animate;
205 | int framedelay;
206 | int length;
207 | } multi_img_t;
208 |
209 | struct img {
210 | Imlib_Image im;
211 | int w;
212 | int h;
213 |
214 | win_t *win;
215 | float x;
216 | float y;
217 |
218 | scalemode_t scalemode;
219 | float zoom;
220 |
221 | bool checkpan;
222 | bool dirty;
223 | bool aa;
224 | bool alpha;
225 |
226 | Imlib_Color_Modifier cmod;
227 | int gamma;
228 |
229 | struct {
230 | bool on;
231 | int delay;
232 | } ss;
233 |
234 | multi_img_t multi;
235 | };
236 |
237 | void img_init(img_t*, win_t*);
238 | bool img_load(img_t*, const fileinfo_t*);
239 | CLEANUP void img_close(img_t*, bool);
240 | void img_render(img_t*);
241 | bool img_fit_win(img_t*, scalemode_t);
242 | bool img_zoom(img_t*, float);
243 | bool img_zoom_in(img_t*);
244 | bool img_zoom_out(img_t*);
245 | bool img_pos(img_t*, float, float);
246 | bool img_move(img_t*, float, float);
247 | bool img_pan(img_t*, direction_t, int);
248 | bool img_pan_edge(img_t*, direction_t);
249 | void img_rotate(img_t*, degree_t);
250 | void img_flip(img_t*, flipdir_t);
251 | void img_toggle_antialias(img_t*);
252 | bool img_change_gamma(img_t*, int);
253 | bool img_frame_navigate(img_t*, int);
254 | bool img_frame_animate(img_t*);
255 |
256 |
257 | /* options.c */
258 |
259 | struct opt {
260 | /* file list: */
261 | char **filenames;
262 | bool from_stdin;
263 | bool to_stdout;
264 | bool recursive;
265 | int filecnt;
266 | int startnum;
267 |
268 | /* image: */
269 | scalemode_t scalemode;
270 | float zoom;
271 | bool animate;
272 | int gamma;
273 | int slideshow;
274 | int framerate;
275 |
276 | /* window: */
277 | bool fullscreen;
278 | bool hide_bar;
279 | long embed;
280 | char *geometry;
281 | char *res_name;
282 |
283 | /* misc flags: */
284 | bool quiet;
285 | bool thumb_mode;
286 | bool clean_cache;
287 | bool private_mode;
288 | };
289 |
290 | extern const opt_t *options;
291 |
292 | void print_usage(void);
293 | void print_version(void);
294 | void parse_options(int, char**);
295 |
296 |
297 | /* thumbs.c */
298 |
299 | typedef struct {
300 | Imlib_Image im;
301 | int w;
302 | int h;
303 | int x;
304 | int y;
305 | } thumb_t;
306 |
307 | struct tns {
308 | fileinfo_t *files;
309 | thumb_t *thumbs;
310 | const int *cnt;
311 | int *sel;
312 | int initnext;
313 | int loadnext;
314 | int first, end;
315 | int r_first, r_end;
316 |
317 | win_t *win;
318 | int x;
319 | int y;
320 | int cols;
321 | int rows;
322 | int zl;
323 | int bw;
324 | int dim;
325 |
326 | bool dirty;
327 | };
328 |
329 | void tns_clean_cache(tns_t*);
330 | void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*);
331 | CLEANUP void tns_free(tns_t*);
332 | bool tns_load(tns_t*, int, bool, bool);
333 | void tns_unload(tns_t*, int);
334 | void tns_render(tns_t*);
335 | void tns_mark(tns_t*, int, bool);
336 | void tns_highlight(tns_t*, int, bool);
337 | bool tns_move_selection(tns_t*, direction_t, int);
338 | bool tns_scroll(tns_t*, direction_t, bool);
339 | bool tns_zoom(tns_t*, int);
340 | int tns_translate(tns_t*, int, int);
341 |
342 |
343 | /* util.c */
344 |
345 | #include
346 |
347 | typedef struct {
348 | DIR *dir;
349 | char *name;
350 | int d;
351 | bool recursive;
352 |
353 | char **stack;
354 | int stcap;
355 | int stlen;
356 | } r_dir_t;
357 |
358 | extern const char *progname;
359 |
360 | void* emalloc(size_t);
361 | void* erealloc(void*, size_t);
362 | char* estrdup(const char*);
363 | void error(int, int, const char*, ...);
364 | void size_readable(float*, const char**);
365 | int r_opendir(r_dir_t*, const char*, bool);
366 | int r_closedir(r_dir_t*);
367 | char* r_readdir(r_dir_t*, bool);
368 | int r_mkdir(char*);
369 |
370 |
371 | /* window.c */
372 |
373 | #include
374 | #include
375 |
376 | enum {
377 | BAR_L_LEN = 512,
378 | BAR_R_LEN = 64
379 | };
380 |
381 | enum {
382 | ATOM_WM_DELETE_WINDOW,
383 | ATOM__NET_WM_NAME,
384 | ATOM__NET_WM_ICON_NAME,
385 | ATOM__NET_WM_ICON,
386 | ATOM__NET_WM_STATE,
387 | ATOM__NET_WM_STATE_FULLSCREEN,
388 | ATOM_COUNT
389 | };
390 |
391 | typedef struct {
392 | Display *dpy;
393 | int scr;
394 | int scrw, scrh;
395 | Visual *vis;
396 | Colormap cmap;
397 | int depth;
398 | } win_env_t;
399 |
400 | typedef struct {
401 | size_t size;
402 | char *p;
403 | char *buf;
404 | } win_bar_t;
405 |
406 | struct win {
407 | Window xwin;
408 | win_env_t env;
409 |
410 | XftColor bg;
411 | XftColor fg;
412 |
413 | int x;
414 | int y;
415 | unsigned int w;
416 | unsigned int h; /* = win height - bar height */
417 | unsigned int bw;
418 |
419 | struct {
420 | int w;
421 | int h;
422 | Pixmap pm;
423 | } buf;
424 |
425 | struct {
426 | unsigned int h;
427 | win_bar_t l;
428 | win_bar_t r;
429 | } bar;
430 | };
431 |
432 | extern Atom atoms[ATOM_COUNT];
433 |
434 | void win_init(win_t*);
435 | void win_open(win_t*);
436 | CLEANUP void win_close(win_t*);
437 | bool win_configure(win_t*, XConfigureEvent*);
438 | void win_toggle_fullscreen(win_t*);
439 | void win_toggle_bar(win_t*);
440 | void win_clear(win_t*);
441 | void win_draw(win_t*);
442 | void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long);
443 | void win_set_title(win_t*, const char*);
444 | void win_set_cursor(win_t*, cursor_t);
445 | void win_cursor_pos(win_t*, int*, int*);
446 |
447 | #endif /* SXIV_H */
448 |
449 |
--------------------------------------------------------------------------------
/commands.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011, 2012, 2014 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _IMAGE_CONFIG
21 | #include "config.h"
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | void remove_file(int, bool);
29 | void load_image(int);
30 | bool mark_image(int, bool);
31 | void close_info(void);
32 | void open_info(void);
33 | int ptr_third_x(void);
34 | void redraw(void);
35 | void reset_cursor(void);
36 | void animate(void);
37 | void slideshow(void);
38 | void set_timeout(timeout_f, int, bool);
39 | void reset_timeout(timeout_f);
40 |
41 | extern appmode_t mode;
42 | extern img_t img;
43 | extern tns_t tns;
44 | extern win_t win;
45 |
46 | extern fileinfo_t *files;
47 | extern int filecnt, fileidx;
48 | extern int alternate;
49 | extern int markcnt;
50 | extern int markidx;
51 |
52 | extern int prefix;
53 | extern bool extprefix;
54 |
55 | bool cg_quit(arg_t _)
56 | {
57 | unsigned int i;
58 |
59 | if (options->to_stdout && markcnt > 0) {
60 | for (i = 0; i < filecnt; i++) {
61 | if (files[i].flags & FF_MARK)
62 | printf("%s\n", files[i].name);
63 | }
64 | }
65 | exit(EXIT_SUCCESS);
66 | }
67 |
68 | bool cg_switch_mode(arg_t _)
69 | {
70 | if (mode == MODE_IMAGE) {
71 | if (tns.thumbs == NULL)
72 | tns_init(&tns, files, &filecnt, &fileidx, &win);
73 | img_close(&img, false);
74 | reset_timeout(reset_cursor);
75 | if (img.ss.on) {
76 | img.ss.on = false;
77 | reset_timeout(slideshow);
78 | }
79 | tns.dirty = true;
80 | mode = MODE_THUMB;
81 | } else {
82 | load_image(fileidx);
83 | mode = MODE_IMAGE;
84 | }
85 | return true;
86 | }
87 |
88 | bool cg_toggle_fullscreen(arg_t _)
89 | {
90 | win_toggle_fullscreen(&win);
91 | /* redraw after next ConfigureNotify event */
92 | set_timeout(redraw, TO_REDRAW_RESIZE, false);
93 | if (mode == MODE_IMAGE)
94 | img.checkpan = img.dirty = true;
95 | else
96 | tns.dirty = true;
97 | return false;
98 | }
99 |
100 | bool cg_toggle_bar(arg_t _)
101 | {
102 | win_toggle_bar(&win);
103 | if (mode == MODE_IMAGE) {
104 | if (win.bar.h > 0)
105 | open_info();
106 | else
107 | close_info();
108 | img.checkpan = img.dirty = true;
109 | } else {
110 | tns.dirty = true;
111 | }
112 | return true;
113 | }
114 |
115 | bool cg_prefix_external(arg_t _)
116 | {
117 | extprefix = true;
118 | return false;
119 | }
120 |
121 | bool cg_reload_image(arg_t _)
122 | {
123 | if (mode == MODE_IMAGE) {
124 | load_image(fileidx);
125 | } else {
126 | win_set_cursor(&win, CURSOR_WATCH);
127 | if (!tns_load(&tns, fileidx, true, false)) {
128 | remove_file(fileidx, false);
129 | tns.dirty = true;
130 | }
131 | }
132 | return true;
133 | }
134 |
135 | bool cg_remove_image(arg_t _)
136 | {
137 | remove_file(fileidx, true);
138 | if (mode == MODE_IMAGE)
139 | load_image(fileidx);
140 | else
141 | tns.dirty = true;
142 | return true;
143 | }
144 |
145 | bool cg_first(arg_t _)
146 | {
147 | if (mode == MODE_IMAGE && fileidx != 0) {
148 | load_image(0);
149 | return true;
150 | } else if (mode == MODE_THUMB && fileidx != 0) {
151 | fileidx = 0;
152 | tns.dirty = true;
153 | return true;
154 | } else {
155 | return false;
156 | }
157 | }
158 |
159 | bool cg_n_or_last(arg_t _)
160 | {
161 | int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1;
162 |
163 | if (mode == MODE_IMAGE && fileidx != n) {
164 | load_image(n);
165 | return true;
166 | } else if (mode == MODE_THUMB && fileidx != n) {
167 | fileidx = n;
168 | tns.dirty = true;
169 | return true;
170 | } else {
171 | return false;
172 | }
173 | }
174 |
175 | bool cg_scroll_screen(arg_t dir)
176 | {
177 | if (mode == MODE_IMAGE)
178 | return img_pan(&img, dir, -1);
179 | else
180 | return tns_scroll(&tns, dir, true);
181 | }
182 |
183 | bool cg_zoom(arg_t d)
184 | {
185 | if (mode == MODE_THUMB)
186 | return tns_zoom(&tns, d);
187 | else if (d > 0)
188 | return img_zoom_in(&img);
189 | else if (d < 0)
190 | return img_zoom_out(&img);
191 | else
192 | return false;
193 | }
194 |
195 | bool cg_toggle_image_mark(arg_t _)
196 | {
197 | return mark_image(fileidx, !(files[fileidx].flags & FF_MARK));
198 | }
199 |
200 | bool cg_reverse_marks(arg_t _)
201 | {
202 | int i;
203 |
204 | for (i = 0; i < filecnt; i++) {
205 | files[i].flags ^= FF_MARK;
206 | markcnt += files[i].flags & FF_MARK ? 1 : -1;
207 | }
208 | if (mode == MODE_THUMB)
209 | tns.dirty = true;
210 | return true;
211 | }
212 |
213 | bool cg_mark_range(arg_t _)
214 | {
215 | int d = markidx < fileidx ? 1 : -1, end, i;
216 | bool dirty = false, on = !!(files[markidx].flags & FF_MARK);
217 |
218 | for (i = markidx + d, end = fileidx + d; i != end; i += d)
219 | dirty |= mark_image(i, on);
220 | return dirty;
221 | }
222 |
223 | bool cg_unmark_all(arg_t _)
224 | {
225 | int i;
226 |
227 | for (i = 0; i < filecnt; i++)
228 | files[i].flags &= ~FF_MARK;
229 | markcnt = 0;
230 | if (mode == MODE_THUMB)
231 | tns.dirty = true;
232 | return true;
233 | }
234 |
235 | bool cg_navigate_marked(arg_t n)
236 | {
237 | int d, i;
238 | int new = fileidx;
239 |
240 | if (prefix > 0)
241 | n *= prefix;
242 | d = n > 0 ? 1 : -1;
243 | for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) {
244 | if (files[i].flags & FF_MARK) {
245 | n -= d;
246 | new = i;
247 | }
248 | }
249 | if (new != fileidx) {
250 | if (mode == MODE_IMAGE) {
251 | load_image(new);
252 | } else {
253 | fileidx = new;
254 | tns.dirty = true;
255 | }
256 | return true;
257 | } else {
258 | return false;
259 | }
260 | }
261 |
262 | bool cg_change_gamma(arg_t d)
263 | {
264 | if (img_change_gamma(&img, d * (prefix > 0 ? prefix : 1))) {
265 | if (mode == MODE_THUMB)
266 | tns.dirty = true;
267 | return true;
268 | } else {
269 | return false;
270 | }
271 | }
272 |
273 | bool ci_navigate(arg_t n)
274 | {
275 | if (prefix > 0)
276 | n *= prefix;
277 | n += fileidx;
278 | if (n < 0)
279 | n = 0;
280 | if (n >= filecnt)
281 | n = filecnt - 1;
282 |
283 | if (n != fileidx) {
284 | load_image(n);
285 | return true;
286 | } else {
287 | return false;
288 | }
289 | }
290 |
291 | bool ci_cursor_navigate(arg_t _)
292 | {
293 | return ci_navigate(ptr_third_x() - 1);
294 | }
295 |
296 | bool ci_alternate(arg_t _)
297 | {
298 | load_image(alternate);
299 | return true;
300 | }
301 |
302 | bool ci_navigate_frame(arg_t d)
303 | {
304 | if (prefix > 0)
305 | d *= prefix;
306 | return !img.multi.animate && img_frame_navigate(&img, d);
307 | }
308 |
309 | bool ci_toggle_animation(arg_t _)
310 | {
311 | bool dirty = false;
312 |
313 | if (img.multi.cnt > 0) {
314 | img.multi.animate = !img.multi.animate;
315 | if (img.multi.animate) {
316 | dirty = img_frame_animate(&img);
317 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
318 | } else {
319 | reset_timeout(animate);
320 | }
321 | }
322 | return dirty;
323 | }
324 |
325 | bool ci_scroll(arg_t dir)
326 | {
327 | return img_pan(&img, dir, prefix);
328 | }
329 |
330 | bool ci_scroll_to_edge(arg_t dir)
331 | {
332 | return img_pan_edge(&img, dir);
333 | }
334 |
335 | bool ci_drag(arg_t mode)
336 | {
337 | int x, y, ox, oy;
338 | float px, py;
339 | XEvent e;
340 |
341 | if ((int)(img.w * img.zoom) <= win.w && (int)(img.h * img.zoom) <= win.h)
342 | return false;
343 |
344 | win_set_cursor(&win, CURSOR_DRAG);
345 |
346 | win_cursor_pos(&win, &x, &y);
347 | ox = x;
348 | oy = y;
349 |
350 | for (;;) {
351 | if (mode == DRAG_ABSOLUTE) {
352 | px = MIN(MAX(0.0, x - win.w*0.1), win.w*0.8) / (win.w*0.8)
353 | * (win.w - img.w * img.zoom);
354 | py = MIN(MAX(0.0, y - win.h*0.1), win.h*0.8) / (win.h*0.8)
355 | * (win.h - img.h * img.zoom);
356 | } else {
357 | px = img.x + x - ox;
358 | py = img.y + y - oy;
359 | }
360 |
361 | if (img_pos(&img, px, py)) {
362 | img_render(&img);
363 | win_draw(&win);
364 | }
365 | XMaskEvent(win.env.dpy,
366 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
367 | if (e.type == ButtonPress || e.type == ButtonRelease)
368 | break;
369 | while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
370 | ox = x;
371 | oy = y;
372 | x = e.xmotion.x;
373 | y = e.xmotion.y;
374 | }
375 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
376 | reset_cursor();
377 |
378 | return true;
379 | }
380 |
381 | bool ci_set_zoom(arg_t zl)
382 | {
383 | return img_zoom(&img, (prefix ? prefix : zl) / 100.0);
384 | }
385 |
386 | bool ci_fit_to_win(arg_t sm)
387 | {
388 | return img_fit_win(&img, sm);
389 | }
390 |
391 | bool ci_rotate(arg_t degree)
392 | {
393 | img_rotate(&img, degree);
394 | return true;
395 | }
396 |
397 | bool ci_flip(arg_t dir)
398 | {
399 | img_flip(&img, dir);
400 | return true;
401 | }
402 |
403 | bool ci_toggle_antialias(arg_t _)
404 | {
405 | img_toggle_antialias(&img);
406 | return true;
407 | }
408 |
409 | bool ci_toggle_alpha(arg_t _)
410 | {
411 | img.alpha = !img.alpha;
412 | img.dirty = true;
413 | return true;
414 | }
415 |
416 | bool ci_slideshow(arg_t _)
417 | {
418 | if (prefix > 0) {
419 | img.ss.on = true;
420 | img.ss.delay = prefix * 10;
421 | set_timeout(slideshow, img.ss.delay * 100, true);
422 | } else if (img.ss.on) {
423 | img.ss.on = false;
424 | reset_timeout(slideshow);
425 | } else {
426 | img.ss.on = true;
427 | }
428 | return true;
429 | }
430 |
431 | bool ct_move_sel(arg_t dir)
432 | {
433 | return tns_move_selection(&tns, dir, prefix);
434 | }
435 |
436 | bool ct_reload_all(arg_t _)
437 | {
438 | tns_free(&tns);
439 | tns_init(&tns, files, &filecnt, &fileidx, &win);
440 | tns.dirty = true;
441 | return true;
442 | }
443 |
444 |
445 | #undef G_CMD
446 | #define G_CMD(c) { -1, cg_##c },
447 | #undef I_CMD
448 | #define I_CMD(c) { MODE_IMAGE, ci_##c },
449 | #undef T_CMD
450 | #define T_CMD(c) { MODE_THUMB, ct_##c },
451 |
452 | const cmd_t cmds[CMD_COUNT] = {
453 | #include "commands.lst"
454 | };
455 |
456 |
--------------------------------------------------------------------------------
/sxiv.1:
--------------------------------------------------------------------------------
1 | .TH SXIV 1 sxiv\-VERSION
2 | .SH NAME
3 | sxiv \- Simple X Image Viewer
4 | .SH SYNOPSIS
5 | .B sxiv
6 | .RB [ \-abcfhiopqrtvZ ]
7 | .RB [ \-A
8 | .IR FRAMERATE ]
9 | .RB [ \-e
10 | .IR WID ]
11 | .RB [ \-G
12 | .IR GAMMA ]
13 | .RB [ \-g
14 | .IR GEOMETRY ]
15 | .RB [ \-N
16 | .IR NAME ]
17 | .RB [ \-n
18 | .IR NUM ]
19 | .RB [ \-S
20 | .IR DELAY ]
21 | .RB [ \-s
22 | .IR MODE ]
23 | .RB [ \-z
24 | .IR ZOOM ]
25 | .IR FILE ...
26 | .SH DESCRIPTION
27 | sxiv is a simple image viewer for X.
28 | .P
29 | It has two modes of operation: image and thumbnail mode. The default is image
30 | mode, in which only the current image is shown. In thumbnail mode a grid of
31 | small previews is displayed, making it easy to choose an image to open.
32 | .P
33 | Please note, that the fullscreen mode requires an EWMH/NetWM compliant window
34 | manager.
35 | .SH OPTIONS
36 | .TP
37 | .BI "\-A " FRAMERATE
38 | Play animations with a constant frame rate set to
39 | .IR FRAMERATE .
40 | .TP
41 | .B \-a
42 | Play animations of multi-frame images.
43 | .TP
44 | .B \-b
45 | Do not show info bar on bottom of window.
46 | .TP
47 | .B \-c
48 | Remove all orphaned cache files from the thumbnail cache directory and exit.
49 | .TP
50 | .BI "\-e " WID
51 | Embed sxiv's window into window whose ID is
52 | .IR WID .
53 | .TP
54 | .B \-f
55 | Start in fullscreen mode.
56 | .TP
57 | .BI "\-G " GAMMA
58 | Set image gamma to GAMMA (-32..32).
59 | .TP
60 | .BI "\-g " GEOMETRY
61 | Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for
62 | more information on GEOMETRY argument.
63 | .TP
64 | .BI "\-N " NAME
65 | Set the resource name of sxiv's X window to NAME.
66 | .TP
67 | .BI "\-n " NUM
68 | Start at picture number NUM.
69 | .TP
70 | .B \-h
71 | Print brief usage information to standard output and exit.
72 | .TP
73 | .B \-i
74 | Read names of files to open from standard input. Also done if FILE is `-'.
75 | .TP
76 | .B \-o
77 | Write list of all marked files to standard output when quitting. In combination
78 | with
79 | .B \-i
80 | sxiv can be used as a visual filter/pipe.
81 | .TP
82 | .B \-p
83 | Enable private mode, in which sxiv does not write any cache or temporary files.
84 | .TP
85 | .B \-q
86 | Be quiet, disable warnings to standard error stream.
87 | .TP
88 | .B \-r
89 | Search the given directories recursively for images to view.
90 | .TP
91 | .BI "\-S " DELAY
92 | Start in slideshow mode. Set the delay between images to
93 | .I DELAY
94 | seconds.
95 | .I DELAY
96 | may be a floating point number.
97 | .TP
98 | .BI "\-s " MODE
99 | Set scale mode according to MODE character. Supported modes are: [d]own,
100 | [f]it, [w]idth, [h]eight.
101 | .TP
102 | .B \-t
103 | Start in thumbnail mode.
104 | .TP
105 | .B \-v
106 | Print version information to standard output and exit.
107 | .TP
108 | .B \-Z
109 | The same as `\-z 100'.
110 | .TP
111 | .BI "\-z " ZOOM
112 | Set zoom level to ZOOM percent.
113 | .SH KEYBOARD COMMANDS
114 | .SS General
115 | The following keyboard commands are available in both image and thumbnail mode:
116 | .TP
117 | .BR 0 \- 9
118 | Prefix the next command with a number (denoted via
119 | .IR count ).
120 | .TP
121 | .B q
122 | Quit sxiv.
123 | .TP
124 | .B Return
125 | Switch to thumbnail mode / open selected image in image mode.
126 | .TP
127 | .B f
128 | Toggle fullscreen mode.
129 | .TP
130 | .B b
131 | Toggle visibility of info bar on bottom of window.
132 | .TP
133 | .B Ctrl-x
134 | Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER
135 | for more information.
136 | .TP
137 | .B g
138 | Go to the first image.
139 | .TP
140 | .B G
141 | Go to the last image, or image number
142 | .IR count .
143 | .TP
144 | .B r
145 | Reload image.
146 | .TP
147 | .B D
148 | Remove current image from file list and go to next image.
149 | .TP
150 | .BR Ctrl-h ", " Ctrl-Left
151 | Scroll left one screen width.
152 | .TP
153 | .BR Ctrl-j ", " Ctrl-Down
154 | Scroll down one screen height.
155 | .TP
156 | .BR Ctrl-k ", " Ctrl-Up
157 | Scroll up one screen height.
158 | .TP
159 | .BR Ctrl-l ", " Ctrl-Right
160 | Scroll right one screen width.
161 | .TP
162 | .BR +
163 | Zoom in.
164 | .TP
165 | .B \-
166 | Zoom out.
167 | .TP
168 | .B m
169 | Mark/unmark the current image.
170 | .TP
171 | .B M
172 | Reverse all image marks.
173 | .TP
174 | .B Ctrl-M
175 | Repeat last mark action on all images from the last marked/unmarked up to the
176 | current one.
177 | .TP
178 | .B Ctrl-m
179 | Remove all image marks.
180 | .TP
181 | .B N
182 | Go
183 | .I count
184 | marked images forward.
185 | .TP
186 | .B P
187 | Go
188 | .I count
189 | marked images backward.
190 | .TP
191 | .B {
192 | Decrease gamma correction by
193 | .I count
194 | steps.
195 | .TP
196 | .B }
197 | Increase gamma correction by
198 | .I count
199 | steps.
200 | .TP
201 | .B Ctrl-g
202 | Reset gamma correction.
203 | .SS Thumbnail mode
204 | The following keyboard commands are only available in thumbnail mode:
205 | .TP
206 | .BR h ", " Left
207 | Move selection left
208 | .I count
209 | times.
210 | .TP
211 | .BR j ", " Down
212 | Move selection down
213 | .I count
214 | times.
215 | .TP
216 | .BR k ", " Up
217 | Move selection up
218 | .I count
219 | times.
220 | .TP
221 | .BR l ", " Right
222 | Move selection right
223 | .I count
224 | times.
225 | .TP
226 | .B R
227 | Reload all thumbnails.
228 | .SS Image mode
229 | The following keyboard commands are only available in image mode:
230 | .TP
231 | Navigate image list:
232 | .TP
233 | .BR n ", " Space
234 | Go
235 | .I count
236 | images forward.
237 | .TP
238 | .BR p ", " Backspace
239 | Go
240 | .I count
241 | images backward.
242 | .TP
243 | .B [
244 | Go
245 | .I count
246 | * 10 images backward.
247 | .TP
248 | .B ]
249 | Go
250 | .I count
251 | * 10 images forward.
252 | .TP
253 | Handle multi-frame images:
254 | .TP
255 | .B Ctrl-n
256 | Go
257 | .I count
258 | frames of a multi-frame image forward.
259 | .TP
260 | .B Ctrl-p
261 | Go
262 | .I count
263 | frames of a multi-frame image backward.
264 | .TP
265 | .B Ctrl-Space
266 | Play/stop animations of multi-frame images.
267 | .TP
268 | Panning:
269 | .TP
270 | .BR h ", " Left
271 | Scroll image 1/5 of window width or
272 | .I count
273 | pixel left.
274 | .TP
275 | .BR j ", " Down
276 | Scroll image 1/5 of window height or
277 | .I count
278 | pixel down.
279 | .TP
280 | .BR k ", " Up
281 | Scroll image 1/5 of window height or
282 | .I count
283 | pixel up.
284 | .TP
285 | .BR l ", " Right
286 | Scroll image 1/5 of window width or
287 | .I count
288 | pixel right.
289 | .TP
290 | .B H
291 | Scroll to left image edge.
292 | .TP
293 | .B J
294 | Scroll to bottom image edge.
295 | .TP
296 | .B K
297 | Scroll to top image edge.
298 | .TP
299 | .B L
300 | Scroll to right image edge.
301 | .TP
302 | Zooming:
303 | .TP
304 | .B =
305 | Set zoom level to 100%, or
306 | .IR count %.
307 | .TP
308 | .B w
309 | Set zoom level to 100%, but fit large images into window.
310 | .TP
311 | .B W
312 | Fit image to window.
313 | .TP
314 | .B e
315 | Fit image to window width.
316 | .TP
317 | .B E
318 | Fit image to window height.
319 | .TP
320 | Rotation:
321 | .TP
322 | .B <
323 | Rotate image counter-clockwise by 90 degrees.
324 | .TP
325 | .B >
326 | Rotate image clockwise by 90 degrees.
327 | .TP
328 | .B ?
329 | Rotate image by 180 degrees.
330 | .TP
331 | Flipping:
332 | .TP
333 | .B |
334 | Flip image horizontally.
335 | .TP
336 | .B _
337 | Flip image vertically.
338 | .TP
339 | Miscellaneous:
340 | .TP
341 | .B a
342 | Toggle anti-aliasing.
343 | .TP
344 | .B A
345 | Toggle visibility of alpha-channel, i.e. image transparency.
346 | .TP
347 | .B s
348 | Toggle slideshow mode and/or set the delay between images to
349 | .I count
350 | seconds.
351 | .SH MOUSE COMMANDS
352 | The following mouse mappings are available in image mode:
353 | .TP
354 | General:
355 | .TP
356 | .B Button3
357 | Switch to thumbnail mode.
358 | .TP
359 | Navigate image list:
360 | .TP
361 | .B Button1
362 | Go to the next image if the mouse cursor is in the right part of the window or
363 | to the previous image if it is in the left part.
364 | .TP
365 | Panning:
366 | .TP
367 | .B Button2
368 | Pan the image according to the mouse cursor position in the window while
369 | keeping this button pressed down.
370 | .TP
371 | Zooming:
372 | .TP
373 | .B ScrollUp
374 | Zoom in.
375 | .TP
376 | .B ScrollDown
377 | Zoom out.
378 | .SH CONFIGURATION
379 | The following X resources are supported:
380 | .TP
381 | .B background
382 | Color of the window background and bar foreground
383 | .TP
384 | .B foreground
385 | Color of the window foreground and bar background
386 | .TP
387 | .B font
388 | Name of Xft bar font
389 | .TP
390 | Please see xrdb(1) on how to change them.
391 | .SH STATUS BAR
392 | The information displayed on the left side of the status bar can be replaced
393 | with the output of a user-provided script, which is called by sxiv whenever an
394 | image gets loaded. The path of this script is
395 | .I $XDG_CONFIG_HOME/sxiv/exec/image-info
396 | and the arguments given to it are: 1) path to image file, 2) image width,
397 | 3) image height.
398 | .P
399 | There is also an example script installed together with sxiv as
400 | .IR PREFIX/share/sxiv/exec/image-info .
401 | .SH EXTERNAL KEY HANDLER
402 | Additional external keyboard commands can be defined using a handler program
403 | located in
404 | .IR $XDG_CONFIG_HOME/sxiv/exec/key-handler .
405 | The handler is invoked by pressing
406 | .BR Ctrl-x .
407 | The next key combo is passed as its first argument. Passed via stdin are the
408 | images to act upon, one path per line: all marked images, if in thumbnail mode
409 | and at least one image has been marked, otherwise the current image.
410 | sxiv(1) will block until the handler terminates. It then checks which images
411 | have been modified and reloads them.
412 |
413 | The key combo argument has the following form: "[C-][M-][S-]KEY",
414 | where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X
415 | keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix.
416 |
417 | There is also an example script installed together with sxiv as
418 | .IR PREFIX/share/sxiv/exec/key-handler .
419 | .SH THUMBNAIL CACHING
420 | sxiv stores all thumbnails under
421 | .IR $XDG_CACHE_HOME/sxiv/ .
422 | .P
423 | Use the command line option
424 | .I \-c
425 | to remove all orphaned cache files. Additionally, run the following command
426 | afterwards inside the cache directory to remove empty subdirectories:
427 | .P
428 | .RS
429 | find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\;
430 | .RE
431 | .SH AUTHOR
432 | .EX
433 | Bert Muennich
434 | .EE
435 | .SH CONTRIBUTORS
436 | .EX
437 | Bastien Dejean
438 | Dave Reisner
439 | Fung SzeTat
440 | Max Voit
441 | .EE
442 | .SH HOMEPAGE
443 | .EX
444 | https://github.com/xyb3rt/sxiv
445 | .EE
446 | .SH SEE ALSO
447 | .BR X (7),
448 | .BR xrdb (1)
449 |
--------------------------------------------------------------------------------
/window.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011-2013 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _WINDOW_CONFIG
21 | #include "config.h"
22 | #include "icon/data.h"
23 | #include "utf8.h"
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #define RES_CLASS "Sxiv"
33 |
34 | enum {
35 | H_TEXT_PAD = 5,
36 | V_TEXT_PAD = 1
37 | };
38 |
39 | static struct {
40 | int name;
41 | Cursor icon;
42 | } cursors[CURSOR_COUNT] = {
43 | { XC_left_ptr }, { XC_dotbox }, { XC_watch },
44 | { XC_sb_left_arrow }, { XC_sb_right_arrow }
45 | };
46 |
47 | static GC gc;
48 |
49 | static XftFont *font;
50 | static int fontheight;
51 | static double fontsize;
52 | static int barheight;
53 |
54 | Atom atoms[ATOM_COUNT];
55 |
56 | void win_init_font(const win_env_t *e, const char *fontstr)
57 | {
58 | if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL)
59 | error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr);
60 | fontheight = font->ascent + font->descent;
61 | FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize);
62 | barheight = fontheight + 2 * V_TEXT_PAD;
63 | }
64 |
65 | void win_alloc_color(const win_env_t *e, const char *name, XftColor *col)
66 | {
67 | if (!XftColorAllocName(e->dpy, DefaultVisual(e->dpy, e->scr),
68 | DefaultColormap(e->dpy, e->scr), name, col))
69 | {
70 | error(EXIT_FAILURE, 0, "Error allocating color '%s'", name);
71 | }
72 | }
73 |
74 | const char* win_res(XrmDatabase db, const char *name, const char *def)
75 | {
76 | char *type;
77 | XrmValue ret;
78 |
79 | if (db != None &&
80 | XrmGetResource(db, name, name, &type, &ret) &&
81 | STREQ(type, "String"))
82 | {
83 | return ret.addr;
84 | } else {
85 | return def;
86 | }
87 | }
88 |
89 | #define INIT_ATOM_(atom) \
90 | atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False);
91 |
92 | void win_init(win_t *win)
93 | {
94 | win_env_t *e;
95 | const char *bg, *fg, *f;
96 | char *res_man;
97 | XrmDatabase db;
98 |
99 | memset(win, 0, sizeof(win_t));
100 |
101 | e = &win->env;
102 | if ((e->dpy = XOpenDisplay(NULL)) == NULL)
103 | error(EXIT_FAILURE, 0, "Error opening X display");
104 |
105 | e->scr = DefaultScreen(e->dpy);
106 | e->scrw = DisplayWidth(e->dpy, e->scr);
107 | e->scrh = DisplayHeight(e->dpy, e->scr);
108 | e->vis = DefaultVisual(e->dpy, e->scr);
109 | e->cmap = DefaultColormap(e->dpy, e->scr);
110 | e->depth = DefaultDepth(e->dpy, e->scr);
111 |
112 | if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0)
113 | error(0, 0, "No locale support");
114 |
115 | XrmInitialize();
116 | res_man = XResourceManagerString(e->dpy);
117 | db = res_man != NULL ? XrmGetStringDatabase(res_man) : None;
118 |
119 | f = win_res(db, RES_CLASS ".font", "monospace-8");
120 | win_init_font(e, f);
121 |
122 | bg = win_res(db, RES_CLASS ".background", "white");
123 | fg = win_res(db, RES_CLASS ".foreground", "black");
124 | win_alloc_color(e, bg, &win->bg);
125 | win_alloc_color(e, fg, &win->fg);
126 |
127 | win->bar.l.size = BAR_L_LEN;
128 | win->bar.r.size = BAR_R_LEN;
129 | /* 3 padding bytes needed by utf8_decode */
130 | win->bar.l.buf = emalloc(win->bar.l.size + 3);
131 | win->bar.l.buf[0] = '\0';
132 | win->bar.r.buf = emalloc(win->bar.r.size + 3);
133 | win->bar.r.buf[0] = '\0';
134 | win->bar.h = options->hide_bar ? 0 : barheight;
135 |
136 | INIT_ATOM_(WM_DELETE_WINDOW);
137 | INIT_ATOM_(_NET_WM_NAME);
138 | INIT_ATOM_(_NET_WM_ICON_NAME);
139 | INIT_ATOM_(_NET_WM_ICON);
140 | INIT_ATOM_(_NET_WM_STATE);
141 | INIT_ATOM_(_NET_WM_STATE_FULLSCREEN);
142 | }
143 |
144 | void win_open(win_t *win)
145 | {
146 | int c, i, j, n;
147 | long parent;
148 | win_env_t *e;
149 | XClassHint classhint;
150 | unsigned long *icon_data;
151 | XColor col;
152 | Cursor *cnone = &cursors[CURSOR_NONE].icon;
153 | char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
154 | Pixmap none;
155 | int gmask;
156 | XSizeHints sizehints;
157 |
158 | e = &win->env;
159 | parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr);
160 |
161 | sizehints.flags = PWinGravity;
162 | sizehints.win_gravity = NorthWestGravity;
163 |
164 | /* determine window offsets, width & height */
165 | if (options->geometry == NULL)
166 | gmask = 0;
167 | else
168 | gmask = XParseGeometry(options->geometry, &win->x, &win->y,
169 | &win->w, &win->h);
170 | if ((gmask & WidthValue) != 0)
171 | sizehints.flags |= USSize;
172 | else
173 | win->w = WIN_WIDTH;
174 | if ((gmask & HeightValue) != 0)
175 | sizehints.flags |= USSize;
176 | else
177 | win->h = WIN_HEIGHT;
178 | if ((gmask & XValue) != 0) {
179 | if ((gmask & XNegative) != 0) {
180 | win->x += e->scrw - win->w;
181 | sizehints.win_gravity = NorthEastGravity;
182 | }
183 | sizehints.flags |= USPosition;
184 | } else {
185 | win->x = 0;
186 | }
187 | if ((gmask & YValue) != 0) {
188 | if ((gmask & YNegative) != 0) {
189 | win->y += e->scrh - win->h;
190 | sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity
191 | ? SouthEastGravity : SouthWestGravity;
192 | }
193 | sizehints.flags |= USPosition;
194 | } else {
195 | win->y = 0;
196 | }
197 |
198 | win->xwin = XCreateWindow(e->dpy, parent,
199 | win->x, win->y, win->w, win->h, 0,
200 | e->depth, InputOutput, e->vis, 0, NULL);
201 | if (win->xwin == None)
202 | error(EXIT_FAILURE, 0, "Error creating X window");
203 |
204 | XSelectInput(e->dpy, win->xwin,
205 | ButtonReleaseMask | ButtonPressMask | KeyPressMask |
206 | PointerMotionMask | StructureNotifyMask);
207 |
208 | for (i = 0; i < ARRLEN(cursors); i++) {
209 | if (i != CURSOR_NONE)
210 | cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name);
211 | }
212 | if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black",
213 | &col, &col) == 0)
214 | {
215 | error(EXIT_FAILURE, 0, "Error allocating color 'black'");
216 | }
217 | none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8);
218 | *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0);
219 |
220 | gc = XCreateGC(e->dpy, win->xwin, 0, None);
221 |
222 | n = icons[ARRLEN(icons)-1].size;
223 | icon_data = emalloc((n * n + 2) * sizeof(*icon_data));
224 |
225 | for (i = 0; i < ARRLEN(icons); i++) {
226 | n = 0;
227 | icon_data[n++] = icons[i].size;
228 | icon_data[n++] = icons[i].size;
229 |
230 | for (j = 0; j < icons[i].cnt; j++) {
231 | for (c = icons[i].data[j] >> 4; c >= 0; c--)
232 | icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F];
233 | }
234 | XChangeProperty(e->dpy, win->xwin,
235 | atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32,
236 | i == 0 ? PropModeReplace : PropModeAppend,
237 | (unsigned char *) icon_data, n);
238 | }
239 | free(icon_data);
240 |
241 | win_set_title(win, "sxiv");
242 |
243 | classhint.res_class = RES_CLASS;
244 | classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv";
245 | XSetClassHint(e->dpy, win->xwin, &classhint);
246 |
247 | XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1);
248 |
249 | sizehints.width = win->w;
250 | sizehints.height = win->h;
251 | sizehints.x = win->x;
252 | sizehints.y = win->y;
253 | XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints);
254 |
255 | win->h -= win->bar.h;
256 |
257 | win->buf.w = e->scrw;
258 | win->buf.h = e->scrh;
259 | win->buf.pm = XCreatePixmap(e->dpy, win->xwin,
260 | win->buf.w, win->buf.h, e->depth);
261 | XSetForeground(e->dpy, gc, win->bg.pixel);
262 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
263 | XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm);
264 |
265 | XMapWindow(e->dpy, win->xwin);
266 | XFlush(e->dpy);
267 |
268 | if (options->fullscreen)
269 | win_toggle_fullscreen(win);
270 | }
271 |
272 | CLEANUP void win_close(win_t *win)
273 | {
274 | int i;
275 |
276 | for (i = 0; i < ARRLEN(cursors); i++)
277 | XFreeCursor(win->env.dpy, cursors[i].icon);
278 |
279 | XFreeGC(win->env.dpy, gc);
280 |
281 | XDestroyWindow(win->env.dpy, win->xwin);
282 | XCloseDisplay(win->env.dpy);
283 | }
284 |
285 | bool win_configure(win_t *win, XConfigureEvent *c)
286 | {
287 | bool changed;
288 |
289 | changed = win->w != c->width || win->h + win->bar.h != c->height;
290 |
291 | win->x = c->x;
292 | win->y = c->y;
293 | win->w = c->width;
294 | win->h = c->height - win->bar.h;
295 | win->bw = c->border_width;
296 |
297 | return changed;
298 | }
299 |
300 | void win_toggle_fullscreen(win_t *win)
301 | {
302 | XEvent ev;
303 | XClientMessageEvent *cm;
304 |
305 | memset(&ev, 0, sizeof(ev));
306 | ev.type = ClientMessage;
307 |
308 | cm = &ev.xclient;
309 | cm->window = win->xwin;
310 | cm->message_type = atoms[ATOM__NET_WM_STATE];
311 | cm->format = 32;
312 | cm->data.l[0] = 2; // toggle
313 | cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN];
314 |
315 | XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False,
316 | SubstructureNotifyMask | SubstructureRedirectMask, &ev);
317 | }
318 |
319 | void win_toggle_bar(win_t *win)
320 | {
321 | if (win->bar.h != 0) {
322 | win->h += win->bar.h;
323 | win->bar.h = 0;
324 | } else {
325 | win->bar.h = barheight;
326 | win->h -= win->bar.h;
327 | }
328 | }
329 |
330 | void win_clear(win_t *win)
331 | {
332 | win_env_t *e;
333 |
334 | e = &win->env;
335 |
336 | if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) {
337 | XFreePixmap(e->dpy, win->buf.pm);
338 | win->buf.w = MAX(win->buf.w, win->w);
339 | win->buf.h = MAX(win->buf.h, win->h + win->bar.h);
340 | win->buf.pm = XCreatePixmap(e->dpy, win->xwin,
341 | win->buf.w, win->buf.h, e->depth);
342 | }
343 | XSetForeground(e->dpy, gc, win->bg.pixel);
344 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h);
345 | }
346 |
347 | #define TEXTWIDTH(win, text, len) \
348 | win_draw_text(win, NULL, NULL, 0, 0, text, len, 0)
349 |
350 | int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y,
351 | char *text, int len, int w)
352 | {
353 | int err, tw = 0;
354 | char *t, *next;
355 | uint32_t rune;
356 | XftFont *f;
357 | FcCharSet *fccharset;
358 | XGlyphInfo ext;
359 |
360 | for (t = text; t - text < len; t = next) {
361 | next = utf8_decode(t, &rune, &err);
362 | if (XftCharExists(win->env.dpy, font, rune)) {
363 | f = font;
364 | } else { /* fallback font */
365 | fccharset = FcCharSetCreate();
366 | FcCharSetAddChar(fccharset, rune);
367 | f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet,
368 | fccharset, FC_SCALABLE, FcTypeBool, FcTrue,
369 | FC_SIZE, FcTypeDouble, fontsize, NULL);
370 | FcCharSetDestroy(fccharset);
371 | }
372 | XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext);
373 | tw += ext.xOff;
374 | if (tw <= w) {
375 | XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t);
376 | x += ext.xOff;
377 | }
378 | if (f != font)
379 | XftFontClose(win->env.dpy, f);
380 | }
381 | return tw;
382 | }
383 |
384 | void win_draw_bar(win_t *win)
385 | {
386 | int len, x, y, w, tw;
387 | win_env_t *e;
388 | win_bar_t *l, *r;
389 | XftDraw *d;
390 |
391 | if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL)
392 | return;
393 |
394 | e = &win->env;
395 | y = win->h + font->ascent + V_TEXT_PAD;
396 | w = win->w - 2*H_TEXT_PAD;
397 | d = XftDrawCreate(e->dpy, win->buf.pm, DefaultVisual(e->dpy, e->scr),
398 | DefaultColormap(e->dpy, e->scr));
399 |
400 | XSetForeground(e->dpy, gc, win->fg.pixel);
401 | XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h);
402 |
403 | XSetForeground(e->dpy, gc, win->bg.pixel);
404 | XSetBackground(e->dpy, gc, win->fg.pixel);
405 |
406 | if ((len = strlen(r->buf)) > 0) {
407 | if ((tw = TEXTWIDTH(win, r->buf, len)) > w)
408 | return;
409 | x = win->w - tw - H_TEXT_PAD;
410 | w -= tw;
411 | win_draw_text(win, d, &win->bg, x, y, r->buf, len, tw);
412 | }
413 | if ((len = strlen(l->buf)) > 0) {
414 | x = H_TEXT_PAD;
415 | w -= 2 * H_TEXT_PAD; /* gap between left and right parts */
416 | win_draw_text(win, d, &win->bg, x, y, l->buf, len, w);
417 | }
418 | XftDrawDestroy(d);
419 | }
420 |
421 | void win_draw(win_t *win)
422 | {
423 | if (win->bar.h > 0)
424 | win_draw_bar(win);
425 |
426 | XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm);
427 | XClearWindow(win->env.dpy, win->xwin);
428 | XFlush(win->env.dpy);
429 | }
430 |
431 | void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw,
432 | unsigned long col)
433 | {
434 | XGCValues gcval;
435 |
436 | gcval.line_width = lw;
437 | gcval.foreground = col;
438 | XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval);
439 |
440 | if (fill)
441 | XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
442 | else
443 | XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h);
444 | }
445 |
446 | void win_set_title(win_t *win, const char *title)
447 | {
448 | XStoreName(win->env.dpy, win->xwin, title);
449 | XSetIconName(win->env.dpy, win->xwin, title);
450 |
451 | XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME],
452 | XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
453 | PropModeReplace, (unsigned char *) title, strlen(title));
454 | XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME],
455 | XInternAtom(win->env.dpy, "UTF8_STRING", False), 8,
456 | PropModeReplace, (unsigned char *) title, strlen(title));
457 | }
458 |
459 | void win_set_cursor(win_t *win, cursor_t cursor)
460 | {
461 | if (cursor >= 0 && cursor < ARRLEN(cursors)) {
462 | XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon);
463 | XFlush(win->env.dpy);
464 | }
465 | }
466 |
467 | void win_cursor_pos(win_t *win, int *x, int *y)
468 | {
469 | int i;
470 | unsigned int ui;
471 | Window w;
472 |
473 | if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui))
474 | *x = *y = 0;
475 | }
476 |
477 |
--------------------------------------------------------------------------------
/icon/data.h:
--------------------------------------------------------------------------------
1 | #ifndef ICON_DATA_H
2 | #define ICON_DATA_H
3 |
4 | typedef struct {
5 | unsigned int size;
6 | unsigned int cnt;
7 | const unsigned char *data;
8 | } icon_data_t;
9 |
10 | static const unsigned int icon_colors[] = {
11 | 0xff222034, 0xffffffff, 0xff306082, 0xff76428a,
12 | 0xfffbf236, 0xff99e550, 0xffd95763, 0xff37946e,
13 | 0xff6abe30, 0xffac3232
14 | };
15 |
16 | static const unsigned char icon_data_16[] = {
17 | 0xf0, 0x80, 0x01, 0xf0, 0x80, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
18 | 0x00, 0x01, 0x40, 0x01, 0x10, 0x01, 0x10, 0x01, 0x00, 0x01, 0x00, 0x01,
19 | 0x30, 0x11, 0x00, 0x01, 0x00, 0x51, 0xf0, 0x22, 0xa0, 0x42, 0x80, 0x62,
20 | 0x03, 0x50, 0x02, 0x34, 0x05, 0x22, 0x13, 0x06, 0x10, 0x37, 0x08, 0x35,
21 | 0x12, 0x13, 0x09, 0x06, 0x22, 0x47, 0x25, 0x02, 0x13, 0x09, 0x06, 0x32,
22 | 0x47, 0x08, 0x05, 0x08, 0x03, 0x19, 0x16, 0x32, 0x47, 0x18, 0x29, 0x16,
23 | 0x42, 0x37, 0x18, 0x19, 0x26, 0x42, 0x47, 0x08
24 | };
25 |
26 | static const unsigned char icon_data_32[] = {
27 | 0xf0, 0x10, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0,
28 | 0xf0, 0xf0, 0xf0, 0xf0, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11,
29 | 0x10, 0x11, 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11,
30 | 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x90, 0x11,
31 | 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x00, 0x22, 0x50, 0x11,
32 | 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x50, 0x11, 0x30,
33 | 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x30, 0x31, 0x10, 0x11,
34 | 0x10, 0xb1, 0x52, 0x30, 0x31, 0x10, 0x11, 0x10, 0xb1, 0x52, 0x30, 0x31,
35 | 0x10, 0x11, 0x10, 0xb1, 0x52, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0,
36 | 0x54, 0x92, 0x13, 0xc0, 0x84, 0x05, 0x62, 0x33, 0x80, 0x74, 0x55, 0x42,
37 | 0x33, 0x09, 0x02, 0x30, 0x77, 0x08, 0x85, 0x32, 0x33, 0x19, 0x12, 0xc7,
38 | 0x08, 0x65, 0x08, 0x12, 0x33, 0x19, 0x06, 0x52, 0x97, 0x08, 0x45, 0x18,
39 | 0x02, 0x33, 0x19, 0x16, 0x62, 0x97, 0x08, 0x35, 0x18, 0x23, 0x29, 0x16,
40 | 0x72, 0x97, 0x18, 0x15, 0x18, 0x23, 0x29, 0x26, 0x72, 0x97, 0x48, 0x13,
41 | 0x39, 0x26, 0x72, 0x97, 0x48, 0x03, 0x39, 0x36, 0x82, 0x97, 0x38, 0x49,
42 | 0x36, 0x82, 0x97, 0x38, 0x39, 0x46, 0x82, 0x97, 0x38, 0x29, 0x56, 0x92,
43 | 0x97, 0x28, 0x29, 0x56, 0x92, 0x97, 0x28
44 | };
45 |
46 | static const unsigned char icon_data_48[] = {
47 | 0xf0, 0xa0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0,
48 | 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
49 | 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
50 | 0x20, 0x21, 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21,
51 | 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x81,
52 | 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21,
53 | 0x50, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21,
54 | 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21,
55 | 0x20, 0x21, 0x10, 0x32, 0x80, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21,
56 | 0x20, 0x21, 0x52, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x00, 0x72,
57 | 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21,
58 | 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82,
59 | 0xf0, 0xf0, 0x00, 0xe2, 0xf0, 0xe0, 0xf2, 0x02, 0xf0, 0xc0, 0xf2, 0x22,
60 | 0xf0, 0xb0, 0xf2, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52,
61 | 0xf0, 0x80, 0x74, 0x15, 0xc2, 0x03, 0xf0, 0x50, 0xc4, 0x15, 0x92, 0x23,
62 | 0xf0, 0x10, 0xe4, 0x35, 0x72, 0x33, 0x19, 0xc0, 0xc4, 0x85, 0x62, 0x43,
63 | 0x19, 0x12, 0x70, 0x57, 0x18, 0xf5, 0x05, 0x52, 0x53, 0x19, 0x12, 0x40,
64 | 0xb7, 0x18, 0xe5, 0x32, 0x53, 0x29, 0x22, 0xf7, 0x37, 0xb5, 0x08, 0x22,
65 | 0x53, 0x29, 0x06, 0x72, 0xf7, 0xa5, 0x08, 0x12, 0x53, 0x29, 0x16, 0x92,
66 | 0xe7, 0x85, 0x18, 0x02, 0x43, 0x39, 0x26, 0x92, 0xe7, 0x08, 0x65, 0x28,
67 | 0x43, 0x39, 0x26, 0xa2, 0xe7, 0x18, 0x45, 0x28, 0x43, 0x39, 0x36, 0xa2,
68 | 0xe7, 0x28, 0x25, 0x28, 0x33, 0x49, 0x36, 0xb2, 0xe7, 0x78, 0x33, 0x49,
69 | 0x46, 0xb2, 0xe7, 0x68, 0x23, 0x59, 0x46, 0xb2, 0xe7, 0x68, 0x13, 0x59,
70 | 0x56, 0xc2, 0xe7, 0x58, 0x79, 0x56, 0xc2, 0xe7, 0x58, 0x69, 0x66, 0xd2,
71 | 0xd7, 0x58, 0x59, 0x76, 0xd2, 0xd7, 0x58, 0x49, 0x86, 0xe2, 0xe7, 0x38,
72 | 0x39, 0x86, 0xf2, 0xe7, 0x38, 0x29, 0x86, 0xf2, 0x02, 0xe7, 0x38
73 | };
74 |
75 | static const unsigned char icon_data_64[] = {
76 | 0xf0, 0xf0, 0x30, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0,
77 | 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31,
78 | 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
79 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
80 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30,
81 | 0x31, 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31,
82 | 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30,
83 | 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31,
84 | 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30,
85 | 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31,
86 | 0xf0, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x20,
87 | 0x42, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00,
88 | 0x62, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72,
89 | 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0,
90 | 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 0x31,
91 | 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0x70, 0x71, 0x30,
92 | 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71,
93 | 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30,
94 | 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71,
95 | 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0xf0, 0xf0, 0x50,
96 | 0xf2, 0x92, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2,
97 | 0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x00, 0x94, 0xf2, 0x42, 0xf0,
98 | 0xe0, 0xe4, 0xf2, 0x12, 0x23, 0xf0, 0xa0, 0xf4, 0x14, 0x05, 0xe2, 0x43,
99 | 0xf0, 0x70, 0xf4, 0x14, 0x35, 0xc2, 0x43, 0x19, 0xf0, 0x30, 0xf4, 0x95,
100 | 0xa2, 0x53, 0x29, 0xe0, 0xf4, 0x04, 0xd5, 0x82, 0x63, 0x29, 0x02, 0x90,
101 | 0xc7, 0xf5, 0x65, 0x62, 0x73, 0x29, 0x12, 0x50, 0xf7, 0x27, 0xf5, 0x35,
102 | 0x52, 0x73, 0x29, 0x06, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x08, 0x42, 0x73,
103 | 0x39, 0x06, 0x32, 0xf7, 0xa7, 0xd5, 0x28, 0x22, 0x73, 0x39, 0x16, 0xa2,
104 | 0xf7, 0x47, 0xb5, 0x38, 0x12, 0x73, 0x39, 0x26, 0xb2, 0xf7, 0x47, 0xa5,
105 | 0x38, 0x02, 0x73, 0x39, 0x26, 0xd2, 0xf7, 0x47, 0x08, 0x75, 0x48, 0x63,
106 | 0x49, 0x36, 0xd2, 0xf7, 0x47, 0x18, 0x65, 0x38, 0x63, 0x49, 0x36, 0xe2,
107 | 0xf7, 0x47, 0x28, 0x45, 0x38, 0x53, 0x59, 0x46, 0xe2, 0xf7, 0x47, 0x38,
108 | 0x15, 0x48, 0x53, 0x59, 0x46, 0xf2, 0xf7, 0x37, 0xa8, 0x43, 0x69, 0x56,
109 | 0xf2, 0xf7, 0x37, 0x98, 0x33, 0x79, 0x56, 0xf2, 0xf7, 0x37, 0x98, 0x23,
110 | 0x79, 0x66, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x13, 0x89, 0x66, 0xf2, 0x02,
111 | 0xf7, 0x37, 0x88, 0x03, 0x89, 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x99,
112 | 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x89, 0x86, 0xf2, 0x12, 0xf7, 0x37,
113 | 0x78, 0x79, 0x96, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22,
114 | 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x59, 0xb6,
115 | 0xf2, 0x32, 0xf7, 0x37, 0x58, 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58,
116 | 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58
117 | };
118 |
119 | static const unsigned char icon_data_128[] = {
120 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
121 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71,
122 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0,
123 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
124 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71,
125 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0,
126 | 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
127 | 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71,
128 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0,
129 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
130 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
131 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
132 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
133 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
134 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
135 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
136 | 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
137 | 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71,
138 | 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70,
139 | 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71,
140 | 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70,
141 | 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71,
142 | 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70,
143 | 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71,
144 | 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70,
145 | 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71,
146 | 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1,
147 | 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70,
148 | 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0,
149 | 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xa0,
150 | 0x42, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 0x71,
151 | 0x60, 0x82, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70,
152 | 0x71, 0x40, 0xa2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71,
153 | 0x70, 0x71, 0x20, 0xc2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70,
154 | 0x71, 0x70, 0x71, 0x00, 0xe2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
155 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
156 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
157 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
158 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
159 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
160 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
161 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71,
162 | 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1,
163 | 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2,
164 | 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0,
165 | 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70,
166 | 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70,
167 | 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1,
168 | 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2,
169 | 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0,
170 | 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70,
171 | 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70,
172 | 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0xf2, 0xf2,
173 | 0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0,
174 | 0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0,
175 | 0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2,
176 | 0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0,
177 | 0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0,
178 | 0x30, 0x12, 0xa4, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf4,
179 | 0x14, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf4, 0x64, 0xf2,
180 | 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf4, 0xb4, 0xf2, 0xf2, 0x42, 0xf0,
181 | 0xf0, 0xf0, 0xd0, 0xf4, 0xd4, 0x15, 0xf2, 0xf2, 0x12, 0x43, 0xf0, 0xf0,
182 | 0xf0, 0x60, 0xf4, 0xf4, 0x04, 0x35, 0xf2, 0xe2, 0x63, 0xf0, 0xf0, 0xf0,
183 | 0x30, 0xf4, 0xf4, 0x24, 0x45, 0xf2, 0xc2, 0x83, 0xf0, 0xf0, 0xf0, 0x00,
184 | 0xf4, 0xf4, 0x34, 0x65, 0xf2, 0xa2, 0x93, 0xf0, 0xf0, 0xe0, 0xf4, 0xf4,
185 | 0x34, 0x95, 0xf2, 0x82, 0xa3, 0x19, 0xf0, 0xf0, 0x90, 0xf4, 0xf4, 0x44,
186 | 0xc5, 0xf2, 0x62, 0xb3, 0x29, 0xf0, 0xf0, 0x40, 0xf4, 0xf4, 0x64, 0xf5,
187 | 0xf2, 0x42, 0xc3, 0x39, 0xf0, 0xf0, 0xf4, 0xf4, 0x74, 0xf5, 0x35, 0xf2,
188 | 0x22, 0xd3, 0x49, 0xf0, 0xa0, 0xf4, 0xf4, 0x84, 0xf5, 0x75, 0xf2, 0x02,
189 | 0xd3, 0x59, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf4, 0x74, 0xf5, 0xb5, 0x08,
190 | 0xe2, 0xe3, 0x59, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18,
191 | 0xc2, 0xe3, 0x59, 0x06, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65,
192 | 0x18, 0xb2, 0xf3, 0x59, 0x06, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5,
193 | 0x45, 0x18, 0xa2, 0xf3, 0x59, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5,
194 | 0xf5, 0x25, 0x28, 0x82, 0xf3, 0x69, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x27,
195 | 0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf3, 0x69, 0x26, 0x82, 0xf7, 0xf7, 0xf7,
196 | 0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf3, 0x79, 0x26, 0xc2, 0xf7, 0xf7, 0xf7,
197 | 0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf3, 0x79, 0x36, 0xf2, 0x22, 0xf7, 0xf7,
198 | 0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf3, 0x79, 0x46, 0xf2, 0x52, 0xf7, 0xf7,
199 | 0x97, 0xf5, 0x85, 0x68, 0x22, 0xf3, 0x89, 0x36, 0xf2, 0x72, 0xf7, 0xf7,
200 | 0x97, 0xf5, 0x65, 0x78, 0x12, 0xf3, 0x89, 0x46, 0xf2, 0x82, 0xf7, 0xf7,
201 | 0x97, 0xf5, 0x55, 0x78, 0x02, 0xf3, 0x89, 0x46, 0xf2, 0xa2, 0xf7, 0xf7,
202 | 0x87, 0x08, 0xf5, 0x35, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xb2, 0xf7, 0xf7,
203 | 0x77, 0x08, 0xf5, 0x25, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xc2, 0xf7, 0xf7,
204 | 0x77, 0x18, 0xf5, 0x05, 0x88, 0xd3, 0xa9, 0x66, 0xf2, 0xc2, 0xf7, 0xf7,
205 | 0x77, 0x28, 0xf5, 0x78, 0xd3, 0xa9, 0x66, 0xf2, 0xd2, 0xf7, 0xf7, 0x77,
206 | 0x38, 0xd5, 0x78, 0xc3, 0xb9, 0x76, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48,
207 | 0xa5, 0x88, 0xc3, 0xb9, 0x76, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85,
208 | 0x88, 0xb3, 0xc9, 0x86, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98,
209 | 0xb3, 0xc9, 0x86, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa3,
210 | 0xc9, 0xa6, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x93, 0xd9, 0xa6,
211 | 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x83, 0xe9, 0xb6, 0xf2, 0xf2,
212 | 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x73, 0xf9, 0xb6, 0xf2, 0xf2, 0xf7, 0xf7,
213 | 0x77, 0xf8, 0x38, 0x63, 0xf9, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77,
214 | 0xf8, 0x28, 0x53, 0xf9, 0x09, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77,
215 | 0xf8, 0x28, 0x43, 0xf9, 0x09, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77,
216 | 0xf8, 0x18, 0x33, 0xf9, 0x19, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77,
217 | 0xf8, 0x18, 0x23, 0xf9, 0x19, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77,
218 | 0xf8, 0x08, 0x13, 0xf9, 0x29, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77,
219 | 0xf8, 0x08, 0x03, 0xf9, 0x29, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7,
220 | 0x77, 0xf8, 0xf9, 0x39, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77,
221 | 0xf8, 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8,
222 | 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9,
223 | 0x19, 0xf6, 0x26, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0x09,
224 | 0xf6, 0x36, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0xf6, 0x56,
225 | 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe9, 0xf6, 0x66, 0xf2, 0xf2,
226 | 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x32, 0xf7,
227 | 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77,
228 | 0xc8, 0xc9, 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc9,
229 | 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb9, 0xf6, 0x96,
230 | 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2,
231 | 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7,
232 | 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77,
233 | 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8
234 | };
235 |
236 | #define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s }
237 |
238 | static const icon_data_t icons[] = {
239 | ICON_(16),
240 | ICON_(32),
241 | ICON_(48),
242 | ICON_(64),
243 | ICON_(128)
244 | };
245 |
246 | #endif /* ICON_DATA_H */
247 |
248 |
--------------------------------------------------------------------------------
/thumbs.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _THUMBS_CONFIG
21 | #include "config.h"
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 |
31 | #if HAVE_LIBEXIF
32 | #include
33 | void exif_auto_orientate(const fileinfo_t*);
34 | #endif
35 | Imlib_Image img_open(const fileinfo_t*);
36 |
37 | static char *cache_dir;
38 |
39 | char* tns_cache_filepath(const char *filepath)
40 | {
41 | size_t len;
42 | char *cfile = NULL;
43 |
44 | if (*filepath != '/')
45 | return NULL;
46 |
47 | if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) {
48 | /* don't cache images inside the cache directory! */
49 | len = strlen(cache_dir) + strlen(filepath) + 2;
50 | cfile = (char*) emalloc(len);
51 | snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1);
52 | }
53 | return cfile;
54 | }
55 |
56 | Imlib_Image tns_cache_load(const char *filepath, bool *outdated)
57 | {
58 | char *cfile;
59 | struct stat cstats, fstats;
60 | Imlib_Image im = NULL;
61 |
62 | if (stat(filepath, &fstats) < 0)
63 | return NULL;
64 |
65 | if ((cfile = tns_cache_filepath(filepath)) != NULL) {
66 | if (stat(cfile, &cstats) == 0) {
67 | if (cstats.st_mtime == fstats.st_mtime)
68 | im = imlib_load_image(cfile);
69 | else
70 | *outdated = true;
71 | }
72 | free(cfile);
73 | }
74 | return im;
75 | }
76 |
77 | void tns_cache_write(Imlib_Image im, const char *filepath, bool force)
78 | {
79 | char *cfile, *dirend;
80 | struct stat cstats, fstats;
81 | struct utimbuf times;
82 | Imlib_Load_Error err;
83 |
84 | if (options->private_mode)
85 | return;
86 |
87 | if (stat(filepath, &fstats) < 0)
88 | return;
89 |
90 | if ((cfile = tns_cache_filepath(filepath)) != NULL) {
91 | if (force || stat(cfile, &cstats) < 0 ||
92 | cstats.st_mtime != fstats.st_mtime)
93 | {
94 | if ((dirend = strrchr(cfile, '/')) != NULL) {
95 | *dirend = '\0';
96 | if (r_mkdir(cfile) == -1) {
97 | error(0, errno, "%s", cfile);
98 | goto end;
99 | }
100 | *dirend = '/';
101 | }
102 | imlib_context_set_image(im);
103 | if (imlib_image_has_alpha()) {
104 | imlib_image_set_format("png");
105 | } else {
106 | imlib_image_set_format("jpg");
107 | imlib_image_attach_data_value("quality", NULL, 90, NULL);
108 | }
109 | imlib_save_image_with_error_return(cfile, &err);
110 | if (err)
111 | goto end;
112 | times.actime = fstats.st_atime;
113 | times.modtime = fstats.st_mtime;
114 | utime(cfile, ×);
115 | }
116 | end:
117 | free(cfile);
118 | }
119 | }
120 |
121 | void tns_clean_cache(tns_t *tns)
122 | {
123 | int dirlen;
124 | char *cfile, *filename;
125 | r_dir_t dir;
126 |
127 | if (r_opendir(&dir, cache_dir, true) < 0) {
128 | error(0, errno, "%s", cache_dir);
129 | return;
130 | }
131 |
132 | dirlen = strlen(cache_dir);
133 |
134 | while ((cfile = r_readdir(&dir, false)) != NULL) {
135 | filename = cfile + dirlen;
136 | if (access(filename, F_OK) < 0) {
137 | if (unlink(cfile) < 0)
138 | error(0, errno, "%s", cfile);
139 | }
140 | free(cfile);
141 | }
142 | r_closedir(&dir);
143 | }
144 |
145 |
146 | void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel,
147 | win_t *win)
148 | {
149 | int len;
150 | const char *homedir, *dsuffix = "";
151 |
152 | if (cnt != NULL && *cnt > 0) {
153 | tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t));
154 | memset(tns->thumbs, 0, *cnt * sizeof(thumb_t));
155 | } else {
156 | tns->thumbs = NULL;
157 | }
158 | tns->files = files;
159 | tns->cnt = cnt;
160 | tns->initnext = tns->loadnext = 0;
161 | tns->first = tns->end = tns->r_first = tns->r_end = 0;
162 | tns->sel = sel;
163 | tns->win = win;
164 | tns->dirty = false;
165 |
166 | tns->zl = THUMB_SIZE;
167 | tns_zoom(tns, 0);
168 |
169 | if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') {
170 | homedir = getenv("HOME");
171 | dsuffix = "/.cache";
172 | }
173 | if (homedir != NULL) {
174 | free(cache_dir);
175 | len = strlen(homedir) + strlen(dsuffix) + 6;
176 | cache_dir = (char*) emalloc(len);
177 | snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix);
178 | } else {
179 | error(0, 0, "Cache directory not found");
180 | }
181 | }
182 |
183 | CLEANUP void tns_free(tns_t *tns)
184 | {
185 | int i;
186 |
187 | if (tns->thumbs != NULL) {
188 | for (i = 0; i < *tns->cnt; i++) {
189 | if (tns->thumbs[i].im != NULL) {
190 | imlib_context_set_image(tns->thumbs[i].im);
191 | imlib_free_image();
192 | }
193 | }
194 | free(tns->thumbs);
195 | tns->thumbs = NULL;
196 | }
197 |
198 | free(cache_dir);
199 | cache_dir = NULL;
200 | }
201 |
202 | Imlib_Image tns_scale_down(Imlib_Image im, int dim)
203 | {
204 | int w, h;
205 | float z, zw, zh;
206 |
207 | imlib_context_set_image(im);
208 | w = imlib_image_get_width();
209 | h = imlib_image_get_height();
210 | zw = (float) dim / (float) w;
211 | zh = (float) dim / (float) h;
212 | z = MIN(zw, zh);
213 | z = MIN(z, 1.0);
214 |
215 | if (z < 1.0) {
216 | imlib_context_set_anti_alias(1);
217 | im = imlib_create_cropped_scaled_image(0, 0, w, h,
218 | MAX(z * w, 1), MAX(z * h, 1));
219 | if (im == NULL)
220 | error(EXIT_FAILURE, ENOMEM, NULL);
221 | imlib_free_image_and_decache();
222 | }
223 | return im;
224 | }
225 |
226 | bool tns_load(tns_t *tns, int n, bool force, bool cache_only)
227 | {
228 | int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1];
229 | bool cache_hit = false;
230 | char *cfile;
231 | thumb_t *t;
232 | fileinfo_t *file;
233 | Imlib_Image im = NULL;
234 |
235 | if (n < 0 || n >= *tns->cnt)
236 | return false;
237 | file = &tns->files[n];
238 | if (file->name == NULL || file->path == NULL)
239 | return false;
240 |
241 | t = &tns->thumbs[n];
242 |
243 | if (t->im != NULL) {
244 | imlib_context_set_image(t->im);
245 | imlib_free_image();
246 | t->im = NULL;
247 | }
248 |
249 | if (!force) {
250 | if ((im = tns_cache_load(file->path, &force)) != NULL) {
251 | imlib_context_set_image(im);
252 | if (imlib_image_get_width() < maxwh &&
253 | imlib_image_get_height() < maxwh)
254 | {
255 | if ((cfile = tns_cache_filepath(file->path)) != NULL) {
256 | unlink(cfile);
257 | free(cfile);
258 | }
259 | imlib_free_image_and_decache();
260 | im = NULL;
261 | } else {
262 | cache_hit = true;
263 | }
264 | #if HAVE_LIBEXIF
265 | } else if (!force && !options->private_mode) {
266 | int pw = 0, ph = 0, w, h, x = 0, y = 0;
267 | bool err;
268 | float zw, zh;
269 | ExifData *ed;
270 | ExifEntry *entry;
271 | ExifContent *ifd;
272 | ExifByteOrder byte_order;
273 | int tmpfd;
274 | char tmppath[] = "/tmp/sxiv-XXXXXX";
275 | Imlib_Image tmpim;
276 |
277 | if ((ed = exif_data_new_from_file(file->path)) != NULL) {
278 | if (ed->data != NULL && ed->size > 0 &&
279 | (tmpfd = mkstemp(tmppath)) >= 0)
280 | {
281 | err = write(tmpfd, ed->data, ed->size) != ed->size;
282 | close(tmpfd);
283 |
284 | if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) {
285 | byte_order = exif_data_get_byte_order(ed);
286 | ifd = ed->ifd[EXIF_IFD_EXIF];
287 | entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION);
288 | if (entry != NULL)
289 | pw = exif_get_long(entry->data, byte_order);
290 | entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION);
291 | if (entry != NULL)
292 | ph = exif_get_long(entry->data, byte_order);
293 |
294 | imlib_context_set_image(tmpim);
295 | w = imlib_image_get_width();
296 | h = imlib_image_get_height();
297 |
298 | if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) {
299 | zw = (float) pw / (float) w;
300 | zh = (float) ph / (float) h;
301 | if (zw < zh) {
302 | pw /= zh;
303 | x = (w - pw) / 2;
304 | w = pw;
305 | } else if (zw > zh) {
306 | ph /= zw;
307 | y = (h - ph) / 2;
308 | h = ph;
309 | }
310 | }
311 | if (w >= maxwh || h >= maxwh) {
312 | if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL)
313 | error(EXIT_FAILURE, ENOMEM, NULL);
314 | }
315 | imlib_free_image_and_decache();
316 | }
317 | unlink(tmppath);
318 | }
319 | exif_data_unref(ed);
320 | }
321 | #endif
322 | }
323 | }
324 |
325 | if (im == NULL) {
326 | if ((im = img_open(file)) == NULL)
327 | return false;
328 | }
329 | imlib_context_set_image(im);
330 |
331 | if (!cache_hit) {
332 | #if HAVE_LIBEXIF
333 | exif_auto_orientate(file);
334 | #endif
335 | im = tns_scale_down(im, maxwh);
336 | imlib_context_set_image(im);
337 | if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh)
338 | tns_cache_write(im, file->path, true);
339 | }
340 |
341 | if (cache_only) {
342 | imlib_free_image_and_decache();
343 | } else {
344 | t->im = tns_scale_down(im, thumb_sizes[tns->zl]);
345 | imlib_context_set_image(t->im);
346 | t->w = imlib_image_get_width();
347 | t->h = imlib_image_get_height();
348 | tns->dirty = true;
349 | }
350 | file->flags |= FF_TN_INIT;
351 |
352 | if (n == tns->initnext)
353 | while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT));
354 | if (n == tns->loadnext && !cache_only)
355 | while (++tns->loadnext < tns->end && (++t)->im != NULL);
356 |
357 | return true;
358 | }
359 |
360 | void tns_unload(tns_t *tns, int n)
361 | {
362 | thumb_t *t;
363 |
364 | if (n < 0 || n >= *tns->cnt)
365 | return;
366 |
367 | t = &tns->thumbs[n];
368 |
369 | if (t->im != NULL) {
370 | imlib_context_set_image(t->im);
371 | imlib_free_image();
372 | t->im = NULL;
373 | }
374 | }
375 |
376 | void tns_check_view(tns_t *tns, bool scrolled)
377 | {
378 | int r;
379 |
380 | if (tns == NULL)
381 | return;
382 |
383 | tns->first -= tns->first % tns->cols;
384 | r = *tns->sel % tns->cols;
385 |
386 | if (scrolled) {
387 | /* move selection into visible area */
388 | if (*tns->sel >= tns->first + tns->cols * tns->rows)
389 | *tns->sel = tns->first + r + tns->cols * (tns->rows - 1);
390 | else if (*tns->sel < tns->first)
391 | *tns->sel = tns->first + r;
392 | } else {
393 | /* scroll to selection */
394 | if (tns->first + tns->cols * tns->rows <= *tns->sel) {
395 | tns->first = *tns->sel - r - tns->cols * (tns->rows - 1);
396 | tns->dirty = true;
397 | } else if (tns->first > *tns->sel) {
398 | tns->first = *tns->sel - r;
399 | tns->dirty = true;
400 | }
401 | }
402 | }
403 |
404 | void tns_render(tns_t *tns)
405 | {
406 | thumb_t *t;
407 | win_t *win;
408 | int i, cnt, r, x, y;
409 |
410 | if (!tns->dirty)
411 | return;
412 |
413 | win = tns->win;
414 | win_clear(win);
415 | imlib_context_set_drawable(win->buf.pm);
416 |
417 | tns->cols = MAX(1, win->w / tns->dim);
418 | tns->rows = MAX(1, win->h / tns->dim);
419 |
420 | if (*tns->cnt < tns->cols * tns->rows) {
421 | tns->first = 0;
422 | cnt = *tns->cnt;
423 | } else {
424 | tns_check_view(tns, false);
425 | cnt = tns->cols * tns->rows;
426 | if ((r = tns->first + cnt - *tns->cnt) >= tns->cols)
427 | tns->first -= r - r % tns->cols;
428 | if (r > 0)
429 | cnt -= r % tns->cols;
430 | }
431 | r = cnt % tns->cols ? 1 : 0;
432 | tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3;
433 | tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3;
434 | tns->loadnext = *tns->cnt;
435 | tns->end = tns->first + cnt;
436 |
437 | for (i = tns->r_first; i < tns->r_end; i++) {
438 | if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL)
439 | tns_unload(tns, i);
440 | }
441 | tns->r_first = tns->first;
442 | tns->r_end = tns->end;
443 |
444 | for (i = tns->first; i < tns->end; i++) {
445 | t = &tns->thumbs[i];
446 | if (t->im != NULL) {
447 | t->x = x + (thumb_sizes[tns->zl] - t->w) / 2;
448 | t->y = y + (thumb_sizes[tns->zl] - t->h) / 2;
449 | imlib_context_set_image(t->im);
450 | imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h);
451 | if (tns->files[i].flags & FF_MARK)
452 | tns_mark(tns, i, true);
453 | } else {
454 | tns->loadnext = MIN(tns->loadnext, i);
455 | }
456 | if ((i + 1) % tns->cols == 0) {
457 | x = tns->x;
458 | y += tns->dim;
459 | } else {
460 | x += tns->dim;
461 | }
462 | }
463 | tns->dirty = false;
464 | tns_highlight(tns, *tns->sel, true);
465 | }
466 |
467 | void tns_mark(tns_t *tns, int n, bool mark)
468 | {
469 | if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) {
470 | win_t *win = tns->win;
471 | thumb_t *t = &tns->thumbs[n];
472 | unsigned long col = win->bg.pixel;
473 | int x = t->x + t->w, y = t->y + t->h;
474 |
475 | win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col);
476 | win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col);
477 |
478 | if (mark)
479 | col = win->fg.pixel;
480 |
481 | win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col);
482 |
483 | if (!mark && n == *tns->sel)
484 | tns_highlight(tns, n, true);
485 | }
486 | }
487 |
488 | void tns_highlight(tns_t *tns, int n, bool hl)
489 | {
490 | if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) {
491 | win_t *win = tns->win;
492 | thumb_t *t = &tns->thumbs[n];
493 | unsigned long col = hl ? win->fg.pixel : win->bg.pixel;
494 | int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2;
495 |
496 | win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh,
497 | false, tns->bw, col);
498 |
499 | if (tns->files[n].flags & FF_MARK)
500 | tns_mark(tns, n, true);
501 | }
502 | }
503 |
504 | bool tns_move_selection(tns_t *tns, direction_t dir, int cnt)
505 | {
506 | int old, max;
507 |
508 | old = *tns->sel;
509 | cnt = cnt > 1 ? cnt : 1;
510 |
511 | switch (dir) {
512 | case DIR_UP:
513 | *tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols);
514 | break;
515 | case DIR_DOWN:
516 | max = tns->cols * ((*tns->cnt - 1) / tns->cols) +
517 | MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols);
518 | *tns->sel = MIN(*tns->sel + cnt * tns->cols, max);
519 | break;
520 | case DIR_LEFT:
521 | *tns->sel = MAX(*tns->sel - cnt, 0);
522 | break;
523 | case DIR_RIGHT:
524 | *tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1);
525 | break;
526 | }
527 |
528 | if (*tns->sel != old) {
529 | tns_highlight(tns, old, false);
530 | tns_check_view(tns, false);
531 | if (!tns->dirty)
532 | tns_highlight(tns, *tns->sel, true);
533 | }
534 | return *tns->sel != old;
535 | }
536 |
537 | bool tns_scroll(tns_t *tns, direction_t dir, bool screen)
538 | {
539 | int d, max, old;
540 |
541 | old = tns->first;
542 | d = tns->cols * (screen ? tns->rows : 1);
543 |
544 | if (dir == DIR_DOWN) {
545 | max = *tns->cnt - tns->cols * tns->rows;
546 | if (*tns->cnt % tns->cols != 0)
547 | max += tns->cols - *tns->cnt % tns->cols;
548 | tns->first = MIN(tns->first + d, max);
549 | } else if (dir == DIR_UP) {
550 | tns->first = MAX(tns->first - d, 0);
551 | }
552 |
553 | if (tns->first != old) {
554 | tns_check_view(tns, true);
555 | tns->dirty = true;
556 | }
557 | return tns->first != old;
558 | }
559 |
560 | bool tns_zoom(tns_t *tns, int d)
561 | {
562 | int i, oldzl;
563 |
564 | oldzl = tns->zl;
565 | tns->zl += -(d < 0) + (d > 0);
566 | tns->zl = MAX(tns->zl, 0);
567 | tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1);
568 |
569 | tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1;
570 | tns->bw = MIN(tns->bw, 4);
571 | tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6;
572 |
573 | if (tns->zl != oldzl) {
574 | for (i = 0; i < *tns->cnt; i++)
575 | tns_unload(tns, i);
576 | tns->dirty = true;
577 | }
578 | return tns->zl != oldzl;
579 | }
580 |
581 | int tns_translate(tns_t *tns, int x, int y)
582 | {
583 | int n;
584 |
585 | if (x < tns->x || y < tns->y)
586 | return -1;
587 |
588 | n = tns->first + (y - tns->y) / tns->dim * tns->cols +
589 | (x - tns->x) / tns->dim;
590 | if (n >= *tns->cnt)
591 | n = -1;
592 |
593 | return n;
594 | }
595 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/image.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011, 2012 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _IMAGE_CONFIG
21 | #include "config.h"
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 |
30 | #if HAVE_LIBEXIF
31 | #include
32 | #endif
33 |
34 | #if HAVE_GIFLIB
35 | #include
36 | enum { DEF_GIF_DELAY = 75 };
37 | #endif
38 |
39 | float zoom_min;
40 | float zoom_max;
41 |
42 | static int zoomdiff(img_t *img, float z)
43 | {
44 | return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom));
45 | }
46 |
47 | void img_init(img_t *img, win_t *win)
48 | {
49 | zoom_min = zoom_levels[0] / 100.0;
50 | zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0;
51 |
52 | imlib_context_set_display(win->env.dpy);
53 | imlib_context_set_visual(win->env.vis);
54 | imlib_context_set_colormap(win->env.cmap);
55 |
56 | img->im = NULL;
57 | img->win = win;
58 | img->scalemode = options->scalemode;
59 | img->zoom = options->zoom;
60 | img->zoom = MAX(img->zoom, zoom_min);
61 | img->zoom = MIN(img->zoom, zoom_max);
62 | img->checkpan = false;
63 | img->dirty = false;
64 | img->aa = ANTI_ALIAS;
65 | img->alpha = ALPHA_LAYER;
66 | img->multi.cap = img->multi.cnt = 0;
67 | img->multi.animate = options->animate;
68 | img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0;
69 | img->multi.length = 0;
70 |
71 | img->cmod = imlib_create_color_modifier();
72 | imlib_context_set_color_modifier(img->cmod);
73 | img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE);
74 |
75 | img->ss.on = options->slideshow > 0;
76 | img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10;
77 | }
78 |
79 | #if HAVE_LIBEXIF
80 | void exif_auto_orientate(const fileinfo_t *file)
81 | {
82 | ExifData *ed;
83 | ExifEntry *entry;
84 | int byte_order, orientation = 0;
85 |
86 | if ((ed = exif_data_new_from_file(file->path)) == NULL)
87 | return;
88 | byte_order = exif_data_get_byte_order(ed);
89 | entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
90 | if (entry != NULL)
91 | orientation = exif_get_short(entry->data, byte_order);
92 | exif_data_unref(ed);
93 |
94 | switch (orientation) {
95 | case 5:
96 | imlib_image_orientate(1);
97 | case 2:
98 | imlib_image_flip_vertical();
99 | break;
100 | case 3:
101 | imlib_image_orientate(2);
102 | break;
103 | case 7:
104 | imlib_image_orientate(1);
105 | case 4:
106 | imlib_image_flip_horizontal();
107 | break;
108 | case 6:
109 | imlib_image_orientate(1);
110 | break;
111 | case 8:
112 | imlib_image_orientate(3);
113 | break;
114 | }
115 | }
116 | #endif
117 |
118 | #if HAVE_GIFLIB
119 | bool img_load_gif(img_t *img, const fileinfo_t *file)
120 | {
121 | GifFileType *gif;
122 | GifRowType *rows = NULL;
123 | GifRecordType rec;
124 | ColorMapObject *cmap;
125 | DATA32 bgpixel, *data, *ptr;
126 | DATA32 *prev_frame = NULL;
127 | Imlib_Image im;
128 | int i, j, bg, r, g, b;
129 | int x, y, w, h, sw, sh;
130 | int px, py, pw, ph;
131 | int intoffset[] = { 0, 4, 2, 1 };
132 | int intjump[] = { 8, 8, 4, 2 };
133 | int transp = -1;
134 | unsigned int disposal = 0, prev_disposal = 0;
135 | unsigned int delay = 0;
136 | bool err = false;
137 |
138 | if (img->multi.cap == 0) {
139 | img->multi.cap = 8;
140 | img->multi.frames = (img_frame_t*)
141 | emalloc(sizeof(img_frame_t) * img->multi.cap);
142 | }
143 | img->multi.cnt = img->multi.sel = 0;
144 | img->multi.length = 0;
145 |
146 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
147 | gif = DGifOpenFileName(file->path, NULL);
148 | #else
149 | gif = DGifOpenFileName(file->path);
150 | #endif
151 | if (gif == NULL) {
152 | error(0, 0, "%s: Error opening gif image", file->name);
153 | return false;
154 | }
155 | bg = gif->SBackGroundColor;
156 | sw = gif->SWidth;
157 | sh = gif->SHeight;
158 | px = py = pw = ph = 0;
159 |
160 | do {
161 | if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
162 | err = true;
163 | break;
164 | }
165 | if (rec == EXTENSION_RECORD_TYPE) {
166 | int ext_code;
167 | GifByteType *ext = NULL;
168 |
169 | DGifGetExtension(gif, &ext_code, &ext);
170 | while (ext) {
171 | if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
172 | if (ext[1] & 1)
173 | transp = (int) ext[4];
174 | else
175 | transp = -1;
176 |
177 | delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
178 | disposal = (unsigned int) ext[1] >> 2 & 0x7;
179 | }
180 | ext = NULL;
181 | DGifGetExtensionNext(gif, &ext);
182 | }
183 | } else if (rec == IMAGE_DESC_RECORD_TYPE) {
184 | if (DGifGetImageDesc(gif) == GIF_ERROR) {
185 | err = true;
186 | break;
187 | }
188 | x = gif->Image.Left;
189 | y = gif->Image.Top;
190 | w = gif->Image.Width;
191 | h = gif->Image.Height;
192 |
193 | rows = (GifRowType*) emalloc(h * sizeof(GifRowType));
194 | for (i = 0; i < h; i++)
195 | rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType));
196 | if (gif->Image.Interlace) {
197 | for (i = 0; i < 4; i++) {
198 | for (j = intoffset[i]; j < h; j += intjump[i])
199 | DGifGetLine(gif, rows[j], w);
200 | }
201 | } else {
202 | for (i = 0; i < h; i++)
203 | DGifGetLine(gif, rows[i], w);
204 | }
205 |
206 | ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh);
207 | cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
208 | r = cmap->Colors[bg].Red;
209 | g = cmap->Colors[bg].Green;
210 | b = cmap->Colors[bg].Blue;
211 | bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
212 |
213 | for (i = 0; i < sh; i++) {
214 | for (j = 0; j < sw; j++) {
215 | if (i < y || i >= y + h || j < x || j >= x + w ||
216 | rows[i-y][j-x] == transp)
217 | {
218 | if (prev_frame != NULL && (prev_disposal != 2 ||
219 | i < py || i >= py + ph || j < px || j >= px + pw))
220 | {
221 | *ptr = prev_frame[i * sw + j];
222 | } else {
223 | *ptr = bgpixel;
224 | }
225 | } else {
226 | r = cmap->Colors[rows[i-y][j-x]].Red;
227 | g = cmap->Colors[rows[i-y][j-x]].Green;
228 | b = cmap->Colors[rows[i-y][j-x]].Blue;
229 | *ptr = 0xffu << 24 | r << 16 | g << 8 | b;
230 | }
231 | ptr++;
232 | }
233 | }
234 |
235 | im = imlib_create_image_using_copied_data(sw, sh, data);
236 |
237 | for (i = 0; i < h; i++)
238 | free(rows[i]);
239 | free(rows);
240 | free(data);
241 |
242 | if (im == NULL) {
243 | err = true;
244 | break;
245 | }
246 |
247 | imlib_context_set_image(im);
248 | imlib_image_set_format("gif");
249 | if (transp >= 0)
250 | imlib_image_set_has_alpha(1);
251 |
252 | if (disposal != 3)
253 | prev_frame = imlib_image_get_data_for_reading_only();
254 | prev_disposal = disposal;
255 | px = x, py = y, pw = w, ph = h;
256 |
257 | if (img->multi.cnt == img->multi.cap) {
258 | img->multi.cap *= 2;
259 | img->multi.frames = (img_frame_t*)
260 | erealloc(img->multi.frames,
261 | img->multi.cap * sizeof(img_frame_t));
262 | }
263 | img->multi.frames[img->multi.cnt].im = im;
264 | delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay;
265 | img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY;
266 | img->multi.length += img->multi.frames[img->multi.cnt].delay;
267 | img->multi.cnt++;
268 | }
269 | } while (rec != TERMINATE_RECORD_TYPE);
270 |
271 | #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
272 | DGifCloseFile(gif, NULL);
273 | #else
274 | DGifCloseFile(gif);
275 | #endif
276 |
277 | if (err && (file->flags & FF_WARN))
278 | error(0, 0, "%s: Corrupted gif file", file->name);
279 |
280 | if (img->multi.cnt > 1) {
281 | imlib_context_set_image(img->im);
282 | imlib_free_image();
283 | img->im = img->multi.frames[0].im;
284 | } else if (img->multi.cnt == 1) {
285 | imlib_context_set_image(img->multi.frames[0].im);
286 | imlib_free_image();
287 | img->multi.cnt = 0;
288 | }
289 |
290 | imlib_context_set_image(img->im);
291 |
292 | return !err;
293 | }
294 | #endif /* HAVE_GIFLIB */
295 |
296 | Imlib_Image img_open(const fileinfo_t *file)
297 | {
298 | struct stat st;
299 | Imlib_Image im = NULL;
300 |
301 | if (access(file->path, R_OK) == 0 &&
302 | stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
303 | {
304 | im = imlib_load_image(file->path);
305 | if (im != NULL) {
306 | imlib_context_set_image(im);
307 | if (imlib_image_get_data_for_reading_only() == NULL) {
308 | imlib_free_image();
309 | im = NULL;
310 | }
311 | }
312 | }
313 | if (im == NULL && (file->flags & FF_WARN))
314 | error(0, 0, "%s: Error opening image", file->name);
315 | return im;
316 | }
317 |
318 | bool img_load(img_t *img, const fileinfo_t *file)
319 | {
320 | const char *fmt;
321 |
322 | if ((img->im = img_open(file)) == NULL)
323 | return false;
324 |
325 | imlib_image_set_changes_on_disk();
326 |
327 | #if HAVE_LIBEXIF
328 | exif_auto_orientate(file);
329 | #endif
330 |
331 | if ((fmt = imlib_image_format()) != NULL) {
332 | #if HAVE_GIFLIB
333 | if (STREQ(fmt, "gif"))
334 | img_load_gif(img, file);
335 | #endif
336 | }
337 | img->w = imlib_image_get_width();
338 | img->h = imlib_image_get_height();
339 | img->checkpan = true;
340 | img->dirty = true;
341 |
342 | return true;
343 | }
344 |
345 | CLEANUP void img_close(img_t *img, bool decache)
346 | {
347 | int i;
348 |
349 | if (img->multi.cnt > 0) {
350 | for (i = 0; i < img->multi.cnt; i++) {
351 | imlib_context_set_image(img->multi.frames[i].im);
352 | imlib_free_image();
353 | }
354 | img->multi.cnt = 0;
355 | img->im = NULL;
356 | } else if (img->im != NULL) {
357 | imlib_context_set_image(img->im);
358 | if (decache)
359 | imlib_free_image_and_decache();
360 | else
361 | imlib_free_image();
362 | img->im = NULL;
363 | }
364 | }
365 |
366 | void img_check_pan(img_t *img, bool moved)
367 | {
368 | win_t *win;
369 | float w, h, ox, oy;
370 |
371 | win = img->win;
372 | w = img->w * img->zoom;
373 | h = img->h * img->zoom;
374 | ox = img->x;
375 | oy = img->y;
376 |
377 | if (w < win->w)
378 | img->x = (win->w - w) / 2;
379 | else if (img->x > 0)
380 | img->x = 0;
381 | else if (img->x + w < win->w)
382 | img->x = win->w - w;
383 | if (h < win->h)
384 | img->y = (win->h - h) / 2;
385 | else if (img->y > 0)
386 | img->y = 0;
387 | else if (img->y + h < win->h)
388 | img->y = win->h - h;
389 |
390 | if (!moved && (ox != img->x || oy != img->y))
391 | img->dirty = true;
392 | }
393 |
394 | bool img_fit(img_t *img)
395 | {
396 | float z, zw, zh;
397 |
398 | if (img->scalemode == SCALE_ZOOM)
399 | return false;
400 |
401 | zw = (float) img->win->w / (float) img->w;
402 | zh = (float) img->win->h / (float) img->h;
403 |
404 | switch (img->scalemode) {
405 | case SCALE_WIDTH:
406 | z = zw;
407 | break;
408 | case SCALE_HEIGHT:
409 | z = zh;
410 | break;
411 | default:
412 | z = MIN(zw, zh);
413 | break;
414 | }
415 | z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max);
416 |
417 | if (zoomdiff(img, z) != 0) {
418 | img->zoom = z;
419 | img->dirty = true;
420 | return true;
421 | } else {
422 | return false;
423 | }
424 | }
425 |
426 | void img_render(img_t *img)
427 | {
428 | win_t *win;
429 | int sx, sy, sw, sh;
430 | int dx, dy, dw, dh;
431 | Imlib_Image bg;
432 | unsigned long c;
433 |
434 | win = img->win;
435 | img_fit(img);
436 |
437 | if (img->checkpan) {
438 | img_check_pan(img, false);
439 | img->checkpan = false;
440 | }
441 |
442 | if (!img->dirty)
443 | return;
444 |
445 | /* calculate source and destination offsets:
446 | * - part of image drawn on full window, or
447 | * - full image drawn on part of window
448 | */
449 | if (img->x <= 0) {
450 | sx = -img->x / img->zoom + 0.5;
451 | sw = win->w / img->zoom;
452 | dx = 0;
453 | dw = win->w;
454 | } else {
455 | sx = 0;
456 | sw = img->w;
457 | dx = img->x;
458 | dw = img->w * img->zoom;
459 | }
460 | if (img->y <= 0) {
461 | sy = -img->y / img->zoom + 0.5;
462 | sh = win->h / img->zoom;
463 | dy = 0;
464 | dh = win->h;
465 | } else {
466 | sy = 0;
467 | sh = img->h;
468 | dy = img->y;
469 | dh = img->h * img->zoom;
470 | }
471 |
472 | win_clear(win);
473 |
474 | imlib_context_set_image(img->im);
475 | imlib_context_set_anti_alias(img->aa);
476 | imlib_context_set_drawable(win->buf.pm);
477 |
478 | if (imlib_image_has_alpha()) {
479 | if ((bg = imlib_create_image(dw, dh)) == NULL)
480 | error(EXIT_FAILURE, ENOMEM, NULL);
481 | imlib_context_set_image(bg);
482 | imlib_image_set_has_alpha(0);
483 |
484 | if (img->alpha) {
485 | int i, c, r;
486 | DATA32 col[2] = { 0xFF666666, 0xFF999999 };
487 | DATA32 * data = imlib_image_get_data();
488 |
489 | for (r = 0; r < dh; r++) {
490 | i = r * dw;
491 | if (r == 0 || r == 8) {
492 | for (c = 0; c < dw; c++)
493 | data[i++] = col[!(c & 8) ^ !r];
494 | } else {
495 | memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0]));
496 | }
497 | }
498 | imlib_image_put_back_data(data);
499 | } else {
500 | c = win->bg.pixel;
501 | imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF);
502 | imlib_image_fill_rectangle(0, 0, dw, dh);
503 | }
504 | imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
505 | imlib_context_set_color_modifier(NULL);
506 | imlib_render_image_on_drawable(dx, dy);
507 | imlib_free_image();
508 | imlib_context_set_color_modifier(img->cmod);
509 | } else {
510 | imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
511 | }
512 | img->dirty = false;
513 | }
514 |
515 | bool img_fit_win(img_t *img, scalemode_t sm)
516 | {
517 | float oz;
518 |
519 | oz = img->zoom;
520 | img->scalemode = sm;
521 |
522 | if (img_fit(img)) {
523 | img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz;
524 | img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz;
525 | img->checkpan = true;
526 | return true;
527 | } else {
528 | return false;
529 | }
530 | }
531 |
532 | bool img_zoom(img_t *img, float z)
533 | {
534 | z = MAX(z, zoom_min);
535 | z = MIN(z, zoom_max);
536 |
537 | img->scalemode = SCALE_ZOOM;
538 |
539 | if (zoomdiff(img, z) != 0) {
540 | int x, y;
541 |
542 | win_cursor_pos(img->win, &x, &y);
543 | if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) {
544 | x = img->win->w / 2;
545 | y = img->win->h / 2;
546 | }
547 | img->x = x - (x - img->x) * z / img->zoom;
548 | img->y = y - (y - img->y) * z / img->zoom;
549 | img->zoom = z;
550 | img->checkpan = true;
551 | img->dirty = true;
552 | return true;
553 | } else {
554 | return false;
555 | }
556 | }
557 |
558 | bool img_zoom_in(img_t *img)
559 | {
560 | int i;
561 | float z;
562 |
563 | for (i = 0; i < ARRLEN(zoom_levels); i++) {
564 | z = zoom_levels[i] / 100.0;
565 | if (zoomdiff(img, z) > 0)
566 | return img_zoom(img, z);
567 | }
568 | return false;
569 | }
570 |
571 | bool img_zoom_out(img_t *img)
572 | {
573 | int i;
574 | float z;
575 |
576 | for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) {
577 | z = zoom_levels[i] / 100.0;
578 | if (zoomdiff(img, z) < 0)
579 | return img_zoom(img, z);
580 | }
581 | return false;
582 | }
583 |
584 | bool img_pos(img_t *img, float x, float y)
585 | {
586 | float ox, oy;
587 |
588 | ox = img->x;
589 | oy = img->y;
590 |
591 | img->x = x;
592 | img->y = y;
593 |
594 | img_check_pan(img, true);
595 |
596 | if (ox != img->x || oy != img->y) {
597 | img->dirty = true;
598 | return true;
599 | } else {
600 | return false;
601 | }
602 | }
603 |
604 | bool img_move(img_t *img, float dx, float dy)
605 | {
606 | return img_pos(img, img->x + dx, img->y + dy);
607 | }
608 |
609 | bool img_pan(img_t *img, direction_t dir, int d)
610 | {
611 | /* d < 0: screen-wise
612 | * d = 0: 1/PAN_FRACTION of screen
613 | * d > 0: num of pixels
614 | */
615 | float x, y;
616 |
617 | if (d > 0) {
618 | x = y = MAX(1, (float) d * img->zoom);
619 | } else {
620 | x = img->win->w / (d < 0 ? 1 : PAN_FRACTION);
621 | y = img->win->h / (d < 0 ? 1 : PAN_FRACTION);
622 | }
623 |
624 | switch (dir) {
625 | case DIR_LEFT:
626 | return img_move(img, x, 0.0);
627 | case DIR_RIGHT:
628 | return img_move(img, -x, 0.0);
629 | case DIR_UP:
630 | return img_move(img, 0.0, y);
631 | case DIR_DOWN:
632 | return img_move(img, 0.0, -y);
633 | }
634 | return false;
635 | }
636 |
637 | bool img_pan_edge(img_t *img, direction_t dir)
638 | {
639 | float ox, oy;
640 |
641 | ox = img->x;
642 | oy = img->y;
643 |
644 | if (dir & DIR_LEFT)
645 | img->x = 0;
646 | if (dir & DIR_RIGHT)
647 | img->x = img->win->w - img->w * img->zoom;
648 | if (dir & DIR_UP)
649 | img->y = 0;
650 | if (dir & DIR_DOWN)
651 | img->y = img->win->h - img->h * img->zoom;
652 |
653 | img_check_pan(img, true);
654 |
655 | if (ox != img->x || oy != img->y) {
656 | img->dirty = true;
657 | return true;
658 | } else {
659 | return false;
660 | }
661 | }
662 |
663 | void img_rotate(img_t *img, degree_t d)
664 | {
665 | int i, tmp;
666 | float ox, oy;
667 |
668 | imlib_context_set_image(img->im);
669 | imlib_image_orientate(d);
670 |
671 | for (i = 0; i < img->multi.cnt; i++) {
672 | if (i != img->multi.sel) {
673 | imlib_context_set_image(img->multi.frames[i].im);
674 | imlib_image_orientate(d);
675 | }
676 | }
677 | if (d == DEGREE_90 || d == DEGREE_270) {
678 | ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom;
679 | oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom;
680 |
681 | img->x = oy + (img->win->w - img->win->h) / 2;
682 | img->y = ox + (img->win->h - img->win->w) / 2;
683 |
684 | tmp = img->w;
685 | img->w = img->h;
686 | img->h = tmp;
687 | img->checkpan = true;
688 | }
689 | img->dirty = true;
690 | }
691 |
692 | void img_flip(img_t *img, flipdir_t d)
693 | {
694 | int i;
695 | void (*imlib_flip_op[3])(void) = {
696 | imlib_image_flip_horizontal,
697 | imlib_image_flip_vertical,
698 | imlib_image_flip_diagonal
699 | };
700 |
701 | d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1;
702 |
703 | if (d < 0 || d >= ARRLEN(imlib_flip_op))
704 | return;
705 |
706 | imlib_context_set_image(img->im);
707 | imlib_flip_op[d]();
708 |
709 | for (i = 0; i < img->multi.cnt; i++) {
710 | if (i != img->multi.sel) {
711 | imlib_context_set_image(img->multi.frames[i].im);
712 | imlib_flip_op[d]();
713 | }
714 | }
715 | img->dirty = true;
716 | }
717 |
718 | void img_toggle_antialias(img_t *img)
719 | {
720 | img->aa = !img->aa;
721 | imlib_context_set_image(img->im);
722 | imlib_context_set_anti_alias(img->aa);
723 | img->dirty = true;
724 | }
725 |
726 | bool img_change_gamma(img_t *img, int d)
727 | {
728 | /* d < 0: decrease gamma
729 | * d = 0: reset gamma
730 | * d > 0: increase gamma
731 | */
732 | int gamma;
733 | double range;
734 |
735 | if (d == 0)
736 | gamma = 0;
737 | else
738 | gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE);
739 |
740 | if (img->gamma != gamma) {
741 | imlib_reset_color_modifier();
742 | if (gamma != 0) {
743 | range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0;
744 | imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE));
745 | }
746 | img->gamma = gamma;
747 | img->dirty = true;
748 | return true;
749 | } else {
750 | return false;
751 | }
752 | }
753 |
754 | bool img_frame_goto(img_t *img, int n)
755 | {
756 | if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
757 | return false;
758 |
759 | img->multi.sel = n;
760 | img->im = img->multi.frames[n].im;
761 |
762 | imlib_context_set_image(img->im);
763 | img->w = imlib_image_get_width();
764 | img->h = imlib_image_get_height();
765 | img->checkpan = true;
766 | img->dirty = true;
767 |
768 | return true;
769 | }
770 |
771 | bool img_frame_navigate(img_t *img, int d)
772 | {
773 | if (img->multi.cnt == 0 || d == 0)
774 | return false;
775 |
776 | d += img->multi.sel;
777 | if (d < 0)
778 | d = 0;
779 | else if (d >= img->multi.cnt)
780 | d = img->multi.cnt - 1;
781 |
782 | return img_frame_goto(img, d);
783 | }
784 |
785 | bool img_frame_animate(img_t *img)
786 | {
787 | if (img->multi.cnt == 0)
788 | return false;
789 |
790 | if (img->multi.sel + 1 >= img->multi.cnt)
791 | img_frame_goto(img, 0);
792 | else
793 | img_frame_goto(img, img->multi.sel + 1);
794 | img->dirty = true;
795 | return true;
796 | }
797 |
798 |
--------------------------------------------------------------------------------
/main.c:
--------------------------------------------------------------------------------
1 | /* Copyright 2011-2013 Bert Muennich
2 | *
3 | * This file is part of sxiv.
4 | *
5 | * sxiv is free software; you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published
7 | * by the Free Software Foundation; either version 2 of the License,
8 | * or (at your option) any later version.
9 | *
10 | * sxiv is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with sxiv. If not, see .
17 | */
18 |
19 | #include "sxiv.h"
20 | #define _MAPPINGS_CONFIG
21 | #include "config.h"
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 |
37 | typedef struct {
38 | struct timeval when;
39 | bool active;
40 | timeout_f handler;
41 | } timeout_t;
42 |
43 | /* timeout handler functions: */
44 | void redraw(void);
45 | void reset_cursor(void);
46 | void animate(void);
47 | void slideshow(void);
48 | void clear_resize(void);
49 |
50 | appmode_t mode;
51 | arl_t arl;
52 | img_t img;
53 | tns_t tns;
54 | win_t win;
55 |
56 | fileinfo_t *files;
57 | int filecnt, fileidx;
58 | int alternate;
59 | int markcnt;
60 | int markidx;
61 |
62 | int prefix;
63 | bool extprefix;
64 |
65 | bool resized = false;
66 |
67 | typedef struct {
68 | int err;
69 | char *cmd;
70 | } extcmd_t;
71 |
72 | struct {
73 | extcmd_t f;
74 | int fd;
75 | unsigned int i, lastsep;
76 | pid_t pid;
77 | } info;
78 |
79 | struct {
80 | extcmd_t f;
81 | bool warned;
82 | } keyhandler;
83 |
84 | timeout_t timeouts[] = {
85 | { { 0, 0 }, false, redraw },
86 | { { 0, 0 }, false, reset_cursor },
87 | { { 0, 0 }, false, animate },
88 | { { 0, 0 }, false, slideshow },
89 | { { 0, 0 }, false, clear_resize },
90 | };
91 |
92 | cursor_t imgcursor[3] = {
93 | CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW
94 | };
95 |
96 | void cleanup(void)
97 | {
98 | img_close(&img, false);
99 | arl_cleanup(&arl);
100 | tns_free(&tns);
101 | win_close(&win);
102 | }
103 |
104 | void check_add_file(char *filename, bool given)
105 | {
106 | char *path;
107 |
108 | if (*filename == '\0')
109 | return;
110 |
111 | if (access(filename, R_OK) < 0 ||
112 | (path = realpath(filename, NULL)) == NULL)
113 | {
114 | if (given)
115 | error(0, errno, "%s", filename);
116 | return;
117 | }
118 |
119 | if (fileidx == filecnt) {
120 | filecnt *= 2;
121 | files = erealloc(files, filecnt * sizeof(*files));
122 | memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files));
123 | }
124 |
125 | files[fileidx].name = estrdup(filename);
126 | files[fileidx].path = path;
127 | if (given)
128 | files[fileidx].flags |= FF_WARN;
129 | fileidx++;
130 | }
131 |
132 | void remove_file(int n, bool manual)
133 | {
134 | if (n < 0 || n >= filecnt)
135 | return;
136 |
137 | if (filecnt == 1) {
138 | if (!manual)
139 | fprintf(stderr, "sxiv: no more files to display, aborting\n");
140 | exit(manual ? EXIT_SUCCESS : EXIT_FAILURE);
141 | }
142 | if (files[n].flags & FF_MARK)
143 | markcnt--;
144 |
145 | if (files[n].path != files[n].name)
146 | free((void*) files[n].path);
147 | free((void*) files[n].name);
148 |
149 | if (n + 1 < filecnt) {
150 | if (tns.thumbs != NULL) {
151 | memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) *
152 | sizeof(*tns.thumbs));
153 | memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs));
154 | }
155 | memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files));
156 | }
157 | filecnt--;
158 | if (fileidx > n || fileidx == filecnt)
159 | fileidx--;
160 | if (alternate > n || alternate == filecnt)
161 | alternate--;
162 | if (markidx > n || markidx == filecnt)
163 | markidx--;
164 | }
165 |
166 | void set_timeout(timeout_f handler, int time, bool overwrite)
167 | {
168 | int i;
169 |
170 | for (i = 0; i < ARRLEN(timeouts); i++) {
171 | if (timeouts[i].handler == handler) {
172 | if (!timeouts[i].active || overwrite) {
173 | gettimeofday(&timeouts[i].when, 0);
174 | TV_ADD_MSEC(&timeouts[i].when, time);
175 | timeouts[i].active = true;
176 | }
177 | return;
178 | }
179 | }
180 | }
181 |
182 | void reset_timeout(timeout_f handler)
183 | {
184 | int i;
185 |
186 | for (i = 0; i < ARRLEN(timeouts); i++) {
187 | if (timeouts[i].handler == handler) {
188 | timeouts[i].active = false;
189 | return;
190 | }
191 | }
192 | }
193 |
194 | bool check_timeouts(struct timeval *t)
195 | {
196 | int i = 0, tdiff, tmin = -1;
197 | struct timeval now;
198 |
199 | while (i < ARRLEN(timeouts)) {
200 | if (timeouts[i].active) {
201 | gettimeofday(&now, 0);
202 | tdiff = TV_DIFF(&timeouts[i].when, &now);
203 | if (tdiff <= 0) {
204 | timeouts[i].active = false;
205 | if (timeouts[i].handler != NULL)
206 | timeouts[i].handler();
207 | i = tmin = -1;
208 | } else if (tmin < 0 || tdiff < tmin) {
209 | tmin = tdiff;
210 | }
211 | }
212 | i++;
213 | }
214 | if (tmin > 0 && t != NULL)
215 | TV_SET_MSEC(t, tmin);
216 | return tmin > 0;
217 | }
218 |
219 | void close_info(void)
220 | {
221 | if (info.fd != -1) {
222 | kill(info.pid, SIGTERM);
223 | close(info.fd);
224 | info.fd = -1;
225 | }
226 | }
227 |
228 | void open_info(void)
229 | {
230 | int pfd[2];
231 | char w[12], h[12];
232 |
233 | if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0)
234 | return;
235 | win.bar.l.buf[0] = '\0';
236 | if (pipe(pfd) < 0)
237 | return;
238 | if ((info.pid = fork()) == 0) {
239 | close(pfd[0]);
240 | dup2(pfd[1], 1);
241 | snprintf(w, sizeof(w), "%d", img.w);
242 | snprintf(h, sizeof(h), "%d", img.h);
243 | execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL);
244 | error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd);
245 | }
246 | close(pfd[1]);
247 | if (info.pid < 0) {
248 | close(pfd[0]);
249 | } else {
250 | fcntl(pfd[0], F_SETFL, O_NONBLOCK);
251 | info.fd = pfd[0];
252 | info.i = info.lastsep = 0;
253 | }
254 | }
255 |
256 | void read_info(void)
257 | {
258 | ssize_t i, n;
259 | char buf[BAR_L_LEN];
260 |
261 | while (true) {
262 | n = read(info.fd, buf, sizeof(buf));
263 | if (n < 0 && errno == EAGAIN)
264 | return;
265 | else if (n == 0)
266 | goto end;
267 | for (i = 0; i < n; i++) {
268 | if (buf[i] == '\n') {
269 | if (info.lastsep == 0) {
270 | win.bar.l.buf[info.i++] = ' ';
271 | info.lastsep = 1;
272 | }
273 | } else {
274 | win.bar.l.buf[info.i++] = buf[i];
275 | info.lastsep = 0;
276 | }
277 | if (info.i + 1 == win.bar.l.size)
278 | goto end;
279 | }
280 | }
281 | end:
282 | info.i -= info.lastsep;
283 | win.bar.l.buf[info.i] = '\0';
284 | win_draw(&win);
285 | close_info();
286 | }
287 |
288 | void load_image(int new)
289 | {
290 | bool prev = new < fileidx;
291 | static int current;
292 |
293 | if (new < 0 || new >= filecnt)
294 | return;
295 |
296 | if (win.xwin != None)
297 | win_set_cursor(&win, CURSOR_WATCH);
298 | reset_timeout(slideshow);
299 |
300 | if (new != current)
301 | alternate = current;
302 |
303 | img_close(&img, false);
304 | while (!img_load(&img, &files[new])) {
305 | remove_file(new, false);
306 | if (new >= filecnt)
307 | new = filecnt - 1;
308 | else if (new > 0 && prev)
309 | new--;
310 | }
311 | files[new].flags &= ~FF_WARN;
312 | fileidx = current = new;
313 |
314 | close_info();
315 | open_info();
316 | arl_setup(&arl, files[fileidx].path);
317 |
318 | if (img.multi.cnt > 0 && img.multi.animate)
319 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
320 | else
321 | reset_timeout(animate);
322 | }
323 |
324 | bool mark_image(int n, bool on)
325 | {
326 | markidx = n;
327 | if (!!(files[n].flags & FF_MARK) != on) {
328 | files[n].flags ^= FF_MARK;
329 | markcnt += on ? 1 : -1;
330 | if (mode == MODE_THUMB)
331 | tns_mark(&tns, n, on);
332 | return true;
333 | }
334 | return false;
335 | }
336 |
337 | void bar_put(win_bar_t *bar, const char *fmt, ...)
338 | {
339 | size_t len = bar->size - (bar->p - bar->buf), n;
340 | va_list ap;
341 |
342 | va_start(ap, fmt);
343 | n = vsnprintf(bar->p, len, fmt, ap);
344 | bar->p += MIN(len, n);
345 | va_end(ap);
346 | }
347 |
348 | #define BAR_SEP " "
349 |
350 | void update_info(void)
351 | {
352 | unsigned int i, fn, fw;
353 | const char * mark;
354 | win_bar_t *l = &win.bar.l, *r = &win.bar.r;
355 |
356 | /* update bar contents */
357 | if (win.bar.h == 0)
358 | return;
359 | for (fw = 0, i = filecnt; i > 0; fw++, i /= 10);
360 | mark = files[fileidx].flags & FF_MARK ? "* " : "";
361 | l->p = l->buf;
362 | r->p = r->buf;
363 | if (mode == MODE_THUMB) {
364 | if (tns.loadnext < tns.end)
365 | bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1);
366 | else if (tns.initnext < filecnt)
367 | bar_put(l, "Caching... %0*d", fw, tns.initnext + 1);
368 | else
369 | strncpy(l->buf, files[fileidx].name, l->size);
370 | bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt);
371 | } else {
372 | bar_put(r, "%s", mark);
373 | if (img.ss.on) {
374 | if (img.ss.delay % 10 != 0)
375 | bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10);
376 | else
377 | bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10);
378 | }
379 | if (img.gamma != 0)
380 | bar_put(r, "G%+d" BAR_SEP, img.gamma);
381 | bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0));
382 | if (img.multi.cnt > 0) {
383 | for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10);
384 | bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt);
385 | }
386 | bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt);
387 | if (info.f.err)
388 | strncpy(l->buf, files[fileidx].name, l->size);
389 | }
390 | }
391 |
392 | int ptr_third_x(void)
393 | {
394 | int x, y;
395 |
396 | win_cursor_pos(&win, &x, &y);
397 | return MAX(0, MIN(2, (x / (win.w * 0.33))));
398 | }
399 |
400 | void redraw(void)
401 | {
402 | int t;
403 |
404 | if (mode == MODE_IMAGE) {
405 | img_render(&img);
406 | if (img.ss.on) {
407 | t = img.ss.delay * 100;
408 | if (img.multi.cnt > 0 && img.multi.animate)
409 | t = MAX(t, img.multi.length);
410 | set_timeout(slideshow, t, false);
411 | }
412 | } else {
413 | tns_render(&tns);
414 | }
415 | update_info();
416 | win_draw(&win);
417 | reset_timeout(redraw);
418 | reset_cursor();
419 | }
420 |
421 | void reset_cursor(void)
422 | {
423 | int c, i;
424 | cursor_t cursor = CURSOR_NONE;
425 |
426 | if (mode == MODE_IMAGE) {
427 | for (i = 0; i < ARRLEN(timeouts); i++) {
428 | if (timeouts[i].handler == reset_cursor) {
429 | if (timeouts[i].active) {
430 | c = ptr_third_x();
431 | c = MAX(fileidx > 0 ? 0 : 1, c);
432 | c = MIN(fileidx + 1 < filecnt ? 2 : 1, c);
433 | cursor = imgcursor[c];
434 | }
435 | break;
436 | }
437 | }
438 | } else {
439 | if (tns.loadnext < tns.end || tns.initnext < filecnt)
440 | cursor = CURSOR_WATCH;
441 | else
442 | cursor = CURSOR_ARROW;
443 | }
444 | win_set_cursor(&win, cursor);
445 | }
446 |
447 | void animate(void)
448 | {
449 | if (img_frame_animate(&img)) {
450 | redraw();
451 | set_timeout(animate, img.multi.frames[img.multi.sel].delay, true);
452 | }
453 | }
454 |
455 | void slideshow(void)
456 | {
457 | load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0);
458 | redraw();
459 | }
460 |
461 | void clear_resize(void)
462 | {
463 | resized = false;
464 | }
465 |
466 | Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg)
467 | {
468 | return ev->type == ButtonPress || ev->type == KeyPress;
469 | }
470 |
471 | void run_key_handler(const char *key, unsigned int mask)
472 | {
473 | pid_t pid;
474 | FILE *pfs;
475 | bool marked = mode == MODE_THUMB && markcnt > 0;
476 | bool changed = false;
477 | int f, i, pfd[2];
478 | int fcnt = marked ? markcnt : 1;
479 | char kstr[32];
480 | struct stat *oldst, st;
481 | XEvent dump;
482 |
483 | if (keyhandler.f.err != 0) {
484 | if (!keyhandler.warned) {
485 | error(0, keyhandler.f.err, "%s", keyhandler.f.cmd);
486 | keyhandler.warned = true;
487 | }
488 | return;
489 | }
490 | if (key == NULL)
491 | return;
492 |
493 | if (pipe(pfd) < 0) {
494 | error(0, errno, "pipe");
495 | return;
496 | }
497 | if ((pfs = fdopen(pfd[1], "w")) == NULL) {
498 | error(0, errno, "open pipe");
499 | close(pfd[0]), close(pfd[1]);
500 | return;
501 | }
502 | oldst = emalloc(fcnt * sizeof(*oldst));
503 |
504 | close_info();
505 | strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size);
506 | win_draw(&win);
507 | win_set_cursor(&win, CURSOR_WATCH);
508 |
509 | snprintf(kstr, sizeof(kstr), "%s%s%s%s",
510 | mask & ControlMask ? "C-" : "",
511 | mask & Mod1Mask ? "M-" : "",
512 | mask & ShiftMask ? "S-" : "", key);
513 |
514 | if ((pid = fork()) == 0) {
515 | close(pfd[1]);
516 | dup2(pfd[0], 0);
517 | execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL);
518 | error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd);
519 | }
520 | close(pfd[0]);
521 | if (pid < 0) {
522 | error(0, errno, "fork");
523 | fclose(pfs);
524 | goto end;
525 | }
526 |
527 | for (f = i = 0; f < fcnt; i++) {
528 | if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
529 | stat(files[i].path, &oldst[f]);
530 | fprintf(pfs, "%s\n", files[i].name);
531 | f++;
532 | }
533 | }
534 | fclose(pfs);
535 | while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
536 |
537 | for (f = i = 0; f < fcnt; i++) {
538 | if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) {
539 | if (stat(files[i].path, &st) != 0 ||
540 | memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0)
541 | {
542 | if (tns.thumbs != NULL) {
543 | tns_unload(&tns, i);
544 | tns.loadnext = MIN(tns.loadnext, i);
545 | }
546 | changed = true;
547 | }
548 | f++;
549 | }
550 | }
551 | /* drop user input events that occurred while running the key handler */
552 | while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL));
553 |
554 | end:
555 | if (mode == MODE_IMAGE) {
556 | if (changed) {
557 | img_close(&img, true);
558 | load_image(fileidx);
559 | } else {
560 | open_info();
561 | }
562 | }
563 | free(oldst);
564 | reset_cursor();
565 | redraw();
566 | }
567 |
568 | #define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask))
569 |
570 | void on_keypress(XKeyEvent *kev)
571 | {
572 | int i;
573 | unsigned int sh = 0;
574 | KeySym ksym, shksym;
575 | char dummy, key;
576 | bool dirty = false;
577 |
578 | XLookupString(kev, &key, 1, &ksym, NULL);
579 |
580 | if (kev->state & ShiftMask) {
581 | kev->state &= ~ShiftMask;
582 | XLookupString(kev, &dummy, 1, &shksym, NULL);
583 | kev->state |= ShiftMask;
584 | if (ksym != shksym)
585 | sh = ShiftMask;
586 | }
587 | if (IsModifierKey(ksym))
588 | return;
589 | if (ksym == XK_Escape && MODMASK(kev->state) == 0) {
590 | extprefix = False;
591 | } else if (extprefix) {
592 | run_key_handler(XKeysymToString(ksym), kev->state & ~sh);
593 | extprefix = False;
594 | } else if (key >= '0' && key <= '9') {
595 | /* number prefix for commands */
596 | prefix = prefix * 10 + (int) (key - '0');
597 | return;
598 | } else for (i = 0; i < ARRLEN(keys); i++) {
599 | if (keys[i].ksym == ksym &&
600 | MODMASK(keys[i].mask | sh) == MODMASK(kev->state) &&
601 | keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT &&
602 | (cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode))
603 | {
604 | if (cmds[keys[i].cmd].func(keys[i].arg))
605 | dirty = true;
606 | }
607 | }
608 | if (dirty)
609 | redraw();
610 | prefix = 0;
611 | }
612 |
613 | void on_buttonpress(XButtonEvent *bev)
614 | {
615 | int i, sel;
616 | bool dirty = false;
617 | static Time firstclick;
618 |
619 | if (mode == MODE_IMAGE) {
620 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
621 | reset_cursor();
622 |
623 | for (i = 0; i < ARRLEN(buttons); i++) {
624 | if (buttons[i].button == bev->button &&
625 | MODMASK(buttons[i].mask) == MODMASK(bev->state) &&
626 | buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT &&
627 | (cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode))
628 | {
629 | if (cmds[buttons[i].cmd].func(buttons[i].arg))
630 | dirty = true;
631 | }
632 | }
633 | if (dirty)
634 | redraw();
635 | } else {
636 | /* thumbnail mode (hard-coded) */
637 | switch (bev->button) {
638 | case Button1:
639 | if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
640 | if (sel != fileidx) {
641 | tns_highlight(&tns, fileidx, false);
642 | tns_highlight(&tns, sel, true);
643 | fileidx = sel;
644 | firstclick = bev->time;
645 | redraw();
646 | } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) {
647 | mode = MODE_IMAGE;
648 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
649 | load_image(fileidx);
650 | redraw();
651 | } else {
652 | firstclick = bev->time;
653 | }
654 | }
655 | break;
656 | case Button3:
657 | if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
658 | bool on = !(files[sel].flags & FF_MARK);
659 | XEvent e;
660 |
661 | for (;;) {
662 | if (sel >= 0 && mark_image(sel, on))
663 | redraw();
664 | XMaskEvent(win.env.dpy,
665 | ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e);
666 | if (e.type == ButtonPress || e.type == ButtonRelease)
667 | break;
668 | while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e));
669 | sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y);
670 | }
671 | }
672 | break;
673 | case Button4:
674 | case Button5:
675 | if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN,
676 | (bev->state & ControlMask) != 0))
677 | redraw();
678 | break;
679 | }
680 | }
681 | prefix = 0;
682 | }
683 |
684 | const struct timespec ten_ms = {0, 10000000};
685 |
686 | void run(void)
687 | {
688 | int xfd;
689 | fd_set fds;
690 | struct timeval timeout;
691 | bool discard, init_thumb, load_thumb, to_set;
692 | XEvent ev, nextev;
693 |
694 | while (true) {
695 | to_set = check_timeouts(&timeout);
696 | init_thumb = mode == MODE_THUMB && tns.initnext < filecnt;
697 | load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end;
698 |
699 | if ((init_thumb || load_thumb || to_set || info.fd != -1 ||
700 | arl.fd != -1) && XPending(win.env.dpy) == 0)
701 | {
702 | if (load_thumb) {
703 | set_timeout(redraw, TO_REDRAW_THUMBS, false);
704 | if (!tns_load(&tns, tns.loadnext, false, false)) {
705 | remove_file(tns.loadnext, false);
706 | tns.dirty = true;
707 | }
708 | if (tns.loadnext >= tns.end)
709 | redraw();
710 | } else if (init_thumb) {
711 | set_timeout(redraw, TO_REDRAW_THUMBS, false);
712 | if (!tns_load(&tns, tns.initnext, false, true))
713 | remove_file(tns.initnext, false);
714 | } else {
715 | xfd = ConnectionNumber(win.env.dpy);
716 | FD_ZERO(&fds);
717 | FD_SET(xfd, &fds);
718 | if (info.fd != -1) {
719 | FD_SET(info.fd, &fds);
720 | xfd = MAX(xfd, info.fd);
721 | }
722 | if (arl.fd != -1) {
723 | FD_SET(arl.fd, &fds);
724 | xfd = MAX(xfd, arl.fd);
725 | }
726 | select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL);
727 | if (info.fd != -1 && FD_ISSET(info.fd, &fds))
728 | read_info();
729 | if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) {
730 | if (arl_handle(&arl)) {
731 | /* when too fast, imlib2 can't load the image */
732 | nanosleep(&ten_ms, NULL);
733 | img_close(&img, true);
734 | load_image(fileidx);
735 | redraw();
736 | }
737 | }
738 | }
739 | continue;
740 | }
741 |
742 | do {
743 | XNextEvent(win.env.dpy, &ev);
744 | discard = false;
745 | if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) {
746 | XPeekEvent(win.env.dpy, &nextev);
747 | switch (ev.type) {
748 | case ConfigureNotify:
749 | case MotionNotify:
750 | discard = ev.type == nextev.type;
751 | break;
752 | case KeyPress:
753 | discard = (nextev.type == KeyPress || nextev.type == KeyRelease)
754 | && ev.xkey.keycode == nextev.xkey.keycode;
755 | break;
756 | }
757 | }
758 | } while (discard);
759 |
760 | switch (ev.type) {
761 | /* handle events */
762 | case ButtonPress:
763 | on_buttonpress(&ev.xbutton);
764 | break;
765 | case ClientMessage:
766 | if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW])
767 | cmds[g_quit].func(0);
768 | break;
769 | case ConfigureNotify:
770 | if (win_configure(&win, &ev.xconfigure)) {
771 | if (mode == MODE_IMAGE) {
772 | img.dirty = true;
773 | img.checkpan = true;
774 | } else {
775 | tns.dirty = true;
776 | }
777 | if (!resized) {
778 | redraw();
779 | set_timeout(clear_resize, TO_REDRAW_RESIZE, false);
780 | resized = true;
781 | } else {
782 | set_timeout(redraw, TO_REDRAW_RESIZE, false);
783 | }
784 | }
785 | break;
786 | case KeyPress:
787 | on_keypress(&ev.xkey);
788 | break;
789 | case MotionNotify:
790 | if (mode == MODE_IMAGE) {
791 | set_timeout(reset_cursor, TO_CURSOR_HIDE, true);
792 | reset_cursor();
793 | }
794 | break;
795 | }
796 | }
797 | }
798 |
799 | int fncmp(const void *a, const void *b)
800 | {
801 | return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name);
802 | }
803 |
804 | void sigchld(int sig)
805 | {
806 | while (waitpid(-1, NULL, WNOHANG) > 0);
807 | }
808 |
809 | void setup_signal(int sig, void (*handler)(int sig))
810 | {
811 | struct sigaction sa;
812 |
813 | sa.sa_handler = handler;
814 | sigemptyset(&sa.sa_mask);
815 | sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
816 | if (sigaction(sig, &sa, 0) == -1)
817 | error(EXIT_FAILURE, errno, "signal %d", sig);
818 | }
819 |
820 | int main(int argc, char **argv)
821 | {
822 | int i, start;
823 | size_t n;
824 | ssize_t len;
825 | char *filename;
826 | const char *homedir, *dsuffix = "";
827 | struct stat fstats;
828 | r_dir_t dir;
829 |
830 | setup_signal(SIGCHLD, sigchld);
831 | setup_signal(SIGPIPE, SIG_IGN);
832 |
833 | setlocale(LC_COLLATE, "");
834 |
835 | parse_options(argc, argv);
836 |
837 | if (options->clean_cache) {
838 | tns_init(&tns, NULL, NULL, NULL, NULL);
839 | tns_clean_cache(&tns);
840 | exit(EXIT_SUCCESS);
841 | }
842 |
843 | if (options->filecnt == 0 && !options->from_stdin) {
844 | print_usage();
845 | exit(EXIT_FAILURE);
846 | }
847 |
848 | if (options->recursive || options->from_stdin)
849 | filecnt = 1024;
850 | else
851 | filecnt = options->filecnt;
852 |
853 | files = emalloc(filecnt * sizeof(*files));
854 | memset(files, 0, filecnt * sizeof(*files));
855 | fileidx = 0;
856 |
857 | if (options->from_stdin) {
858 | n = 0;
859 | filename = NULL;
860 | while ((len = getline(&filename, &n, stdin)) > 0) {
861 | if (filename[len-1] == '\n')
862 | filename[len-1] = '\0';
863 | check_add_file(filename, true);
864 | }
865 | free(filename);
866 | }
867 |
868 | for (i = 0; i < options->filecnt; i++) {
869 | filename = options->filenames[i];
870 |
871 | if (stat(filename, &fstats) < 0) {
872 | error(0, errno, "%s", filename);
873 | continue;
874 | }
875 | if (!S_ISDIR(fstats.st_mode)) {
876 | check_add_file(filename, true);
877 | } else {
878 | if (r_opendir(&dir, filename, options->recursive) < 0) {
879 | error(0, errno, "%s", filename);
880 | continue;
881 | }
882 | start = fileidx;
883 | while ((filename = r_readdir(&dir, true)) != NULL) {
884 | check_add_file(filename, false);
885 | free((void*) filename);
886 | }
887 | r_closedir(&dir);
888 | if (fileidx - start > 1)
889 | qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp);
890 | }
891 | }
892 |
893 | if (fileidx == 0)
894 | error(EXIT_FAILURE, 0, "No valid image file given, aborting");
895 |
896 | filecnt = fileidx;
897 | fileidx = options->startnum < filecnt ? options->startnum : 0;
898 |
899 | for (i = 0; i < ARRLEN(buttons); i++) {
900 | if (buttons[i].cmd == i_cursor_navigate) {
901 | imgcursor[0] = CURSOR_LEFT;
902 | imgcursor[2] = CURSOR_RIGHT;
903 | break;
904 | }
905 | }
906 |
907 | win_init(&win);
908 | img_init(&img, &win);
909 | arl_init(&arl);
910 |
911 | if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') {
912 | homedir = getenv("HOME");
913 | dsuffix = "/.config";
914 | }
915 | if (homedir != NULL) {
916 | extcmd_t *cmd[] = { &info.f, &keyhandler.f };
917 | const char *name[] = { "image-info", "key-handler" };
918 |
919 | for (i = 0; i < ARRLEN(cmd); i++) {
920 | n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12;
921 | cmd[i]->cmd = (char*) emalloc(n);
922 | snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]);
923 | if (access(cmd[i]->cmd, X_OK) != 0)
924 | cmd[i]->err = errno;
925 | }
926 | } else {
927 | error(0, 0, "Exec directory not found");
928 | }
929 | info.fd = -1;
930 |
931 | if (options->thumb_mode) {
932 | mode = MODE_THUMB;
933 | tns_init(&tns, files, &filecnt, &fileidx, &win);
934 | while (!tns_load(&tns, fileidx, false, false))
935 | remove_file(fileidx, false);
936 | } else {
937 | mode = MODE_IMAGE;
938 | tns.thumbs = NULL;
939 | load_image(fileidx);
940 | }
941 | win_open(&win);
942 | win_set_cursor(&win, CURSOR_WATCH);
943 |
944 | atexit(cleanup);
945 |
946 | set_timeout(redraw, 25, false);
947 |
948 | run();
949 |
950 | return 0;
951 | }
952 |
--------------------------------------------------------------------------------